Overall Statistics |
Total Trades 410 Average Win 2.48% Average Loss -1.96% Compounding Annual Return -0.441% Drawdown 26.700% Expectancy -0.001 Net Profit -5.551% Sharpe Ratio 0.01 Probabilistic Sharpe Ratio 0.001% Loss Rate 56% Win Rate 44% Profit-Loss Ratio 1.26 Alpha -0.001 Beta 0.018 Annual Standard Deviation 0.089 Annual Variance 0.008 Information Ratio -0.561 Tracking Error 0.169 Treynor Ratio 0.052 Total Fees $10127.00 Estimated Strategy Capacity $850000000.00 Lowest Capacity Asset ZM Y4B76QPA3Z8L |
from AlgorithmImports import * from datetime import timedelta import numpy as np class FuturesMomentumEffect(QCAlgorithm): '''Basic template algorithm simply initializes the date range and cash''' def Initialize(self): self.SetStartDate(2010, 1, 1) # self.SetEndDate(2020, 10, 1) self.SetCash(1000000) futures_universe = [ Futures.Grains.Wheat, Futures.Grains.HRWWheat, Futures.Grains.Corn, Futures.Grains.Soybeans, Futures.Grains.SoybeanMeal, Futures.Grains.SoybeanOil ] indicator_period = 21 # lookback_multiplier = 1 # holding_multiplier = 6 lookback_multiplier = int(self.GetParameter("lookback-period")) holding_multiplier = int(self.GetParameter("holding-period")) self.continuous_contracts = {} self.logr_indicators = {} for i in futures_universe: future = self.AddFuture(i, dataNormalizationMode=DataNormalizationMode.BackwardsRatio, dataMappingMode=DataMappingMode.FirstDayMonth, contractDepthOffset=0, resolution=Resolution.Daily) future.SetFilter(0, 182) self.continuous_contracts[future.Symbol.Value] = future self.logr_indicators[future.Symbol.Value] = self.LOGR( future.Symbol, indicator_period * lookback_multiplier, Resolution.Daily) self.nextRebalance = self.Time self.Rebalancetime = timedelta(30) * (holding_multiplier-1) # set warmup self.SetWarmUp(timedelta(indicator_period * lookback_multiplier)) self.Log( f"Testing - lookback period: {lookback_multiplier} months, holding period: {holding_multiplier} months") self.test_tag = f"({lookback_multiplier}-{holding_multiplier})" self.most_liquid_contracts = {} self.long_holdings = {} self.short_holdings = {} self.quantity = 10 self.SetSecurityInitializer(BrokerageModelSecurityInitializer(self.BrokerageModel, FuncSecuritySeeder(self.GetLastKnownPrices))) def OnData(self, slice: Slice) -> None: if self.IsWarmingUp: return for changed_event in slice.SymbolChangedEvents.Values: old_contract = self.Symbol(changed_event.OldSymbol) new_contract = self.Symbol(changed_event.NewSymbol) old_month = old_contract.ID.Date.month new_month = new_contract.ID.Date.month rollover_tag = f"Rollover: {old_contract.Value} -> {new_contract.Value}, ({old_month}) -> ({new_month})" liquidate_tag = rollover_tag + \ f', [Liquidate {old_contract.Value}]' new_tag = rollover_tag + f', [Open {new_contract.Value}]' if self.Portfolio[old_contract].Invested: if self.Portfolio[old_contract].Quantity > 0: self.Liquidate(old_contract, tag=liquidate_tag) self.MarketOrder(new_contract, self.quantity, tag=new_tag) elif self.Portfolio[old_contract].Quantity < 0: self.Liquidate(old_contract, tag=liquidate_tag) self.MarketOrder(new_contract, - self.quantity, tag=new_tag) self.Log(rollover_tag) # else: # # raise error # self.Log(rollover_tag + ' --- SKIPPED: old contract not invested') # if first day of month if self.Time < (self.nextRebalance): return self.Log('---------------------') self.Log(f"Rebalancing at {self.Time}") invested = [x.Symbol.Value for x in self.Portfolio.Values if x.Invested] # get portfolio value portfolio_value = self.Portfolio.TotalPortfolioValue # get portfolio cash portfolio_cash = self.Portfolio.Cash # get portfolio pnl portfolio_pnl = self.Portfolio.TotalProfit self.Log( f"Portfolio value: {portfolio_value}, profit: {portfolio_pnl}, cash: {portfolio_cash}") self.Log(f"Current holdings: {invested}") self.Log("Liquidating all positions...") self.Liquidate(tag=f'Rebalance - Liquidate ALL - {self.test_tag}') highest_logr, lowest_logr = self.Momentum_Signal(self.logr_indicators) # highest_logr_mapped = self.continuous_contracts[highest_logr].Mapped # lowest_logr_mapped = self.continuous_contracts[lowest_logr].Mapped # highest_logr_mapped_debug = self.Symbol( # highest_logr_mapped).Value # lowest_logr_mapped_debug = self.Symbol( # lowest_logr_mapped).Value for continuous_contract_symbol, continuous_contract in self.continuous_contracts.items(): mapped_contract = continuous_contract.Mapped mapped_contract_debug = self.Symbol(mapped_contract).Value mapping_debug = f'{continuous_contract_symbol} Mapped: {mapped_contract_debug} ({mapped_contract})' if highest_logr == continuous_contract_symbol: self.Log(mapping_debug + f' --> LONG {mapped_contract_debug}') self.MarketOrder(mapped_contract, self.quantity, tag=f'LONG {mapped_contract_debug} - {self.test_tag}') elif lowest_logr == continuous_contract_symbol: self.Log(mapping_debug + f' --> SHORT {mapped_contract_debug}') self.MarketOrder(mapped_contract, -self.quantity, tag=f'SHORT {mapped_contract_debug} - {self.test_tag}') # set next rebalance time self.nextRebalance = Expiry.EndOfMonth(self.Time) + self.Rebalancetime self.Log(f"Next rebalance at {self.nextRebalance}") def Momentum_Signal(self, mom_indicator_dict): self.Log('Calculating momentum signal:') # get current value for logr indicators logr_values = {} for i in mom_indicator_dict: # check if indicator is ready if mom_indicator_dict[i].IsReady: logr_values[i] = round(mom_indicator_dict[i].Current.Value, 4) else: self.Log( f'{i} indicator not ready, {mom_indicator_dict[i].Samples} samples') # sort logr values sorted_logr_values = dict( sorted(logr_values.items(), key=lambda x: x[1])) self.Log(f"Sorted logr values: {sorted_logr_values}") # get highest and lowest logr values if len(sorted_logr_values) < 2: self.Log('Not enough data to calculate momentum signal') return None, None highest_logr = list(sorted_logr_values.keys())[-1] lowest_logr = list(sorted_logr_values.keys())[0] self.Log( f"Highest logr: {highest_logr} with value {sorted_logr_values[highest_logr]}") self.Log( f"Lowest logr: {lowest_logr} with value {sorted_logr_values[lowest_logr]}") return highest_logr, lowest_logr