Hi,
I added RollingWindow to a Moving Average strategy for futures. There are few examples of futures. I tried my best to write this strategy, but it still does not work. Can anyone point out what is wrong with my Algo? Thanks.
import clr
import decimal as d
import pandas as pd
class FuturesMovingAverage (QCAlgorithm):
def Initialize(self):
self.contract = None
self.SetStartDate(2019, 1, 1)
self.SetEndDate(2020, 1, 1)
self.SetCash(100000)
self.SetWarmUp(TimeSpan.FromDays(5))
self.SetTimeZone("America/New_York")
self.new_day = True
self.reset = True
CL = self.AddFuture(Futures.Energies.CrudeOilWTI)
CL.SetFilter(TimeSpan.Zero, TimeSpan.FromDays(360))
self.consolidators = {}
self.movingAverages = {}
self.sma = None
self.sma_period = 60
self.window = RollingWindow[TradeBar](3)
def OnData(self, slice, data):
if not self.InitUpdateContract(slice):
return
if self.reset:
self.reset = False
self.window.Add(data["CL"])
def InitUpdateContract(self, slice):
if not self.new_day:
return True
if self.contract != None and (self.contract.Expiry - self.Time).days >= 3:
return True
for chain in slice.FutureChains.Values:
idx = 0
if self.contract != None:
self.Log('Expiry days away {} - {}'.format((self.contract.Expiry-self.Time).days, self.contract.Expiry))
if self.contract != None and (self.contract.Expiry - self.Time).days < 3:
idx = 1
contracts = list(chain.Contracts.Values)
chain_contracts = list(contracts)
chain_contracts = sorted(chain_contracts, key=lambda x: x.Expiry)
if len(chain_contracts) < 2:
return False
first = chain_contracts[idx]
second = chain_contracts[idx+1]
if (first.Expiry - self.Time).days >= 3:
self.contract = first
elif (first.Expiry - self.Time).days < 3 and (second.Expiry - self.Time).days >= 3:
self.contract = second
self.Log("Setting contract to: {}".format(self.contract.Symbol.Value))
self.new_day = False
self.reset = True
if self.contract.Symbol not in self.consolidators:
self.consolidators[self.contract.Symbol] = self.Consolidate(
self.contract.Symbol, timedelta(minutes=5), self.OnFiveMinutes)
if self.contract.Symbol not in self.movingAverages:
self.movingAverages[self.contract.Symbol] = self.SMA(self.contract.Symbol, self.sma_period, Resolution.Minute)
history = self.History(self.contract.Symbol, self.sma_period, Resolution.Minute).close.unstack(1)
for index, close in history[self.contract.Symbol].items():
time = index[1]
self.movingAverages[self.contract.Symbol].Update(time, close)
return True
return False
def OnFiveMinutes(self, bar):
if not self.window.IsReady:
return
currentBar = self.window[0]
nearBar = self.window[1]
farBar = self.window[2]
if bar.Symbol != self.contract.Symbol:
return
sma = self.movingAverages.get(bar.Symbol, None)
if sma is None or not sma.IsReady:
return
near = nearBar.Close
far = farBar.Close
sma = sma.Current.Value
price = bar.Close
holdings = self.Portfolio[bar.Symbol].Quantity
if not self.Portfolio.Invested:
if far < sma and near > sma and price > near:
self.MarketOrder(bar.Symbol, 1)
if far > sma and near < sma and price < near:
self.MarketOrder(bar.Symbol, -1)
if holdings > 0 and price < sma:
self.Liquidate(bar.Symbol)
if holdings < 0 and price > sma:
self.Liquidate(bar.Symbol)
def OnEndOfDay(self):
self.new_day = True
Derek Melchin
Hi Croft,
Can you re-upload the code or attach a backtest? It is difficult to debug as the indentation has been removed.
Best,
Derek Melchin
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
Croft
import clr import decimal as d import pandas as pd class FuturesMovingAverage (QCAlgorithm): def Initialize(self): self.contract = None self.SetStartDate(2019, 1, 1) self.SetEndDate(2020, 1, 1) self.SetCash(100000) self.SetWarmUp(TimeSpan.FromDays(5)) self.SetTimeZone("America/New_York") self.new_day = True self.reset = True CL = self.AddFuture(Futures.Energies.CrudeOilWTI) CL.SetFilter(TimeSpan.Zero, TimeSpan.FromDays(360)) self.consolidators = {} self.movingAverages = {} self.sma = None self.sma_period = 60 self.window = RollingWindow[TradeBar](3) def OnData(self, slice, data): if not self.InitUpdateContract(slice): return if self.reset: self.reset = False self.window.Add(data["CL"]) def InitUpdateContract(self, slice): if not self.new_day: return True if self.contract != None and (self.contract.Expiry - self.Time).days >= 3: return True for chain in slice.FutureChains.Values: idx = 0 if self.contract != None: self.Log('Expiry days away {} - {}'.format((self.contract.Expiry-self.Time).days, self.contract.Expiry)) if self.contract != None and (self.contract.Expiry - self.Time).days < 3: idx = 1 contracts = list(chain.Contracts.Values) chain_contracts = list(contracts) chain_contracts = sorted(chain_contracts, key=lambda x: x.Expiry) if len(chain_contracts) < 2: return False first = chain_contracts[idx] second = chain_contracts[idx+1] if (first.Expiry - self.Time).days >= 3: self.contract = first elif (first.Expiry - self.Time).days < 3 and (second.Expiry - self.Time).days >= 3: self.contract = second self.Log("Setting contract to: {}".format(self.contract.Symbol.Value)) self.new_day = False self.reset = True if self.contract.Symbol not in self.consolidators: self.consolidators[self.contract.Symbol] = self.Consolidate( self.contract.Symbol, timedelta(minutes=5), self.OnFiveMinutes) if self.contract.Symbol not in self.movingAverages: self.movingAverages[self.contract.Symbol] = self.SMA(self.contract.Symbol, self.sma_period, Resolution.Minute) history = self.History(self.contract.Symbol, self.sma_period, Resolution.Minute).close.unstack(1) for index, close in history[self.contract.Symbol].items(): time = index[1] self.movingAverages[self.contract.Symbol].Update(time, close) return True return False def OnFiveMinutes(self, bar): if not self.window.IsReady: return currentBar = self.window[0] nearBar = self.window[1] farBar = self.window[2] if bar.Symbol != self.contract.Symbol: return sma = self.movingAverages.get(bar.Symbol, None) if sma is None or not sma.IsReady: return near = nearBar.Close far = farBar.Close sma = sma.Current.Value price = bar.Close holdings = self.Portfolio[bar.Symbol].Quantity if not self.Portfolio.Invested: if far < sma and near > sma and price > near: self.MarketOrder(bar.Symbol, 1) if far > sma and near < sma and price < near: self.MarketOrder(bar.Symbol, -1) if holdings > 0 and price < sma: self.Liquidate(bar.Symbol) if holdings < 0 and price > sma: self.Liquidate(bar.Symbol) def OnEndOfDay(self): self.new_day = True
Hope this time it works.
Rahul Chowdhury
Hey Croft,
There are a few things to note.
1. The OnData(self, slice) method should only have 1 parameter. The self refers to the instance of the class and does not count as a parameter. OnData's parameter is the Slice object which contains all the data currently available. This Slice object can be referred to with any variable name, in this instance OnData(self, slice), we are referring to the Slice parameter with the name slice. But in general, we can use any name. You may notice that in other examples/algorithms, the Slice variable is referred to with the name data; OnData(self, data). Both OnData(self, data) and OnData(self, slice) are both valid and do not change the behavior of OnData. In the algorithm you provided , both slice and data appear in OnData(self, slice, data), which is incorrect because 2 parameters are provided, when OnData should only provide 1. Let's choose 1 variable name to designate to the Slice object parameter; we can use OnData(self, slice).
2. This line is attempting to add bar data for the CL future chain.
self.window.Add(slice["CL"])
This does not work because the ticker "CL" does not refer to any one security. Instead, It refers to a chain of securities/futures contracts. So, we should add bar data for specific contracts in the chain.A simple solution is to add the 5 minute bars to our window as they are created.
def OnFiveMinutes(self, bar): self.window.Add(bar) ....
3. Before we attempt to access the close column in our history dataframe, we should make sure it is not empty. Otherwise, we may encounter an error.
history = self.History(self.contract.Symbol, self.sma_period, Resolution.Minute).close.unstack(1) # Instead use history = self.History(self.contract.Symbol, self.sma_period, Resolution.Minute) if not history.empty: history = history.close.unstack(1)
Make sure to also check out this Futures strategy which trades based on EMA crossovers on the front month contract.
Best
Rahul
Croft
Rahul, thank you so much.
Croft
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!