| Overall Statistics |
|
Total Orders 507 Average Win 1.96% Average Loss -2.09% Compounding Annual Return -18.586% Drawdown 79.300% Expectancy -0.135 Start Equity 10000000 End Equity 3574804.22 Net Profit -64.252% Sharpe Ratio -0.271 Sortino Ratio -0.249 Probabilistic Sharpe Ratio 0.093% Loss Rate 55% Win Rate 45% Profit-Loss Ratio 0.94 Alpha -0.102 Beta 0.002 Annual Standard Deviation 0.377 Annual Variance 0.142 Information Ratio -0.433 Tracking Error 0.403 Treynor Ratio -57.312 Total Fees $50503.52 Estimated Strategy Capacity $320000.00 Lowest Capacity Asset PL Z0A8S1BLJXS1 Portfolio Turnover 7.77% Drawdown Recovery 201 |
#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.settings.seed_initial_prices = True
self.set_cash(10_000_000)
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))
self.schedule.on(self.date_rules.month_start("SPY"), self.time_rules.after_market_open("SPY", 30), self._rebalance)
def _rebalance(self):
roll_return_by_contract = {}
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 = contracts[0].last_price or 0.5*(contracts[0].ask_price + contracts[0].bid_price)
for c in contracts[1:]:
if not c.open_interest:
continue
security = self.securities[c.symbol]
if not security.price:
continue
roll_return_by_contract[security] = (price_nearest-c.last_price)*365 / (c.expiry-expiry_nearest).days
sorted_by_roll_return = sorted(roll_return_by_contract, key=lambda c: roll_return_by_contract[c], reverse=True)
tertile = floor(1/3*len(sorted_by_roll_return))
targets = self._create_targets(sorted_by_roll_return[:tertile])
targets += self._create_targets(sorted_by_roll_return[-tertile:], False, -1)
self.set_holdings(targets, True)
def _create_targets(self, contracts, reverse=True, direction=1):
selected_contracts = sorted(
contracts,
key=lambda contract: contract.mean_return.current.value,
reverse=reverse
)[:int(len(contracts)*0.5)]
weight = direction*0.05/len(selected_contracts)
return [PortfolioTarget(contract.symbol, weight) for contract in selected_contracts]
def on_securities_changed(self, changes):
# Add an indicator to each contract to track its mean daily return.
for security in changes.added_securities:
roc = self.roc(security, 1, Resolution.DAILY)
security.mean_return = IndicatorExtensions.sma(roc, 21)
for bar in self.history[TradeBar](security, 22, Resolution.DAILY):
roc.update(bar)