Back

Moving Average with Rolling Window

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

 

 

 

Update Backtest







0

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.


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

0

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.


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.

0

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

1

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.


Rahul, thank you so much.

0

Update Backtest





0

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.


Loading...

This discussion is closed