Overall Statistics
Total Trades
449
Average Win
1.28%
Average Loss
-0.89%
Compounding Annual Return
9.227%
Drawdown
22.700%
Expectancy
0.067
Net Profit
14.172%
Sharpe Ratio
0.409
Probabilistic Sharpe Ratio
18.559%
Loss Rate
56%
Win Rate
44%
Profit-Loss Ratio
1.44
Alpha
0.096
Beta
-0.107
Annual Standard Deviation
0.214
Annual Variance
0.046
Information Ratio
0.016
Tracking Error
0.263
Treynor Ratio
-0.817
Total Fees
$3727.75
Estimated Strategy Capacity
$130000000.00
Lowest Capacity Asset
LBS 1S1
#region imports
from AlgorithmImports import *
#endregion

# futs_list = [Futures.Indices.SP500EMini, Futures.Grains.Corn, Futures.Currencies.JPY]

futs_list = [
    Futures.Grains.BlackSeaCornFinanciallySettledPlatts,
    Futures.Grains.BlackSeaWheatFinanciallySettledPlatts,
    Futures.Grains.SRWWheat,
    Futures.Grains.HRWWheat,
    Futures.Grains.Corn,
    Futures.Grains.Soybeans,
    Futures.Grains.SoybeanMeal,
    Futures.Grains.SoybeanOil,
    Futures.Grains.Oats,
    Futures.Currencies.GBP,
    Futures.Currencies.CAD,
    Futures.Currencies.JPY,
    Futures.Currencies.CHF,
    Futures.Currencies.EUR,
    Futures.Currencies.AUD,
    Futures.Currencies.NZD,
    Futures.Currencies.RUB,
    Futures.Currencies.BRL,
    Futures.Currencies.MXN,
    Futures.Currencies.ZAR,
    # Futures.Currencies.AUDCAD,
    # Futures.Currencies.AUDJPY,
    # Futures.Currencies.AUDNZD,
    # Futures.Currencies.BTC,
    # Futures.Currencies.CADJPY,
    # Futures.Currencies.StandardSizeUSDOffshoreRMBCNH,
    # Futures.Currencies.EuroFXEmini,
    # Futures.Currencies.EURAUD,
    # Futures.Currencies.EURCAD,
    # Futures.Currencies.EURSEK,
    # Futures.Currencies.JapaneseYenEmini,
    # Futures.Energies.PropaneNonLDHMontBelvieu,
    # Futures.Energies.ArgusPropaneFarEastIndexBALMO,
    # Futures.Energies.MiniEuropeanThreePointPercentFiveFuelOilBargesPlatts,
    # Futures.Energies.MiniSingaporeFuelOil180CstPlatts,
    # Futures.Energies.GulfCoastULSDPlattsUpDownBALMO,
    # Futures.Energies.GulfCoastJetPlattsUpDownBALMO,
    # Futures.Energies.PropaneNonLDHMontBelvieuOPIS,
    # Futures.Energies.EuropeanPropaneCIFARAArgusBALMO,
    # Futures.Energies.PremiumUnleadedGasoline10ppmFOBMEDPlatts,
    # Futures.Energies.ArgusPropaneFarEastIndex,
    # Futures.Energies.GasolineEurobobOxyNWEBargesArgusCrackSpreadBALMO,
    # Futures.Energies.MontBelvieuNaturalGasolineOPIS,
    # Futures.Energies.MontBelvieuNormalButaneOPISBALMO,
    # Futures.Energies.ConwayPropaneOPIS,
    # Futures.Energies.MontBelvieuLDHPropaneOPISBALMO,
    # Futures.Energies.ArgusPropaneFarEastIndexVsEuropeanPropaneCIFARAArgus,
    # Futures.Energies.ArgusPropaneSaudiAramco,
    # Futures.Energies.GroupThreeULSDPlattsVsNYHarborULSD,
    # Futures.Energies.GroupThreeSuboctaneGasolinePlattsVsRBOB,
    # Futures.Energies.SingaporeFuelOil180cstPlattsBALMO,
    # Futures.Energies.SingaporeFuelOil380cstPlattsBALMO,
    # Futures.Energies.MontBelvieuEthaneOPIS,
    # Futures.Energies.MontBelvieuNormalButaneOPIS,
    # Futures.Energies.BrentCrudeOilVsDubaiCrudeOilPlatts,
    # Futures.Energies.ArgusLLSvsWTIArgusTradeMonth,
    # Futures.Energies.SingaporeGasoilPlattsVsLowSulphurGasoilFutures,
    # Futures.Energies.LosAngelesCARBOBGasolineOPISvsRBOBGasoline,
    # Futures.Energies.LosAngelesJetOPISvsNYHarborULSD,
    # Futures.Energies.LosAngelesCARBDieselOPISvsNYHarborULSD,
    # Futures.Energies.EuropeanNaphthaPlattsBALMO,
    # Futures.Energies.EuropeanPropaneCIFARAArgus,
    # Futures.Energies.MontBelvieuNaturalGasolineOPISBALMO,
    # Futures.Energies.RBOBGasolineCrackSpread,
    # Futures.Energies.GulfCoastHSFOPlattsBALMO,
    # Futures.Energies.MarsArgusVsWTITradeMonth,
    # Futures.Energies.MarsArgusVsWTIFinancial,
    # Futures.Energies.EthanolT2FOBRdamIncludingDutyPlatts,
    # Futures.Energies.MontBelvieuLDHPropaneOPIS,
    # Futures.Energies.GasolineEurobobOxyNWEBargesArgus,
    # Futures.Energies.WTIBrentFinancial,
    # Futures.Energies.ThreePointFivePercentFuelOilBargesFOBRdamPlattsCrackSpread1000mt,
    # Futures.Energies.GasolineEurobobOxyNWEBargesArgusBALMO,
    # Futures.Energies.BrentLastDayFinancial,
    Futures.Energies.CrudeOilWTI,
    # Futures.Energies.GulfCoastCBOBGasolineA2PlattsVsRBOBGasoline,
    # Futures.Energies.ClearbrookBakkenSweetCrudeOilMonthlyIndexNetEnergy,
    # Futures.Energies.WTIFinancial,
    # Futures.Energies.ChicagoEthanolPlatts,
    # Futures.Energies.SingaporeMogas92UnleadedPlattsBrentCrackSpread,
    # Futures.Energies.DubaiCrudeOilPlattsFinancial,
    # Futures.Energies.JapanCnFNaphthaPlattsBALMO,
    Futures.Energies.Ethanol,
    # Futures.Energies.EuropeanNaphthaPlattsCrackSpread,
    # Futures.Energies.EuropeanPropaneCIFARAArgusVsNaphthaCargoesCIFNWEPlatts,
    # Futures.Energies.SingaporeFuelOil380cstPlattsVsEuropeanThreePointFivePercentFuelOilBargesFOBRdamPlatts,
    # Futures.Energies.EastWestGasolineSpreadPlattsArgus,
    # Futures.Energies.EastWestNaphthaJapanCFvsCargoesCIFNWESpreadPlatts,
    # Futures.Energies.RBOBGasolineVsEurobobOxyNWEBargesArgusThreeHundredFiftyThousandGallons,
    # Futures.Energies.ThreePointFivePercentFuelOilBargesFOBRdamPlattsCrackSpread,
    # Futures.Energies.FreightRouteTC14Baltic,
    # Futures.Energies.OnePercentFuelOilCargoesFOBNWEPlattsVsThreePointFivePercentFuelOilBargesFOBRdamPlatts,
    # Futures.Energies.GulfCoastHSFOPlattsVsEuropeanThreePointFivePercentFuelOilBargesFOBRdamPlatts,
    Futures.Energies.WTIHoustonCrudeOil,
    # Futures.Energies.NaturalGasHenryHubLastDayFinancial,
    Futures.Energies.HeatingOil,
    # Futures.Energies.NaturalGasHenryHubPenultimateFinancial,
    # Futures.Energies.WTIHoustonArgusVsWTITradeMonth,
    Futures.Energies.Gasoline,
    Futures.Energies.NaturalGas,
    Futures.Financials.Y30TreasuryBond,
    Futures.Financials.Y10TreasuryNote,
    Futures.Financials.Y5TreasuryNote,
    Futures.Financials.Y2TreasuryNote,
    Futures.Financials.EuroDollar,
    # Futures.Financials.FiveYearUSDMACSwap,
    # Futures.Financials.UltraUSTreasuryBond,
    # Futures.Financials.UltraTenYearUSTreasuryNote,
    Futures.Indices.SP500EMini,
    Futures.Indices.NASDAQ100EMini,
    # Futures.Indices.Dow30EMini,
    # Futures.Indices.VIX,
    # Futures.Indices.Russell2000EMini,
    Futures.Indices.Nikkei225Dollar,
    Futures.Indices.BloombergCommodityIndex,
    Futures.Indices.NASDAQ100BiotechnologyEMini,
    Futures.Indices.FTSEEmergingEmini,
    Futures.Indices.SP400MidCapEmini,
    Futures.Indices.SPGSCICommodity,
    Futures.Indices.USDDenominatedIbovespa,
    Futures.Indices.MSCITaiwanIndex,
    Futures.Indices.Nikkei225Yen,
    Futures.Indices.Nifty50,
    Futures.Indices.HangSeng,
    Futures.Forestry.RandomLengthLumber,
    Futures.Meats.LiveCattle,
    Futures.Meats.FeederCattle,
    Futures.Meats.LeanHogs,
    Futures.Metals.Gold,
    Futures.Metals.Silver,
    Futures.Metals.Platinum,
    Futures.Metals.Palladium,
    Futures.Metals.AluminumMWUSTransactionPremiumPlatts25MT,
    Futures.Metals.AluminiumEuropeanPremiumDutyPaidMetalBulletin,
    Futures.Metals.Copper,
    Futures.Metals.USMidwestDomesticHotRolledCoilSteelCRUIndex,
    Futures.Softs.Sugar11CME,
    Futures.Softs.Cocoa,
    Futures.Dairy.CashSettledButter,
    Futures.Dairy.CashSettledCheese,
    Futures.Dairy.ClassIIIMilk,
    Futures.Dairy.DryWhey,
    Futures.Dairy.ClassIVMilk,
    Futures.Dairy.NonfatDryMilk,
]
# region imports
from AlgorithmImports import *
# endregion
from Futs import futs_list

