| Overall Statistics |
|
Total Orders 499 Average Win 1.85% Average Loss -1.90% Compounding Annual Return -3.180% Drawdown 49.500% Expectancy -0.024 Start Equity 10000000 End Equity 8507015.52 Net Profit -14.930% Sharpe Ratio 0.014 Sortino Ratio 0.014 Probabilistic Sharpe Ratio 0.776% Loss Rate 51% Win Rate 49% Profit-Loss Ratio 0.98 Alpha 0.003 Beta 0.033 Annual Standard Deviation 0.365 Annual Variance 0.133 Information Ratio -0.155 Tracking Error 0.389 Treynor Ratio 0.154 Total Fees $49839.47 Estimated Strategy Capacity $230000000.00 Lowest Capacity Asset SI Z1ZAHU02AQVX Portfolio Turnover 7.56% Drawdown Recovery 321 |
#region imports
from AlgorithmImports import *
from math import floor
#endregion
# https://quantpedia.com/Screener/Details/23
class CommodityMomentumCombinedWithTermStructureAlgorithm(QCAlgorithm):
def initialize(self):
self.set_start_date(self.end_date - timedelta(5*365))
self.set_cash(10_000_000)
self.settings.seed_initial_prices = True
self._lookback = int(7.5*60*21) # 21 trading days
# Add some Futures to the algorithm.
tickers = [
Futures.Softs.COCOA,
Futures.Softs.COFFEE,
Futures.Grains.CORN,
Futures.Softs.COTTON_2,
Futures.Grains.OATS,
Futures.Softs.ORANGE_JUICE,
Futures.Grains.SOYBEAN_MEAL,
Futures.Grains.SOYBEAN_OIL,
Futures.Grains.SOYBEANS,
Futures.Softs.SUGAR_11,
Futures.Grains.WHEAT,
Futures.Meats.FEEDER_CATTLE,
Futures.Meats.LEAN_HOGS,
Futures.Meats.LIVE_CATTLE,
Futures.Energies.CRUDE_OIL_WTI,
Futures.Energies.HEATING_OIL,
Futures.Energies.NATURAL_GAS,
Futures.Energies.GASOLINE,
Futures.Metals.MICRO_GOLD,
Futures.Metals.PALLADIUM,
Futures.Metals.PLATINUM,
Futures.Metals.SILVER
]
for ticker in tickers:
future = self.add_future(ticker)
future.set_filter(timedelta(0), timedelta(90))
# Add a warm-up period so the algorithm trades on deployment.
self.set_warmup(timedelta(45))
def on_securities_changed(self, changes):
for security in changes.added_securities:
roc = self.roc(security, 1)
security.mean_roc = IndicatorExtensions.sma(roc, self._lookback)
for bar in self.history[TradeBar](security, self._lookback, Resolution.MINUTE):
roc.update(bar)
def on_warmup_finished(self):
# Add a Scheduled event to rebalance the portfolio monthly.
time_rule = self.time_rules.after_market_open("SPY", 30)
self.schedule.on(self.date_rules.month_start("SPY"), time_rule, self._rebalance)
# Rebalance the portfolio today too.
if self.live_mode:
self._rebalance()
else:
self.schedule.on(self.date_rules.today, time_rule, self._rebalance)
def _rebalance(self):
# Calculate the roll return of each contract.
roll_return = {}
for symbol, chain in self.current_slice.future_chains.items():
if len(chain) < 2:
continue
contracts = sorted(chain, key=lambda x: x.expiry)
expiry_nearest = contracts[0].expiry
price_nearest = float(contracts[0].last_price) if contracts[0].last_price>0 else 0.5*float(contracts[0].ask_price+contracts[0].bid_price)
for x in contracts[1:]:
roll_return[x] = (price_nearest-float(x.last_price))*365 / (x.expiry-expiry_nearest).days
# Split the contracts into high and low roll return buckets.
sorted_by_roll_return = sorted(roll_return, key=lambda x: roll_return[x], reverse=True)
tertile = floor(1/3*len(sorted_by_roll_return))
high = sorted_by_roll_return[:tertile]
low = sorted_by_roll_return[-tertile:]
# For the contracts with the highest roll returns, select the subset with the greatest mean return.
mean_return_high = {i: self.securities[i.symbol].mean_roc.current.value for i in high}
high_winners = sorted(mean_return_high, key=lambda x: mean_return_high[x], reverse=True)[:int(len(high)*0.5)]
# For the contracts with the lowest roll returns, select the subset with the lowest mean return.
mean_return_low = {i: self.securities[i.symbol].mean_roc.current.value for i in high}
low_losers = sorted(mean_return_low, key=lambda x: mean_return_low[x], reverse=True)[-int(len(low)*0.5):]
# Form a long-short portfolio.
short_weight = -0.05/len(low_losers)
long_weight = 0.05/len(high_winners)
targets = [PortfolioTarget(short.symbol, short_weight) for short in low_losers]
targets += [PortfolioTarget(long_.symbol, long_weight) for long_ in high_winners]
self.set_holdings(targets, True)