Overall Statistics
Total Trades
12
Average Win
0.30%
Average Loss
-0.35%
Compounding Annual Return
-2.579%
Drawdown
1.500%
Expectancy
-0.070
Net Profit
-0.155%
Sharpe Ratio
-0.563
Probabilistic Sharpe Ratio
36.137%
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
0.86
Alpha
-0.025
Beta
-0.004
Annual Standard Deviation
0.047
Annual Variance
0.002
Information Ratio
-4.401
Tracking Error
0.102
Treynor Ratio
6.306
Total Fees
$12.00
from datetime import timedelta
import numpy as np
import pandas as pd
from System.Drawing import Color

class ModelA(AlphaModel): 
    
    def __init__(self, resolution, insightsTimeDelta ):

        self.symbolDataBySymbol =   {}
        self.modelResolution    =   resolution
        self.insightsTimeDelta  =   insightsTimeDelta

    def OnSecuritiesChanged(self, algorithm, changes):
            for added in changes.AddedSecurities:
                symbolData = self.symbolDataBySymbol.get(added.Symbol)
                if symbolData is None:
                    symbolData = SymbolData(added.Symbol, algorithm, self.modelResolution)
                    self.symbolDataBySymbol[added.Symbol] = symbolData
 
    def Update(self, algorithm, data):
        
        insights=[]

        invested = [ x.Symbol.Value for x in algorithm.Portfolio.Values if x.Invested ]
       
        for symbol, symbolData in self.symbolDataBySymbol.items():
 
            isInvested= str(symbol) in invested  
            algorithm.Log(f"{symbol} {isInvested} {invested} {symbolData.InsightDirection}")                          
            symbolData.getInsight(algorithm.Securities[symbol].Price, isInvested) # Latest known price; we are at 12:00 and the last trade at 10.57 
            
            if symbolData.trade:
                insights.append(Insight(symbol, self.insightsTimeDelta, InsightType.Price, symbolData.InsightDirection, 0.0025,None, "ModelA",None))
                algorithm.Log(f"{symbol}\tMOM\t[{symbolData.fmom}]\t{round(symbolData.mom.Current.Value,2)}\tKAMA\t[{symbolData.fkama}]\t{round(symbolData.kama.Current.Value,2)}\
                                \tPrice\t{symbolData.price}\tROC\t[{symbolData.froc}]\t{round(symbolData.roc.Current.Value,4)}\tEMA\t[{symbolData.fema}]\tEMA-13\t{round(symbolData.ema13.Current.Value,2)}\
                                \tEMA-63\t{round(symbolData.ema63.Current.Value,2)}\tEMA-150\t{round(symbolData.ema150.Current.Value,2)}\taction\t{symbolData.InsightDirection}")

        return insights

class FrameworkAlgorithm(QCAlgorithm):
    
    def Initialize(self):

        algo=algoData()
        symbols             =   [Symbol.Create(x, SecurityType.Equity, Market.USA) for x in algo.tickers]
        insightsTimeDelta   = algo.timedelta  

        self.SetStartDate(algo.startYYYY,algo.startMM,algo.startDD)   
        self.SetCash(algo.cash)           
        self.SetBenchmark("SPY")
        self.UniverseSettings.Resolution = algo.resolution
        self.SetWarmUp(timedelta(algo.warmup)) 
        self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))
        self.SetAlpha(ModelA(algo.resolution,insightsTimeDelta))
        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
        #self.SetPortfolioConstruction(MeanVarianceOptimizationPortfolioConstructionModel(resolution,PortfolioBias.LongShort,1,63,resolution,0.02,MaximumSharpeRatioPortfolioOptimizer(0,1,0)))
        self.SetRiskManagement(MaximumDrawdownPercentPerSecurity(algo.maxDrawDown)) # drop in profit from the max / done daily > redo hourly?
        self.SetExecution(ImmediateExecutionModel())
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(TimeSpan.FromMinutes(algo.runEveryXminutes)), self.hourlyHouseKeeping)


    def hourlyHouseKeeping(self):
        
            # Fail Safe - If our strategy is losing than acceptable (something is wrong)
            # Strategy suddenly losing moiney or logic problem/bug we did't carch i testing
            pnl= sum([self.Portfolio[symbol].NetProfit for symbol in self.Portfolio.Keys])
        
            #if self.LiveMode:
            if pnl < -1000: # can't pass value via parametr? 
                self.Log(f"Fallback event triggered, liquidating with total portfolio loss of {pnl}")
                self.Liquidate()
                self.Quit()
            
            dt=int(self.Time.hour)
            if dt >9 and dt<18:
                if (self.IsMarketOpen("SPY") and self.Portfolio.Invested):
                    self.Log("\n\nPortfolio")
                    summary = {}
    
                    invested = [ x.Symbol.Value for x in self.Portfolio.Values if x.Invested ]
                    for symbol in invested:
                    
                        hold_val    = round(self.Portfolio[symbol].HoldingsValue, 2)
                        abs_val     = round(self.Portfolio[symbol].AbsoluteHoldingsValue, 2)
                        pnl         = round(self.Portfolio[symbol].UnrealizedProfit, 2)
                        qty         = self.Portfolio[symbol].Quantity
                        price       = self.Portfolio[symbol].Price
                        
                        summary[symbol]=[hold_val,abs_val,pnl,qty,price]
                        
                    df=pd.DataFrame(summary)
                    df.index = ['hold_val', 'abs_val', 'pnl', 'qty','price']
                    df=df.T
                    hold_val_total= abs(df['hold_val']).sum()
                    df = df.assign(weight=abs(df['hold_val'])/hold_val_total)
                    self.Log(df)
                    self.Log("\n\n")

