| Overall Statistics |
|
Total Orders 208 Average Win 0.15% Average Loss -0.21% Compounding Annual Return -1.116% Drawdown 4.700% Expectancy -0.161 Start Equity 1000000 End Equity 981424.92 Net Profit -1.858% Sharpe Ratio -1.395 Sortino Ratio -1.622 Probabilistic Sharpe Ratio 2.080% Loss Rate 51% Win Rate 49% Profit-Loss Ratio 0.73 Alpha -0.033 Beta -0.028 Annual Standard Deviation 0.024 Annual Variance 0.001 Information Ratio -0.49 Tracking Error 0.137 Treynor Ratio 1.192 Total Fees $642.20 Estimated Strategy Capacity $150000000.00 Lowest Capacity Asset ZL X7TG3O4R7OKL Portfolio Turnover 1.13% |
#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.set_start_date(2018, 1, 1)
self.set_end_date(2019, 9, 1)
self.set_cash(1000000)
symbols = [Futures.Meats.LIVE_CATTLE,
Futures.Meats.LEAN_HOGS,
Futures.Energies.BRENT_CRUDE,
Futures.Energies.LOW_SULFUR_GASOIL,
Futures.Softs.COTTON_2,
Futures.Softs.COFFEE,
Futures.Softs.COCOA,
Futures.Softs.SUGAR_11,
Futures.Grains.WHEAT,
Futures.Grains.CORN,
Futures.Grains.SOYBEANS,
Futures.Grains.SOYBEAN_MEAL,
Futures.Grains.SOYBEAN_OIL,
]
# Last trading date tracker to achieve rebalancing the portfolio every month
self.rebalancing_time = datetime.min
self.period = 252
self.symbol_data = {}
for symbol in symbols:
future = self.add_future(symbol,
resolution = Resolution.DAILY,
extended_market_hours = True,
data_normalization_mode = DataNormalizationMode.BACKWARDS_RATIO,
data_mapping_mode = DataMappingMode.OPEN_INTEREST,
contract_depth_offset = 0
)
future.set_leverage(1)
self.symbol_data[future.symbol] = SymbolData(self, future, self.period)
def on_data(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.symbol_changed_events.contains_key(symbol):
changed_event = data.symbol_changed_events[symbol]
old_symbol = changed_event.old_symbol
new_symbol = changed_event.new_symbol
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.market_order(new_symbol, quantity // self.securities[new_symbol].symbol_properties.contract_multiplier, tag = tag)
# skip if less than 30 days passed since the last trading date
if self.time < self.rebalancing_time:
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.to_string()
if symbol_id in weights:
weight = np.sign(symbol_data.value) * weights[symbol_id] *.5
mapped = symbol_data.mapped
qty = self.calculate_order_quantity(mapped, np.clip(weight, -1, 1))
multiplier = self.securities[mapped].symbol_properties.contract_multiplier
order_qty = (qty - self.portfolio[mapped].quantity) // multiplier
self.market_order(mapped, order_qty)
# Set next rebalance time
self.rebalancing_time = Expiry.end_of_month(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.warm_up_indicator(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 is_ready(self):
return self.ROC.is_ready