| Overall Statistics |
|
Total Trades 206 Average Win 0.15% Average Loss -0.20% Compounding Annual Return -1.022% Drawdown 4.400% Expectancy -0.154 Net Profit -1.701% Sharpe Ratio -0.29 Probabilistic Sharpe Ratio 2.215% Loss Rate 52% Win Rate 48% Profit-Loss Ratio 0.77 Alpha -0.005 Beta -0.029 Annual Standard Deviation 0.023 Annual Variance 0.001 Information Ratio -0.486 Tracking Error 0.137 Treynor Ratio 0.234 Total Fees $654.55 Estimated Strategy Capacity $1200000000.00 Lowest Capacity Asset ZM XAB1S6F46C6D |
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/Screener/Details/118
from QuantConnect.Python import PythonQuandl
import numpy as np
class TimeSeriesMomentumEffect(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2018, 1, 1)
self.SetEndDate(2019, 9, 1)
self.SetCash(1000000)
symbols = [Futures.Meats.LiveCattle,
Futures.Meats.LeanHogs,
Futures.Energies.BrentCrude,
Futures.Energies.LowSulfurGasoil,
Futures.Softs.Cotton2,
Futures.Softs.Coffee,
Futures.Softs.Cocoa,
Futures.Softs.Sugar11,
Futures.Grains.Wheat,
Futures.Grains.Corn,
Futures.Grains.Soybeans,
Futures.Grains.SoybeanMeal,
Futures.Grains.SoybeanOil,
]
# Last trading date tracker to achieve rebalancing the portfolio every month
self.RebalancingTime = datetime.min
self.period = 252
self.symbol_data = {}
for symbol in symbols:
future = self.AddFuture(symbol,
resolution = Resolution.Daily,
extendedMarketHours = True,
dataNormalizationMode = DataNormalizationMode.BackwardsRatio,
dataMappingMode = DataMappingMode.OpenInterest,
contractDepthOffset = 0
)
future.SetLeverage(1)
self.symbol_data[future.Symbol] = SymbolData(self, future, self.period)
def OnData(self, data):
'''
Monthly rebalance at the beginning of each month.
'''
# Rollover for future contract mapping change
for symbol, symbol_data in self.symbol_data.items():
if data.SymbolChangedEvents.ContainsKey(symbol):
changed_event = data.SymbolChangedEvents[symbol]
old_symbol = changed_event.OldSymbol
new_symbol = changed_event.NewSymbol
tag = f"Rollover - Symbol changed at {self.Time}: {old_symbol} -> {new_symbol}"
quantity = self.Portfolio[old_symbol].Quantity
# Rolling over: to liquidate any position of the old mapped contract and switch to the newly mapped contract
self.Liquidate(old_symbol, tag = tag)
self.MarketOrder(new_symbol, quantity // self.Securities[new_symbol].SymbolProperties.ContractMultiplier, tag = tag)
# skip if less than 30 days passed since the last trading date
if self.Time < self.RebalancingTime:
return
# dataframe that contains the historical data for all securities
history = self.History(list(self.symbol_data.keys()), self.period, Resolution.Daily)
history = history.droplevel([0]).unstack(level=0).close.pct_change().dropna()
# Compute the inverse of the volatility
# The weights are the normalized inverse of the volatility
vol_inv = 1 / history.std(ddof=1)
vol_sum = vol_inv.sum()
weights = (vol_inv / vol_sum).fillna(0).to_dict()
#np.sign(roc.Current.Value) tells us whether to long or short
for symbol, symbol_data in self.symbol_data.items():
symbol_id = symbol.ID.ToString()
if symbol_id in weights:
weight = np.sign(symbol_data.Value) * weights[symbol_id] *.5
mapped = symbol_data.Mapped
qty = self.CalculateOrderQuantity(mapped, np.clip(weight, -1, 1))
multiplier = self.Securities[mapped].SymbolProperties.ContractMultiplier
order_qty = (qty - self.Portfolio[mapped].Quantity) // multiplier
self.MarketOrder(mapped, order_qty)
# Set next rebalance time
self.RebalancingTime = Expiry.EndOfMonth(self.Time)
class SymbolData:
def __init__(self, algorithm, future, period):
self._future = future
self.Symbol = future.Symbol
self.ROC = algorithm.ROC(future.Symbol, period)
algorithm.WarmUpIndicator(future.Symbol, self.ROC, Resolution.Daily)
@property
def Value(self):
return self.ROC.Current.Value
@property
def Mapped(self):
return self._future.Mapped
@property
def IsReady(self):
return self.ROC.IsReady