| 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)