Overall Statistics
Total Trades
844
Average Win
0.98%
Average Loss
-0.96%
Compounding Annual Return
11.071%
Drawdown
33.800%
Expectancy
0.188
Net Profit
106.165%
Sharpe Ratio
0.49
Probabilistic Sharpe Ratio
4.543%
Loss Rate
41%
Win Rate
59%
Profit-Loss Ratio
1.03
Alpha
0.106
Beta
-0.086
Annual Standard Deviation
0.196
Annual Variance
0.038
Information Ratio
-0.064
Tracking Error
0.252
Treynor Ratio
-1.111
Total Fees
$3729.60
Estimated Strategy Capacity
$110000000.00
Lowest Capacity Asset
BTC 1S1
from alpha import EmaCrossoverAlphaModel
from portfolio import NaiveFuturesPortfolioConstructionModel


class EmaCrossoverFutures(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2015, 1, 1) 
        self.SetCash(1_000_000)
        self.Settings.EnableAutomaticIndicatorWarmUp = True
        futures = [Futures.Metals.Gold, Futures.Energies.CrudeOilWTI, Futures.Indices.SP500EMini, Futures.Currencies.BTC]
        for future in futures:
            self.AddFuture(future, 
                            dataNormalizationMode = DataNormalizationMode.BackwardsRatio,
                            dataMappingMode = DataMappingMode.OpenInterest,
                            contractDepthOffset = 0)

        self.AddAlpha(EmaCrossoverAlphaModel())
        self.Settings.FreePortfolioValuePercentage = .1
        self.SetPortfolioConstruction(NaiveFuturesPortfolioConstructionModel())
        
        
                                            
    


 


from collections import defaultdict
from datetime import datetime, timedelta
from itertools import groupby
import pytz



class NaiveFuturesPortfolioConstructionModel(PortfolioConstructionModel):
    
    def __init__(self):
        self.insightCollection = InsightCollection()
        self.contracts = {}
        self.securities = {}
        self.removedSymbols = []
        self.nextExpiryTime = datetime.min.replace(tzinfo=pytz.utc)
        self.rebalanceFreq = timedelta(1)
        self.maxGrossExposurePerSecurity = 10
        
    
    def CreateTargets(self, algorithm, insights):
        targets = []
        if not self.ShouldCreateTargets(algorithm.UtcTime, insights):
            return targets
        self.insightCollection.AddRange(insights)
        targets.extend(self.CreateZeroQuantityTargetsForRemovedSecurities())
        targets.extend(self.CreateZeroQuantityTargetsForExpiredInsights(algorithm))
        lastActiveInsights = self.GetLastActiveInsights(algorithm)
        if self.ShouldUpdateTargetPercent(algorithm, lastActiveInsights):
            targets.extend(self.DeterminePortfolioTargets(algorithm, lastActiveInsights))
        self.UpdateNextExpiryTime(algorithm)
        return targets
        
    def DeterminePortfolioTargets(self, algorithm, lastActiveInsights):
        targets = []
        if not lastActiveInsights:
            return targets
        TPV = algorithm.Portfolio.TotalPortfolioValue
        n = max(1, len({insight.Symbol for insight in lastActiveInsights if insight.Direction != InsightDirection.Flat}))
        for insight in lastActiveInsights:
            symbol = insight.Symbol
            if symbol not in self.securities:
                targets.append(PortfolioTarget(symbol, 0))
                continue
            security = self.securities[symbol]
            notionalValue = security.Close*security.SymbolProperties.ContractMultiplier
            weight = min(insight.Direction*notionalValue*self.maxGrossExposurePerSecurity/TPV/n, self.maxGrossExposurePerSecurity)
            quantity = math.floor(weight*TPV/notionalValue)
            targets.append(PortfolioTarget(symbol, quantity))
        return targets
    
    
    def ShouldCreateTargets(self, time, insights):
        return len(insights) > 0 or (time > self.nextExpiryTime)
    
    def CreateZeroQuantityTargetsForRemovedSecurities(self):
        if len(self.removedSymbols) == 0:
            return []
        zeroTargets = [PortfolioTarget(symbol, 0) for symbol in self.removedSymbols]
        self.insightCollection.Clear(self.removedSymbols)
        self.removedSymbols = []
        return zeroTargets
        
    
    def CreateZeroQuantityTargetsForExpiredInsights(self, algorithm):
        zeroTargets = []
        expiredInsights = self.insightCollection.RemoveExpiredInsights(algorithm.UtcTime)
        if len(expiredInsights) == 0:
            return zeroTargets
        key = lambda insight: insight.Symbol
        for symbol, _ in groupby(sorted(expiredInsights, key=key), key):
            if not self.insightCollection.HasActiveInsights(symbol, algorithm.UtcTime): 
                zeroTargets.append(PortfolioTarget(symbol, 0))
                continue
        return zeroTargets
        
    
    def GetLastActiveInsights(self, algorithm):
        activeInsights = self.insightCollection.GetActiveInsights(algorithm.UtcTime)
        lastActiveInsights = []
        groupedInsights = GroupBy(activeInsights, key = lambda insight: (insight.Symbol, insight.SourceModel))
        for kvp in groupedInsights:
            lastActiveInsights.append(sorted(kvp[1], key=lambda insight: insight.GeneratedTimeUtc)[-1])
        return lastActiveInsights    

    
    def ShouldUpdateTargetPercent(self, algorithm, lastActiveInsights):
        if algorithm.UtcTime > self.nextExpiryTime:
            return True
        for insight in lastActiveInsights:
            if insight.Direction != InsightDirection.Flat and not algorithm.Portfolio[insight.Symbol].Invested:
                return True
            elif insight.Direction != InsightDirection.Up and algorithm.Portfolio[insight.Symbol].IsLong:
                return True
            elif insight.Direction != InsightDirection.Down and algorithm.Portfolio[insight.Symbol].IsShort:
                return True
            else:
                continue
        return False
  
    
    def UpdateNextExpiryTime(self, algorithm):
        self.nextExpiryTime = self.insightCollection.GetNextExpiryTime()
        if self.nextExpiryTime is None:
            self.nextExpiryTime = algorithm.UtcTime + self.rebalanceFreq
        
    
    def OnSecuritiesChanged(self, algorithm, changes):
        for security in changes.RemovedSecurities:
            symbol = security.Symbol
            self.removedSymbols.append(symbol)
            self.securities.pop(symbol, None)
        
        for security in changes.AddedSecurities:
            self.securities[security.Symbol] = security


