Overall Statistics
Total Trades
5464
Average Win
0.39%
Average Loss
-0.72%
Compounding Annual Return
175.960%
Drawdown
88.200%
Expectancy
0.294
Net Profit
19615.711%
Sharpe Ratio
2.279
Probabilistic Sharpe Ratio
86.241%
Loss Rate
16%
Win Rate
84%
Profit-Loss Ratio
0.55
Alpha
1.463
Beta
0.389
Annual Standard Deviation
0.664
Annual Variance
0.441
Information Ratio
2.07
Tracking Error
0.668
Treynor Ratio
3.89
Total Fees
$16605932.31
Estimated Strategy Capacity
$830000.00
Lowest Capacity Asset
ETHUSD XJ
class StatArbAlgo(QCAlgorithm):
    
    def Initialize(self):
        
        self.SetStartDate(2016, 5, 2)  
        self.SetEndDate(2021, 7, 14)  
        self.SetCash(1000000)  
        self.AddEquity("SPY", Resolution.Minute)  
        self.SetBenchmark("SPY")
        self.SetBrokerageModel(BrokerageName.AlphaStreams)
        self.SetExecution(ImmediateExecutionModel())
        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
        
        symbols = [self.AddCrypto(symbol, Resolution.Minute).Symbol for symbol in ["BTCUSD","ETHUSD","LTCUSD"]]
        
        self.AddAlpha(StatArbAlphaModel(self.Time))
                        
class StatArbAlphaModel(AlphaModel):
                        
    def __init__(self, Time):
        self.dataBySymbol = {}
        self.rebalanceTime = Time
        
    def Update(self, algorithm, data):
        insights = []
        
        if algorithm.Time < self.rebalanceTime: return []
        
        for symbol, symbolData in self.dataBySymbol.items():
            if data.Bars.ContainsKey(symbol) and symbolData.IsReady():
                    
                if symbolData.slow.Current.Value < symbolData.fast.Current.Value:
                    insights.append(Insight(symbol, timedelta(days=30), InsightType.Price, InsightDirection.Up))
                else:
                    insights.append(Insight(symbol, timedelta(days=30), InsightType.Price, InsightDirection.Flat))
                    
        self.rebalanceTime = Expiry.EndOfDay(algorithm.Time)
                
        return insights
    
    def OnSecuritiesChanged(self, algorithm, changes):
        for change in changes.AddedSecurities:
            self.dataBySymbol[change.Symbol] = SymbolData(algorithm, change.Symbol)
            
        for change in changes.RemovedSecurities:
            if change.Symbol in self.dataBySymbol:
                del self.dataBySymbol[change.Symbol]
                
        
class SymbolData:
    def __init__(self, algorithm, symbol):
        
        algorithm.Consolidate(symbol, Resolution.Daily, self.DailyBarHandler)
        
        self.fast = algorithm.SMA(symbol, 5, Resolution.Daily, Field.Low)
        self.slow = algorithm.SMA(symbol, 60, Resolution.Daily, Field.Low)
        
        history = algorithm.History(symbol, 60, Resolution.Daily)
        
        if not history.empty:
            for index, tradebar in history.loc[symbol].iterrows():
                self.fast.Update(index, tradebar.low)
                self.slow.Update(index, tradebar.low)
            
            last_row = history.loc[symbol].iloc[-1]
            self.open = last_row.open
            self.close = last_row.close
            self.high = last_row.high
            self.low = last_row.low
        
    def DailyBarHandler(self, consolidated):
        self.open = consolidated.Open
        self.close = consolidated.Close
        self.high = consolidated.High
        self.low = consolidated.Low
            
    def IsReady(self):
        return self.fast.IsReady and self.slow.IsReady