Overall Statistics |
Total Trades 1862 Average Win 0.16% Average Loss -0.11% Compounding Annual Return -2.446% Drawdown 22.400% Expectancy -0.113 Net Profit -11.664% Sharpe Ratio -0.145 Probabilistic Sharpe Ratio 0.097% Loss Rate 63% Win Rate 37% Profit-Loss Ratio 1.41 Alpha -0.006 Beta -0.063 Annual Standard Deviation 0.09 Annual Variance 0.008 Information Ratio -0.625 Tracking Error 0.189 Treynor Ratio 0.208 Total Fees $1913.26 Estimated Strategy Capacity $41000000.00 Lowest Capacity Asset MELI TV0AIPE7984L |
from collections import deque from AlgorithmImports import * from datetime import timedelta, time import pandas as pd import numpy as np class BollBands(QCAlgorithm): #initialize method def Initialize(self): self.SetStartDate(2015, 10, 7) self.SetEndDate(2020, 10, 7)# Set Start Date self.SetCash(100000) # Set Strategy Cash self.SetBenchmark('SPY') self.UniverseSettings.Resolution = Resolution.Daily self.SetWarmUp(timedelta(days=7)) self.SetExecution(ImmediateExecutionModel()) self.AddUniverse(self.CoarseUniverse, self.FineUniverse) self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel()) self.AddAlpha(AlphaBollingerBands()) #used for rebalancing, and to select how many stocks goes to the coarse and fine. self.lastMonth = -1 self.coarse_filter = 100 self.fine_filter = 10 self.vol_history = 120 #Plotting standard variables def OnEndOfDay(self): self.Plot("Positions", "Num", len([x.Symbol for x in self.Portfolio.Values if self.Portfolio[x.Symbol].Invested])) self.Plot(f"Margin", "Used", self.Portfolio.TotalMarginUsed) self.Plot(f"Margin", "Remaining", self.Portfolio.MarginRemaining) self.Plot(f"Cash", "Remaining", self.Portfolio.Cash) self.Plot(f"Symbols", "Symbols", len(symbols_in_universe)) def CoarseUniverse(self, coarse): #Rebalance function, once a month if self.Time.month == self.lastMonth: return Universe.Unchanged self.lastMonth = self.Time.month #sorting by stocks over 10 bucks, and that has fundamental data selected = sorted([x for x in coarse if x.Price > 10 and x.HasFundamentalData], key = lambda x: x.DollarVolume, reverse=True) return [x.Symbol for x in selected[:self.coarse_filter]] def FineUniverse(self, fine): #make a list that only contains the symbols filtered_fine = [x.Symbol for x in fine] #implement the methods that is described stocks_by_vol = self.SortVolatility(filtered_fine, 360, Resolution.Daily) stocks_by_vol_keys = self.get_keys(stocks_by_vol) return [x for x in filtered_fine if str(x) in stocks_by_vol_keys[:self.fine_filter]] def SortVolatility(self, filtered_fine, lenght, resolution): #method that calculates the volatility with standard deviation, and returns it as a dict history = self.History(filtered_fine, lenght, resolution) prices = history.drop_duplicates().close.unstack(level =0) vol = np.std(prices) vol_to_dict = vol.to_dict() rangeret = sorted(vol_to_dict, key = vol_to_dict.get, reverse = True) return {symbol: rank for rank, symbol in enumerate(rangeret, 1)} def get_keys(self, dic): #Method to get the historie = {key:dic.get(key, 0) for key in set(dic)} historie = sorted(historie.items(), key = lambda x: x[1], reverse = False) listoflist = [] for tuple1 in historie: list1 = list(tuple1) i = list1.pop(0) listoflist.append(i) return listoflist class AlphaBollingerBands(AlphaModel): def __init__(self, period = 10, deviation = 2, movingAverageType = MovingAverageType.DoubleExponential, resolution = Resolution.Daily): self.period = period self.deviation = deviation self.movingAverageType = movingAverageType self.resolution = resolution self.insightPeriode = Time.Multiply(Extensions.ToTimeSpan(resolution), period) self.symbolDataBySymbol = {} global symbols_in_universe symbols_in_universe = self.symbolDataBySymbol self.days = 10 def Update(self, algorithm, data): if not self.days == 10: self.days += 1 return [] else: self.days = 0 insights = [] for symbol, symbolDataBySymbol in self.symbolDataBySymbol.items(): direction = InsightDirection.Flat price = symbolDataBySymbol.Security.Price lower = symbolDataBySymbol.Bollinger.LowerBand.Current.Value upper = symbolDataBySymbol.Bollinger.UpperBand.Current.Value middle = symbolDataBySymbol.Bollinger.MiddleBand.Current.Value #implemeter "previous state" for at tjekke hvilken tilstand at den sidst var i if symbolDataBySymbol.Security.Invested: if algorithm.Portfolio[symbol.Symbol].IsLong: if price >= middle: direction = InsightDirection.Flat else: direction = InsightDirection.Up elif algorithm.Portfolio[symbol.Symbol].IsShort: if price <= middle: direction = InsightDirection.Flat else: direction = InsightDirection.Down elif not symbolDataBySymbol.Security.Invested: if price <= lower: direction = InsightDirection.Up elif price >= upper: direction = InsightDirection.Down else: direction = InsightDirection.Flat if direction == symbolDataBySymbol.PreviousDirection: continue insight = Insight.Price(symbolDataBySymbol.Security.Symbol, self.insightPeriode, direction) symbolDataBySymbol.PreviousDirections = insight.Direction insights.append(insight) return insights def OnSecuritiesChanged(self, algorithm, changes): for symbol in changes.AddedSecurities: if symbol not in self.symbolDataBySymbol: symbol_data = SymbolData(symbol) symbol_data.RegisterIndicatorBollinger(algorithm, self.period, self.deviation, self.movingAverageType, self.resolution) history = algorithm.History([symbol.Symbol], self.period, self.resolution) if not symbol_data.Bollinger.IsReady: symbol_data.WarmUpIndicators(history, symbol) self.symbolDataBySymbol[symbol] = symbol_data for removed in changes.RemovedSecurities: data = self.symbolDataBySymbol.pop(removed.Symbol, None) if data is not None: SymbolData.RemoveConsolidators(algorithm) class SymbolData: def __init__(self, symbol): self.Security = symbol def RegisterIndicatorBollinger(self, algorithm, period, deviation, movingAverageType, resolution): self.period = period self.deviation = deviation self.movingAverageType = movingAverageType self.Consolidator = None self.Bollinger = BollingerBands(self.period, deviation, movingAverageType) self.Consolidator = algorithm.ResolveConsolidator(self.Security.Symbol, resolution) algorithm.RegisterIndicator(self.Security.Symbol, self.Bollinger, self.Consolidator) self.PreviousDirection = None def RemoveConsolidators(self, algorithm): if self.Consolidator is not None: algorithm.SubscriptionManager.RemoveConsolidator(self.Security, self.Consolidator) def WarmUpIndicators(self, history, symbol): for time, row in history.loc[symbol.Symbol].iterrows(): self.Bollinger.Update(time, row['close'])