Overall Statistics
Total Trades
107
Average Win
1.89%
Average Loss
-1.47%
Compounding Annual Return
6.386%
Drawdown
4.100%
Expectancy
0.426
Net Profit
37.060%
Sharpe Ratio
1.28
Probabilistic Sharpe Ratio
76.460%
Loss Rate
38%
Win Rate
62%
Profit-Loss Ratio
1.29
Alpha
0.046
Beta
-0.018
Annual Standard Deviation
0.035
Annual Variance
0.001
Information Ratio
-0.344
Tracking Error
0.164
Treynor Ratio
-2.501
Total Fees
$513.76
Estimated Strategy Capacity
$63000.00
Lowest Capacity Asset
GC XHD34ASNX0NX
from AlgorithmImports import *

class FuturesBackwardation(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2015, 8, 1)
        self.SetEndDate(2020, 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)