class FutureObject(object):
    
    def __init__(self, algo, symbol):
        
        self.algo = algo
        self.future = self.algo.AddFuture(symbol, self.algo.Resolution, dataNormalizationMode = DataNormalizationMode.BackwardsRatio, dataMappingMode = DataMappingMode.OpenInterest, contractDepthOffset = 0)
        self.consolidator = TradeBarConsolidator(timedelta(days=1)) # before continuous futures they recommended QuoteBarConsolidator.
        self.consolidator.DataConsolidated += self.algo.OnDataConsolidated
        self.algo.SubscriptionManager.AddConsolidator(self.future.Symbol, self.consolidator)
        
        self.multiplier = self.future.SymbolProperties.ContractMultiplier
        
        self.fast_ma_bars = 50
        self.slow_ma_bars = 100
        self.atr_bars = 100
        self.lookback_bars = 252
        self.fast_highlow_bars = 25
        self.slow_highlow_bars = 50
        
        self.fast_ma = SimpleMovingAverage(f"FAST_MA", self.fast_ma_bars)
        self.slow_ma = SimpleMovingAverage(f"SLOW_MA", self.slow_ma_bars)
        self.atr = AverageTrueRange(f"ATR", self.atr_bars)
        
        self.fast_high = Maximum(f"FAST_HIGH", self.fast_highlow_bars)
        self.slow_high = Maximum(f"SLOW_HIGH", self.slow_highlow_bars)
        
        self.fast_low = Minimum(f"FAST_LOW", self.fast_highlow_bars)
        self.slow_low = Minimum(f"SLOW_LOW", self.slow_highlow_bars)
        
        self.logr = LogReturn(f"LOGR", 1)
        self.logr.Updated += self.logr_updated
        
        self.closes = RollingWindow[float](self.lookback_bars)
        self.volumes = RollingWindow[float](3)
        self.logrs = RollingWindow[float](self.lookback_bars)
        self.volume = 0
        
        self.invested = 0
        self.size = 0
        self.high_since_buy = 0
        self.low_since_buy = 9e99
        
        self.indicators = [
            self.fast_ma,
            self.slow_ma,
            self.atr,
            self.fast_high,
            self.slow_high,
            self.fast_low,
            self.slow_low,
            self.logr,
            ]
            
    def reset(self):
        self.invested = 0
        self.size = 0
        self.high_since_buy = 0
        self.low_since_buy = 9e99
    
    def logr_updated(self, sender, updated):
        self.logrs.Add(updated.Value)
    
    def is_ready(self):
        for indicator in self.indicators:
            if not indicator.IsReady:
                return False
        return True
    
    def trade(self, close):
        signal = self.signal(close)
        if self.invested == 0:
            if signal == 1: self.open_position(1, close)
            elif signal == -1: self.open_position(-1, close)
        else:
            if signal == -1: self.close_position() 

    def close_position(self):
        self.algo.Liquidate(self.future.Symbol)
        self.reset()
    
    def open_position(self, signal, price):
        contracts = self.get_size(price)
        if (signal == 1) and (contracts):
            self.algo.Debug(f"Buying {self.future.Symbol}")
            self.algo.Buy(self.future.Symbol, contracts)
            self.size = contracts
            self.invested = 1
        elif (signal == -1) and (contracts):
            self.algo.Debug(f"Shorting {self.future.Symbol}")
            self.algo.Sell(self.future.Symbol, contracts)
            self.size = contracts
            self.invested = -1
    
    def signal(self, close):
        
        if self.invested == 0:
            if (self.fast_ma.Current.Value > self.slow_ma.Current.Value) and (close > self.slow_high.Current.Value): return 1
            if (self.fast_ma.Current.Value < self.slow_ma.Current.Value) and (close < self.slow_low.Current.Value): return -1
            return 0
        
        self.high_since_buy = np.max([self.high_since_buy, close])
        self.low_since_buy = np.min([self.low_since_buy, close])
        
        if self.invested == 1:
            if (close < self.fast_low.Current.Value): return -1
            if (close < self.high_since_buy - self.atr.Current.Value * 3): return -1
            return 0
        elif self.invested == -1:
            if (close > self.fast_high.Current.Value): return -1
            if (close > self.low_since_buy + self.atr.Current.Value * 3): return -1
            return 0        
    
    def get_volume(self, close):
        self.volume = np.mean([x for x in list(self.volumes)])
    
    def get_size(self, price):
        atr = self.atr.Current.Value
        if atr == 0: return 0
        num_contracts = (self.algo.risk_factor * self.algo.Portfolio.TotalPortfolioValue) / \
             (atr * self.multiplier)
        notional = num_contracts * self.multiplier * price
        if (notional > self.algo.Portfolio.MarginRemaining):
            self.algo.Debug(f"Not enough margin remaining to buy {num_contracts} of {self.future.Symbol} ({notional})")
            return None
        if (num_contracts < 1):
            self.algo.Debug(f"Contracts too expensive (can't buy {num_contracts} of {self.future.Symbol})")
            return 0        
        return num_contracts        
    
    def validate(self, data):
        if (not self.future.Symbol in data.Bars) | (not self.algo.Securities.ContainsKey(self.future.Symbol)):
            # if self.invested != 0: self.close_position()
            return False
        return True
        

