| Overall Statistics |
|
Total Trades 3010 Average Win 0.54% Average Loss -0.51% Compounding Annual Return 32.235% Drawdown 24.700% Expectancy 0.116 Net Profit 131.406% Sharpe Ratio 1.048 Probabilistic Sharpe Ratio 44.812% Loss Rate 46% Win Rate 54% Profit-Loss Ratio 1.07 Alpha 0.166 Beta 0.995 Annual Standard Deviation 0.236 Annual Variance 0.056 Information Ratio 0.911 Tracking Error 0.181 Treynor Ratio 0.248 Total Fees $14063.89 Estimated Strategy Capacity $170000.00 Lowest Capacity Asset PBR RX3JVQEROTWL Portfolio Turnover 27.38% |
#region imports
from AlgorithmImports import *
#endregion
def InitCharts(self):
self.sectordict = {'Technology' : 311, 'BasicMaterials' : 101, 'ConsumerCyclical' : 102, 'FinancialServices' : 103, 'RealEstate' : 104, 'ConsumerDefensive' : 205, 'Healthcare' : 206, 'Utilities' : 207, 'CommunicationServices' : 308, 'Energy' : 309, 'Industrials' : 310}
sector_plot = Chart('Sector Performance')
for sector in self.sectordict.keys():
sector_plot.AddSeries(Series(sector, SeriesType.Line, 0)) # Option for stacked area instead is below
#sector_plot.AddSeries(Series(sector, SeriesType.StackedArea, 0))
self.AddChart(sector_plot)
sectorweight_plot = Chart('Sector Weightings')
for sector in self.sectordict.keys():
sectorweight_plot.AddSeries(Series(sector, SeriesType.Line, 0, "%"))
self.AddChart(sectorweight_plot)
def PlotSectorChart(self):
for sector in list(self.sectordict.keys()):
valueinsector = []
for security, v in self.Portfolio.items():
if v.Invested:
if self.dictionary[security.Value].AssetClassification.MorningstarSectorCode == self.sectordict[sector]:
valueinsector.append(v.NetProfit + v.UnrealizedProfit) #total net and unrealized profit of invested security
if v.LastTradeProfit != 0 and v.Invested == False:
if self.dictionary[security.Value].AssetClassification.MorningstarSectorCode == self.sectordict[sector]:
valueinsector.append(v.NetProfit) #total net profit of security that was once invested but is no longer invested
Sectorprofitvalue = sum([f for f in valueinsector])
if Sectorprofitvalue != 0: #net profit is -1 for first month because fees. Graph looks better starting from the origin
self.Plot('Sector Performance', f'{sector}', Sectorprofitvalue)
else:
pass
def PlotSectorWeightingsChart(self):
for sector in list(self.sectordict.keys()):
weightsinsector=[]
for security, v in self.Portfolio.items():
if v.Invested:
if self.dictionary[security.Value].AssetClassification.MorningstarSectorCode == self.sectordict[sector]:
weightsinsector.append(100*v.AbsoluteHoldingsValue/self.Portfolio.TotalHoldingsValue)
Totalweightingofsector = sum([abs(f) for f in weightsinsector])
self.Plot('Sector Weightings', f'{sector}', Totalweightingofsector)
#region imports
from AlgorithmImports import *
#endregion
from universe_selection import FactorUniverseSelectionModel
from charting import InitCharts, PlotSectorChart, PlotSectorWeightingsChart
class TradingBot(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 9, 1)
self.SetEndDate(2023, 9, 1)
self.SetCash(100000)
self.SetWarmUp(14, Resolution.Daily)
# Data resolution
self.UniverseSettings.Resolution = Resolution.Minute
# Universe selection model
self.securities = []
self.dictionary = {} # Dictionary to hold fundamental data of past and present holdings. This is used rather than pulling from Securities dictionary as fine fundamental data is not always available for all stocks on everyday so bugs are avoided this way
self.CustomUniverseSelectionModel = FactorUniverseSelectionModel(self)
self.AddUniverse(self.CustomUniverseSelectionModel.SelectCoarse, self.CustomUniverseSelectionModel.SelectFine)
# Add SPY for trading days data
self.AddEquity('SPY', Resolution.Daily)
self.SetBenchmark("SPY")
# Schedule rebalancing
self.Schedule.On(self.DateRules.Every(DayOfWeek.Friday), self.TimeRules.At(12,15,0), Action(self.Execute))
#Initiate charting
InitCharts(self)
#Schedule charting once per week
self.Schedule.On(self.DateRules.Every(DayOfWeek.Friday), self.TimeRules.BeforeMarketClose("SPY", 0), Action(self.PlotCharts))
def OnData(self, data):
self.slice = data
def PlotCharts(self):
if self.IsWarmingUp:
pass
else:
PlotSectorChart(self)
PlotSectorWeightingsChart(self)
def Execute(self):
if self.IsWarmingUp:
pass
else:
self.Liquidate()
self.dictionary.update({f.Symbol.Value : f for f in self.securities})
portfoliodataframe = pd.DataFrame.from_records(
[
{
'symbol': security.Symbol,
'weight': 1/len(self.securities)
} for security in self.securities
]).set_index('symbol')
self.Log(portfoliodataframe)
self.Log(f"Setting portfolio holdings for {len(portfoliodataframe.index)} securities...")
for security, weight in portfoliodataframe.iterrows():
if self.slice.Bars.ContainsKey(security) and self.slice[security] is not None:
self.SetHoldings(security, weight)
self.Log(f'BREAK - Successfully set all holdings')
#region imports
from AlgorithmImports import *
#endregion
import numpy as np
class FactorUniverseSelectionModel():
def __init__(self, algorithm):
self.algorithm = algorithm
def SelectCoarse(self, coarse):
# self.algorithm.Log("Generating universe...")
universe = self.FilterDollarPriceVolume(coarse)
return [c.Symbol for c in universe]
def SelectFine(self, fine):
universe = self.FilterFactor(fine)
# self.algorithm.Log(f"Universe consists of {len(universe)} securities")
self.algorithm.securities = universe
return [f.Symbol for f in universe]
def FilterDollarPriceVolume(self, coarse):
filter_dollar_price = [c for c in coarse if 5 < c.Price]
sorted_dollar_volume = sorted([c for c in filter_dollar_price if c.HasFundamentalData], key=lambda c: c.DollarVolume, reverse=True)
return sorted_dollar_volume[:1000]
def FilterFactor(self, fine):
stocksinsector = [f for f in fine if f.AssetClassification.MorningstarSectorCode != MorningstarSectorCode.FinancialServices]
sortedbycashreturn = sorted(stocksinsector, key=lambda f: f.ValuationRatios.CashReturn, reverse=True)
universe = sortedbycashreturn[:10]
return universe