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