def GroupBy(iterable, key=lambda x: x):
    d = defaultdict(list)
    for item in iterable:
        d[key(item)].append(item)
    return d.items()
class EmaCrossoverAlphaModel(AlphaModel):
    
    def __init__(self):
        self.symbolDataDict = {}
        self.date = None
    
    def Update(self, algorithm, data):
        insights = []
        if self.date == algorithm.Time.date():
            return insights
        self.date = algorithm.Time.date()
        
        for symbol, symbolData in self.symbolDataDict.items():
            if symbolData.Security.HasData and symbol in data.QuoteBars:
                insight = symbolData.CreateInsight()
                insights.append(insight)
        return insights
        
        
    
    def OnSecuritiesChanged(self, algorithm, changes):
        for security in changes.RemovedSecurities:
            self.symbolDataDict.pop(security.Symbol, None)
            
        for security in changes.AddedSecurities:
            if security.Symbol not in self.symbolDataDict:
                self.symbolDataDict[security.Symbol] = SymbolData(algorithm, security.Symbol)
        
      

class SymbolData:
    
    def __init__(self, algorithm, symbol):
        self.algorithm = algorithm
        self.Symbol = symbol
        self.Security = algorithm.Securities[symbol]
        barsPerDay = self.Security.Exchange.Hours.RegularMarketDuration.seconds/Extensions.ToTimeSpan(Resolution.Minute).seconds
        periodFast, periodSlow = [int(barsPerDay*10), int(barsPerDay*50)]
        self.fast = algorithm.EMA(symbol, periodFast, Resolution.Minute)
        self.slow = algorithm.EMA(symbol, periodSlow, Resolution.Minute)
        self.tol = .01
        self.futureName = symbol.ID.ToString().split()[0]
        self.scheduledEvent = algorithm.Schedule.On(algorithm.DateRules.EveryDay(symbol), algorithm.TimeRules.At(12,0), self.UpdateCharts )
    
    def CreateInsight(self):
        return Insight.Price(self.Symbol, timedelta(2), self.InsightDirection)
    
    
    @property 
    def InsightDirection(self):
        if not self.slow.IsReady:
            return InsightDirection.Flat
        if self.fast.Current.Value > self.slow.Current.Value*(1 + self.tol):
            return InsightDirection.Up
        elif self.fast.Current.Value < self.slow.Current.Value*(1 - self.tol):
            return InsightDirection.Down
        else:
            return InsightDirection.Flat

    
    def UpdateCharts(self):
        if (self.Security.HasData and self.slow.IsReady):
            self.algorithm.Plot('Signals ' + self.futureName, 'Price', self.Security.Close)
            self.algorithm.Plot('Signals ' + self.futureName, 'FastEMA', self.fast.Current.Value)
            self.algorithm.Plot('Signals ' + self.futureName, 'SlowEMA', self.slow.Current.Value)