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