| Overall Statistics |
|
Total Orders 170 Average Win 1.92% Average Loss -1.88% Compounding Annual Return 0.843% Drawdown 14.200% Expectancy 0.059 Start Equity 1000000 End Equity 1068748.82 Net Profit 6.875% Sharpe Ratio -0.282 Sortino Ratio -0.281 Probabilistic Sharpe Ratio 0.266% Loss Rate 48% Win Rate 52% Profit-Loss Ratio 1.02 Alpha -0.008 Beta 0.172 Annual Standard Deviation 0.036 Annual Variance 0.001 Information Ratio 0.028 Tracking Error 0.057 Treynor Ratio -0.06 Total Fees $716.30 Estimated Strategy Capacity $4000000.00 Lowest Capacity Asset GC Y9O6T2ED3VRX Portfolio Turnover 2.65% |
from AlgorithmImports import *
class PredictionOnFuturesContango(QCAlgorithm):
def initialize(self):
self.set_start_date(2015, 8, 1)
self.set_end_date(2023, 7, 1)
self.set_cash(1000000)
# Subscribe and set our expiry filter for the futures chain
self.future_g_o_l_d = self.add_future(
Futures.Metals.GOLD,
resolution = Resolution.MINUTE,
data_normalization_mode = DataNormalizationMode.BACKWARDS_RATIO,
data_mapping_mode = DataMappingMode.OPEN_INTEREST,
contract_depth_offset = 0
)
# expiry between 0 and 90 days to avoid naked position stays for too long to tie up fund
self.future_g_o_l_d.set_filter(0, 90)
# 20-day SMA on return as the basis mean-reversion predictor
self.roc = self.ROC(self.future_g_o_l_d.symbol, 1, Resolution.DAILY)
self.sma = IndicatorExtensions.of(SimpleMovingAverage(20), self.roc)
self.set_warm_up(21, Resolution.DAILY)
ief = self.add_equity("IEF").symbol
self.set_benchmark(ief)
def on_data(self, slice):
if not self.portfolio.invested and not self.is_warming_up:
# We only trade during last-day return is lower than average return
if not self.roc.is_ready or not self.sma.is_ready or self.sma.current.value < self.roc.current.value:
return
spreads = {}
for chain in slice.future_chains:
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.bid_price - near_contract.ask_price
total_price = contract.bid_price + near_contract.ask_price + 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.add_future_contract(far_contract, Resolution.MINUTE)
self.add_future_contract(near_contract, Resolution.MINUTE)
num_of_contract = max((self.portfolio.total_portfolio_value / min_spread_pair[1][1]) // self.future_g_o_l_d.symbol_properties.contract_multiplier, 1)
self.market_order(far_contract, num_of_contract)
self.market_order(near_contract, -num_of_contract)