class algoData:
    def __init__(self):
        
        self.startYYYY          = 2020
        self.startMM            = 12
        self.startDD            = 10
        self.cash               = 10000
        self.warmup             = 28
        self.resolution         = Resolution.Hour  #10-11, etc Daily data is midnight to mifnight, 12AM EST 
        self.tickers            = ["MSFT"]
        self.fallback_barrier   = -1000
        self.timedelta          = timedelta(hours=1)
        self.maxDrawDown        = 0.5
        self.runEveryXminutes   = 60 # Schedule frequency
        
class SymbolData:
    
    def __init__(self, symbol, algorithm, resolution):
        
        self.symbol             = symbol
        self.price              = 0.00
        self.kama               = algorithm.KAMA(symbol, 10,2,30, resolution)
        self.kama_factor        = 1.01 # tolerance level to avoid buy and immediate sell scenario
        self.mom                = algorithm.MOM(symbol, 14, resolution)
        self.roc                = algorithm.ROC(symbol, 9, resolution) 
        self.ema13              = algorithm.EMA(symbol, 13, resolution)
        self.ema63              = algorithm.EMA(symbol, 63, resolution)
        self.ema150             = algorithm.EMA(symbol, 150, resolution)
        self.fkama              = False
        self.fmom               = False
        self.froc               = False
        self.fema               = False
        
        self.InsightDirection   = InsightDirection.Flat
        self.trade              = False
        
        # Chart Plotting 
        self.algorithm          = algorithm
        self.kama.Updated       += self.OnSymbolDataUpdate
        
        self.dataPlot = Chart('Detail'+str(self.symbol))
        self.dataPlot.AddSeries(Series('Price', SeriesType.Line, '$'))
        self.dataPlot.AddSeries(Series('Kama', SeriesType.Line, '$'))
        self.dataPlot.AddSeries(Series('MOM', SeriesType.Line, ''))
        self.dataPlot.AddSeries(Series('EMA13', SeriesType.Line, '$'))
        self.dataPlot.AddSeries(Series('EMA63', SeriesType.Line, '$'))
        self.dataPlot.AddSeries(Series('EMA150', SeriesType.Line, '$'))
        self.dataPlot.AddSeries(Series('ROC', SeriesType.Line, ''))
        self.dataPlot.AddSeries(Series('Buy', SeriesType.Scatter, '$', Color.Green,ScatterMarkerSymbol.Circle))
        self.dataPlot.AddSeries(Series('Sell', SeriesType.Scatter, '$', Color.Red,ScatterMarkerSymbol.Circle))
        self.algorithm.AddChart(self.dataPlot)
        
    def getInsight(self, price, isInvested):    
        
        self.price              = price
        self.fkama              = self.price>self.kama.Current.Value*self.kama_factor 
        self.fmom               = self.mom.Current.Value>0
        self.froc               = self.roc.Current.Value>0
        self.fema               = self.ema13.Current.Value>self.ema63.Current.Value>self.ema150.Current.Value

        self.trade              = False
        
        if not isInvested and self.fmom and self.fkama and self.fema and self.froc:
            self.InsightDirection   =   InsightDirection.Up
            self.trade              =   True
            self.algorithm.Plot('Detail'+str(self.symbol),'Buy', self.price)

        if isInvested and (not self.fmom or not self.fkama or not self.fema or not self.froc):
            self.InsightDirection = InsightDirection.Flat # liqudates position - work around InsightDirection.Down which may sell and then short  
            self.trade              =   True
            self.algorithm.Plot('Detail'+str(self.symbol),'Sell',self.price)
    

    def OnSymbolDataUpdate(self, sender, updated):
        
        self.algorithm.Plot('Detail'+str(self.symbol),'Price', self.price)
        self.algorithm.Plot('Detail'+str(self.symbol),'Kama', self.kama.Current.Value)
        self.algorithm.Plot('Detail'+str(self.symbol),'ROC', self.roc.Current.Value)
        self.algorithm.Plot('Detail'+str(self.symbol),'MOM', self.mom.Current.Value)
        self.algorithm.Plot('Detail'+str(self.symbol),'EMA13', self.ema13.Current.Value)
        self.algorithm.Plot('Detail'+str(self.symbol),'EMA63', self.ema63.Current.Value)
        self.algorithm.Plot('Detail'+str(self.symbol),'EMA150', self.ema150.Current.Value)