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