Overall Statistics |
Total Trades 13 Average Win 0.28% Average Loss -2.21% Compounding Annual Return 0.923% Drawdown 7.000% Expectancy -0.154 Net Profit 0.155% Sharpe Ratio 0.136 Probabilistic Sharpe Ratio 34.977% Loss Rate 25% Win Rate 75% Profit-Loss Ratio 0.13 Alpha 0.027 Beta -0.004 Annual Standard Deviation 0.193 Annual Variance 0.037 Information Ratio -1.025 Tracking Error 0.212 Treynor Ratio -6.61 Total Fees $117.80 Estimated Strategy Capacity $300000.00 |
import pandas as pd import numpy as np from scipy.stats import multivariate_normal as mvn from scipy.stats import norm class HighDebtUniverse(QCAlgorithm): def Initialize(self): self.SetStartDate(2004, 1, 1) self.SetEndDate(2004, 3, 1) # self.SetStartDate(2021, 1, 21) # self.SetEndDate(2021, 1, 18) self.SetCash(100000) self.SetBenchmark("SPY") self.SetWarmUp(1) self.UniverseSettings.Resolution = Resolution.Daily # self.UniverseSettings.Resolution = Resolution.Monthly self.AddUniverse(self.CoarseSelectionFunction, self.SelectFine) self.SetBrokerageModel(InteractiveBrokersBrokerageModel()) self.SetExecution(ImmediateExecutionModel()) self.EMPTY_SERIES = pd.Series() self.AddEquity("SPY",Resolution.Hour).Symbol self.stops = {} # Keep track of stop loss orders so we can update them self.stoplevel=0.70 # self.FirstFilter = 60 self.FirstFilter = 100 self.SecondFilter = 5 self.WeekDay = 3 self.numberOfSymbolsCoarse = 200 self.numberOfSymbolsFine = 3000 self.result = {} self.liquidate = set() self.mvn_lookback = 120 self.mvn_window = 60 # self.mvn_cdf_window = 20 self.mvn_lookahead = 20 self.mvn_threshold = 0.995 self.mvn_threshold_min = 0.6 self.mvn_rev_threshold = 0.005 self.cond_lookback = 60 self.prob = 0 # self.lastMonth = -1 # self.dollarVolumeBySymbol = {} self.rebalence_flag = 0 # make sure to run the universe selection at the start of the algorithm even it's not the manth start self.first_month_trade_flag = 1 self.downturn = 1 self.cover = 1 self.Schedule.On(self.DateRules.MonthEnd("SPY",2), self.TimeRules.At(1, 0), Action(self.monthly_rebalance)) self.Schedule.On(self.DateRules.MonthEnd("SPY"), self.TimeRules.AfterMarketOpen("SPY", 0), Action(self.rebalance)) # self.Schedule.On(self.DateRules.MonthEnd("SPY"), self.TimeRules.At(10, 0), Action(self.rebalance)) monitorPlot = Chart('Monitor') monitorPlot.AddSeries(Series('Leverage', SeriesType.Line, 0)) self.AddChart(monitorPlot) self.Schedule.On(self.DateRules.MonthEnd("SPY"), \ self.TimeRules.BeforeMarketClose("SPY", 0), \ self.Record) def Record(self): leverage = self.Portfolio.TotalAbsoluteHoldingsCost / self.Portfolio.TotalPortfolioValue self.Plot('Monitor', 'Leverage', leverage) def monthly_rebalance(self): self.rebalence_flag = 1 def CoarseSelectionFunction(self, coarse): selected = [] if not (self.rebalence_flag or self.first_month_trade_flag): return Universe.Unchanged sortedByDollarVolume = sorted([x for x in coarse if x.HasFundamentalData and x.DollarVolume > 3000000 and x.Price >0], key = lambda x: x.DollarVolume, reverse=True)[:self.numberOfSymbolsCoarse] return [x.Symbol for x in sortedByDollarVolume] def factor_stock_selection(self, name, fine, fun_filter, filter_lmd, order): if filter_lmd: fact_lmd = lambda x: fun_filter(x) > 0 and filter_lmd(x) else: fact_lmd = lambda x: fun_filter(x) > 0 fine = ([x for x in fine if fact_lmd(x) and x.CompanyReference.CountryId == "USA" and x.CompanyReference.PrimaryExchangeID in ["NYS","NAS"] and (self.Time - x.SecurityReference.IPODate).days > 180 and x.MarketCap > 2e9]) if len(fine) == 0: return ([], 0) sortedByDEratio = sorted(fine, key=fun_filter, reverse=order)[:self.FirstFilter] symbols = [x.Symbol for x in sortedByDEratio] averages = dict() history = self.History(symbols, 150, Resolution.Daily).close.unstack(0) for symbol in symbols: if symbol in history: df = history[symbol].dropna() else: continue if df.empty or len(df)<130: continue mom=df[:-1].pct_change(126) .iloc[-1] if pd.notnull(mom): averages[symbol] = mom mom_list = averages.items() sortedbyMomentum = sorted(mom_list, key=lambda x: x[1], reverse=True) selectedSyms = [x[0] for x in sortedbyMomentum[:self.SecondFilter]] selectedPrices = history[selectedSyms] return (selectedSyms, (selectedPrices.iloc[-1] - selectedPrices.iloc[-10]).sum(), selectedPrices.iloc[-self.mvn_lookback:].sum(axis=1), name) def SelectFine(self, fine): if not (self.rebalence_flag or self.first_month_trade_flag): return Universe.Unchanged self.rebalence_flag = 0 self.first_month_trade_flag = 0 best_factor = self.factor_stock_selection('RevenueGrowth',fine, lambda x: x.OperationRatios.RevenueGrowth.ThreeMonths, lambda x: x.FinancialStatements.IncomeStatement.TotalRevenue.ThreeMonths > 0, True) self.selected_price = best_factor[2] self.selected_symbols = best_factor[0] self.Log('======SELECT FACTOR:' + str(best_factor[3]) + ', ret:' + str(best_factor[1])) return best_factor[0] def rebalance(self): self.result = {} self.liquidate = set() count = len(self.selected_symbols) percent = 0 if count == 0 else 1 / count for stock in self.selected_symbols: # self.Log('==== SELECTED: ' + " " + stock.Value) self.result[stock] = percent invested = [ x.Symbol for x in self.Portfolio.Values if x.Invested] for share in invested: if not share in self.result: self.liquidate.add(share) self.Log('====================== Day: ' + str(self.Time) + ' ================================') for security in self.result: self.SetHoldings(security, self.result[security]) self.Log('==== BUYING: ' + security.Value + ': ' + str(self.result[security])) for security in self.liquidate: self.Log('==== LIQUIDATING: ' + security.Value) self.Liquidate(security)