Overall Statistics |
Total Trades 174 Average Win 1.90% Average Loss -1.86% Compounding Annual Return 0.783% Drawdown 15.100% Expectancy 0.056 Net Profit 6.517% Sharpe Ratio -0.319 Sortino Ratio -0.317 Probabilistic Sharpe Ratio 0.217% Loss Rate 48% Win Rate 52% Profit-Loss Ratio 1.02 Alpha -0.012 Beta 0.004 Annual Standard Deviation 0.036 Annual Variance 0.001 Information Ratio -0.552 Tracking Error 0.159 Treynor Ratio -3.075 Total Fees $726.18 Estimated Strategy Capacity $5600000.00 Lowest Capacity Asset GC YBD8IV2TUNQ5 Portfolio Turnover 2.64% |
from AlgorithmImports import * class FuturesBackwardation(QCAlgorithm): def Initialize(self): self.SetStartDate(2015, 8, 1) self.SetEndDate(2023, 9, 1) self.SetCash(1000000) # Subscribe and set our expiry filter for the futures chain self.futureGold = self.AddFuture(Futures.Metals.Gold, Resolution.Minute) # expiry between 0 and 90 days to avoid naked position stays for too long to tie up fund self.futureGold.SetFilter(0, 90) # 20-day SMA on return as the basis mean-reversion predictor self.roc = self.ROC(self.futureGold.Symbol, 1, Resolution.Daily) self.sma = IndicatorExtensions.Of(SimpleMovingAverage(20), self.roc) self.SetWarmUp(21, Resolution.Daily) def OnData(self, slice): if not self.Portfolio.Invested and not self.IsWarmingUp: # We only trade during last-day return is lower than average return if not self.roc.IsReady or not self.sma.IsReady or self.sma.Current.Value < self.roc.Current.Value: return spreads = {} for chain in slice.FutureChains: contracts = list(chain.Value) # if there is less than or equal 1 contracts, we cannot compare the spot price if len(contracts) < 2: continue # sort the contracts by expiry sorted_contracts = sorted(contracts, key=lambda x: x.Expiry) # compare the spot price for i, contract in enumerate(sorted_contracts): if i == 0: continue # compare the ask price for each contract having nearer term for j in range(i): near_contract = sorted_contracts[j] # get the spread and total cost (price of contracts and commission fee $1 x 2) horizontal_spread = contract.BidPrice - near_contract.AskPrice total_price = contract.BidPrice + near_contract.AskPrice + 2 spreads[(contract.Symbol, near_contract.Symbol)] = (horizontal_spread, total_price) # Select the pair with the lowest spread to trade for maximum potential contango if spreads: min_spread_pair = sorted(spreads.items(), key=lambda x: x[1][0])[0] far_contract, near_contract = min_spread_pair[0] # subscribe to the contracts to avoid removing from the universe self.AddFutureContract(far_contract, Resolution.Minute) self.AddFutureContract(near_contract, Resolution.Minute) num_of_contract = max((self.Portfolio.TotalPortfolioValue / min_spread_pair[1][1]) // 100, 1) self.MarketOrder(far_contract, num_of_contract) self.MarketOrder(near_contract, -num_of_contract)