class FatOrangePelican(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 11, 28)  # Set Start Date
        self.SetCash(1_000_000)  # Set Strategy Cash
        self.risk_factor = 0.005
        
        self.Resolution = Resolution.Minute
        self.n_contracts = 40
        
        self.tickers = [    Futures.Indices.SP500EMini, Futures.Indices.NASDAQ100EMini, Futures.Indices.Russell2000EMini, Futures.Indices.Nikkei225Dollar, Futures.Indices.HangSeng, Futures.Indices.FTSEEmergingEmini, \
                            Futures.Currencies.AUD, Futures.Currencies.GBP, Futures.Currencies.EUR, Futures.Currencies.JPY, Futures.Currencies.NZD, Futures.Currencies.CAD, Futures.Currencies.CHF, \
                            Futures.Grains.Corn, Futures.Forestry.RandomLengthLumber, Futures.Meats.LiveCattle, Futures.Meats.LeanHogs, Futures.Grains.Oats, Futures.Grains.Soybeans, Futures.Softs.Sugar11CME, Futures.Grains.SRWWheat, \
                            Futures.Energies.CrudeOilWTI, Futures.Energies.HeatingOil, Futures.Energies.NaturalGas, Futures.Energies.Gasoline, \
                            Futures.Metals.Gold, Futures.Metals.Palladium, Futures.Metals.Platinum, Futures.Metals.Silver, \
                            Futures.Financials.Y30TreasuryBond, Futures.Financials.Y10TreasuryNote, Futures.Financials.Y5TreasuryNote, Futures.Financials.Y2TreasuryNote, Futures.Financials.EuroDollar]
                            
        
        self.futures = { x.future.Symbol:x for x in [FutureObject(self, y) for y in self.tickers] }
            
    def OnDataConsolidated(self, sender, bar: TradeBar):
        symbol = bar.Symbol
        future = self.futures[symbol]
        for indicator in future.indicators:
            if indicator.Name == 'ATR': indicator.Update(bar)
            else: indicator.Update(bar.Time, bar.Close)
        future.closes.Add(bar.Close)
        future.volumes.Add(bar.Volume * future.multiplier * bar.Close)
        
    def OnData(self, data: Slice):
        if len([x.is_ready() for x in self.futures.values()]) == 0:
            return
    
        # universe = self.get_universe(data)
        
        # self.Debug(f"Validating...")
        for f in self.futures.values():
            if not f.validate(data):
                continue
            # self.Debug(f"Successfully validated {f.future.Symbol}")
            close = data.Bars[f.future.Symbol].Close
            f.trade(close)
        
        # self.Debug(str([x.Value for x in self.Portfolio.Keys]))
        
    def get_universe(self, data):
        min_volume = sorted([future.volume for future in self.futures.values() if future.validate(data)], reverse=True)
        if len(min_volume) == 0: min_volume = 0
        else: min_volume = min_volume[min(len(min_volume)-1, self.n_contracts)]
        universe = [future for future in self.futures.values() if (future.volume >= min_volume) and (future.validate(data))]
        return universe