Overall Statistics |
Total Trades 452 Average Win 1.32% Average Loss -0.76% Compounding Annual Return 11.425% Drawdown 10.400% Expectancy 0.462 Net Profit 114.519% Sharpe Ratio 0.98 Probabilistic Sharpe Ratio 42.824% Loss Rate 46% Win Rate 54% Profit-Loss Ratio 1.73 Alpha 0.047 Beta 0.324 Annual Standard Deviation 0.083 Annual Variance 0.007 Information Ratio -0.214 Tracking Error 0.12 Treynor Ratio 0.251 Total Fees $1095.77 Estimated Strategy Capacity $840000000.00 Lowest Capacity Asset SPY R735QTJ8XC9X |
/* Creator: Conor Flynn MGMT6420-Student Investment Fund Strategy: ETF Drawdown Date: 01/19/2022 */ using MathNet.Numerics; namespace QuantConnect.Algorithm.CSharp { public class ETFDrawdown : QCAlgorithm { // BACKTESTING PARAMETERS // ================================================================================================================= // general settings: // set starting cash private int startingCash = 100000; // backtesting start date time: // date setting variables private int startYear = 2015; private int startMonth = 1; private int startDay = 1; // backtesting end date time: // determines whether there is a specified end date // if false it will go to the current date (if 'true' it will go to the specified date) private bool enableEndDate = false; // date setting variables private int endYear = 2020; private int endMonth = 1; private int endDay = 5; // universe settings: // data update resolution // changes how often the data updates and algorithm looks for entry // determines how often the function OnData runs // list of resolutions: // Resolution.Tick; Resolution.Second; Resolution.Minute; Resolution.Hour; Resolution.Daily private readonly Resolution resolution = Resolution.Daily; // stock list for equities // list of equities you want in the universe // used in manual selection of universe // set selectionType = false for activation private readonly String[] manualUniverse = new String[]{ // "XLB", // Materials // "XLC", // Communication Services // "XLE", // Energy // "XLF", // Financials // "XLI", // Industrials // "XLK", // Technology // "XLP", // Staples // "XLRE", // Real Estate // "XLU", // Utilities // "XLV", // Health Care // "XLY", // "QQQ", // "SPY", // "VOO", // "GLD", // "EEM", // "IEMG", // "VTI", // "IVV", // "VEA", // "VTV", // "GDX", // "EFA" //"QQQ", "SPY" }; // position settings: // percent of portfolio to enter a position with // note this value is recommended to be < 1 / manualUniverse.Count() private readonly decimal portfolioAllocation = 1m; // number of securities to invest in // note this will split the portfolioAllocation evenly among them private readonly int stockCount = 5; // indicator settings: // drawdown threshold required for consideration public static readonly double DrawdownThreshold = 0.01; // drawdown SMA length // fast moving SMA public static readonly int DrawdownSmaFastLength = 10; // slow moving SMA public static readonly int DrawdownSmaSlowLength = 10; // linear regression length public static readonly int LinearRegressionLength = 25; // ================================================================================================================= // creates new universe variable setting private Dictionary<Symbol, SymbolData> universe = new Dictionary<Symbol, SymbolData>(); // invested securities private Dictionary<Symbol, SymbolData> invested = new Dictionary<Symbol, SymbolData>(); // security changes variable private SecurityChanges securityChanges = SecurityChanges.None; // define offset universe to avoid first candle private bool offsetUniverse = true; public override void Initialize() { // set start date SetStartDate(startYear, startMonth, startDay); // set end date if(enableEndDate) SetEndDate(endYear, endMonth, endDay); // set starting cash SetCash(startingCash); foreach(string s in manualUniverse) { AddEquity(s, resolution); } } // filter based on CoarseFundamental public IEnumerable<Symbol> CoarseFilterFunction(IEnumerable<CoarseFundamental> coarse) { // returns the highest DollarVolume stocks // returns "totalNumberOfStocks" amount of stocks return (from stock in coarse where !stock.HasFundamentalData orderby stock.DollarVolume descending select stock.Symbol).Take(stockCount); return Universe.Unchanged; } // empty since we consolidate bars public override void OnData(Slice data) { foreach(var symbol in data.Keys) { SymbolData sd = universe[symbol]; sd.Update((double)Securities[symbol].Price); if(!sd.IsReady) continue; if(symbol == "SPY") { Plot("Symbol Drawdown", "Drawdown", sd.Drawdown()); Plot("Symbol Drawdown", "DrawdownLocalMean", sd.DrawdownLocalMean()); Plot("Symbol Drawdown", "DrawdownLocalStd", sd.DrawdownLocalMeanStd(-1)); Plot("Symbol Drawdown", "DrawdownSma", sd.DrawdownSmaFast()); //Plot("Symbol Drawdown", "StandardDeviation(2)", sd.DrawdownMeanStd(2)); //Plot("Symbol Drawdown", "DrawdownThreshold", DrawdownThreshold); //Plot("STD", "DrawdownMean", sd.DrawdownMean()); //Plot("STD", "LocalSTD", sd.DrawdownLocalStd()); //Plot("STD", "LocalSTD1", sd.DrawdownLocalMeanStd(-0.5)); //Plot("Symbol Drawdown", "SmaFast", sd.DrawdownSmaFast()); //Plot("Symbol Drawdown", "SmaSlow", sd.DrawdownSmaSlow()); Plot("LR", "LR Slope0", (decimal)sd.DrawdownLR()[0]); Plot("LR", "LR Slope1", (decimal)sd.DrawdownLR()[1]); Plot("Symbol Pricing", "Close", Securities[symbol].Close); } CheckEntry(sd); CheckExit(sd); } } public void CheckEntry(SymbolData sd) { if(Securities[sd.Symbol].Invested) return; if(sd.Drawdown() < sd.DrawdownSmaFast() || sd.Drawdown() < DrawdownThreshold) Allocate(sd); } public void Allocate(SymbolData sd) { SetHoldings(sd.Symbol, portfolioAllocation / manualUniverse.Count()); } public void CheckExit(SymbolData sd) { if(!Securities[sd.Symbol].Invested) return; if(sd.Drawdown() > sd.DrawdownSmaSlow() && sd.DrawdownSmaSlow() >= sd.DrawdownSmaFast()) Liquidate(sd.Symbol); } // OnSecuritiesChanged runs when the universe updates current securities public override void OnSecuritiesChanged(SecurityChanges changes) { securityChanges = changes; // remove stocks from list that get removed from universe foreach (var security in securityChanges.RemovedSecurities) { if(Securities[security.Symbol].Invested) { Log($"{Time}->Portfolio: Liquidated security {security.Symbol} on universe exit"); Liquidate(security.Symbol); } universe.Remove(security.Symbol); Log($"{Time}->Universe: Removed security {security.Symbol} from universe"); } // add new securities to universe list foreach(var security in securityChanges.AddedSecurities) { // create SymbolData variable for security SymbolData sd = new SymbolData(); // initalize data points: sd.Algorithm = this; sd.Symbol = security.Symbol; // load indicators: sd.Max = MAX(sd.Symbol, 253, Resolution.Daily, Field.High); sd.SmaFast = new SimpleMovingAverage(DrawdownSmaFastLength); sd.SmaSlow = new SimpleMovingAverage(DrawdownSmaSlowLength); // historical data load var history = History(sd.Symbol, 253, Resolution.Daily); foreach(var bar in history) { sd.Max.Update(bar.Time, bar.High); } // load linear regression variables: sd.XValues = new double[LinearRegressionLength]; for(int i = 0; i < LinearRegressionLength; i++) sd.XValues[i] = i; sd.YValues = new double[LinearRegressionLength]; // add SymbolData to universe universe.Add(security.Symbol, sd); Log($"{Time}->Universe: Added security {security.Symbol} to universe"); } } // default class containing all symbol information public class SymbolData { // Variables: // algorithm public ETFDrawdown Algorithm; // stock symbol public string Symbol = ""; // is ready public bool IsReady => Max.IsReady && SmaFast.IsReady && SmaSlow.IsReady && LRRollingWindow.IsReady; // drawdown variables: // 52-week high public Maximum Max; // drawdown private double _drawdown = 0.0; // drawdown SMA public SimpleMovingAverage SmaFast; public SimpleMovingAverage SmaSlow; // drawdown local private List<double> _drawdownLocal = new List<double>(); // drawdown local maximum private double _drawdownLocalMaximum = 0.0; // drawdown historical maximums private List<double> _drawdownMaximum = new List<double>(); // mean of maximums private double _drawdownMean; // std of maximums private double _drawdownStd; // linear regression variables: // default XValues public double[] XValues; public double[] YValues; public RollingWindow<double> LRRollingWindow = new RollingWindow<double>(LinearRegressionLength); public void Update(double value) { if(!Max.IsReady) return; // update drawdown _drawdown = 1 - (value / (double)Max.Current.Value); SmaFast.Update(Algorithm.Time, (decimal)_drawdown); SmaSlow.Update(Algorithm.Time, (decimal)_drawdown); // update LR RollingWindow LRRollingWindow.Add(_drawdown); // if above threshold then add to temporary if(_drawdown > ETFDrawdown.DrawdownThreshold) { // add to local max list _drawdownLocal.Add(_drawdown); // if local max if(_drawdown > _drawdownLocalMaximum) _drawdownLocalMaximum = _drawdown; } // if below threshold add local maximum if set else if(_drawdown < ETFDrawdown.DrawdownThreshold && _drawdownLocalMaximum != 0.0) { // if outside 2.0 range, don't add if(_drawdownMaximum.Count() == 0 || _drawdownLocalMaximum < DrawdownMeanStd(2)) { Recalculate(); } // clear drawdown _drawdownLocal.Clear(); _drawdownLocalMaximum = 0; } } private void Recalculate() { _drawdownMaximum.Add(_drawdownLocalMaximum); // calculate mean and std _drawdownMean = (_drawdownLocalMaximum + _drawdownMaximum.Count() * _drawdownMean) / (_drawdownMaximum.Count() + 1); _drawdownStd = StandardDeviation(_drawdownMaximum); _drawdownLocalMaximum = 0.0; } public static double StandardDeviation(IEnumerable<double> sequence) { if(sequence.Count() < 2) return 0; double average = sequence.Average(); double sum = sequence.Sum(d => Math.Pow(d - average, 2)); return Math.Sqrt((sum) / (sequence.Count() - 1)); } public double Drawdown() { return _drawdown; } public double DrawdownSmaFast() { return (double)SmaFast.Current.Value; } public double DrawdownSmaSlow() { return (double)SmaSlow.Current.Value; } public double DrawdownMean() { return _drawdownMean; } public double DrawdownLocalMean() { if(_drawdownLocal.Count() == 0) return 0; return _drawdownLocal.Average(); } public double DrawdownStd() { return _drawdownStd; } public double DrawdownLocalStd() { if(_drawdownLocal.Count() < 2) return 0.0; return StandardDeviation(_drawdownLocal); } public double DrawdownMeanStd(double multiplier = 0.0) { return _drawdownMean + (multiplier * _drawdownStd); } public double DrawdownLocalMeanStd(double multiplier = 0.0) { return DrawdownLocalMean() + (multiplier * StandardDeviation(_drawdownLocal)); } public double[] DrawdownLR() { for(int i = 0; i < XValues.Count(); i++) YValues[i] = LRRollingWindow[i]; return Fit.Polynomial(XValues, YValues, 2); } } } }