Overall Statistics |
Total Trades 4112 Average Win 0.11% Average Loss -0.19% Compounding Annual Return 11.836% Drawdown 39.300% Expectancy 0.183 Net Profit 206.136% Sharpe Ratio 0.653 Loss Rate 26% Win Rate 74% Profit-Loss Ratio 0.59 Alpha 0.162 Beta -1.456 Annual Standard Deviation 0.203 Annual Variance 0.041 Information Ratio 0.555 Tracking Error 0.203 Treynor Ratio -0.091 Total Fees $4570.59 |
//using pd = pandas; //using np = numpy; //using LinearRegression = sklearn.linear_model.LinearRegression; using System; using System.Collections.Generic; using System.Linq; using MathNet.Numerics.LinearRegression; using MathNet.Numerics.Statistics; using QuantConnect; using QuantConnect.Algorithm; using QuantConnect.Data; using QuantConnect.Data.Market; using QuantConnect.Indicators; namespace JamesChCh.Algorithm { public class TrendFollowingAlgorithmCSharp : QCAlgorithm { int lookback = Convert.ToInt32(252 / 2); double profittake = 1.96; double maxlever = 0.9; double multiple = 5.0; double PctDailyVolatilityTarget = 0.025; List<Symbol> symbols = new List<Symbol>(); Dictionary<Symbol, double> weights = new Dictionary<Symbol, double>(); Dictionary<Symbol, double?> stops = new Dictionary<Symbol, double?>(); Dictionary<Symbol, List<double>> price = new Dictionary<Symbol, List<double>>(); public override void Initialize() { SetStartDate(2008, 1, 1); SetEndDate(2018, 1, 1); SetCash(100000); AddEquity("SPY", Resolution.Minute); load_symbols(); Schedule.On(DateRules.EveryDay("SPY"), TimeRules.AfterMarketOpen("SPY", 10), () => trail_stop()); Schedule.On(DateRules.EveryDay("SPY"), TimeRules.AfterMarketOpen("SPY", 28), () => regression()); Schedule.On(DateRules.EveryDay("SPY"), TimeRules.AfterMarketOpen("SPY", 30), () => trade()); } public override void OnData(Slice data) { } public virtual Dictionary<Symbol, double> calc_vol_scalar() { var df_price = price.ToDictionary(k => k.Key, v => v.Value.ToArray()); var rets = df_price.ToDictionary(k => k.Key, v => v.Value.Log().Diff().DropNan()); //not being used //var lock_value = df_price.Last().Value; var price_vol = calc_std(rets); var volatility_scalar = price_vol.ToDictionary(k => k.Key, v => PctDailyVolatilityTarget / v.Value); return volatility_scalar; } public virtual Dictionary<Symbol, double> calc_std(Dictionary<Symbol, double[]> returns) { var downside_only = false; if (downside_only) { returns = returns.ToDictionary(k => k.Key, v => v.Value.Select(r => r > 0.0 ? Double.NaN : r).ToArray()); } // Exponentially-weighted moving std // jameschch: Just a std dev in C#. var b = returns.ToDictionary(k => k.Key, v => v.Value.StandardDeviation()); return b; } public virtual void regression() { price = new Dictionary<Symbol, List<double>>(); foreach (var symbol in symbols) { var history = History<TradeBar>(symbol, lookback, Resolution.Daily); var current = History<TradeBar>(symbol, 28, Resolution.Minute); if (!(history?.Any() ?? false)) { continue; } price.Add(symbol, history.Select(ss => (double)ss.Open).ToList()); price[symbol].Add(current.Select(ss => (double)ss.Open).First()); } var A = Range(lookback + 1); foreach (var symbol in price.Keys) { // volatility var std = price[symbol].StandardDeviation(); // Price points to run regression var Y = price[symbol].ToArray(); // Add column of ones so we get intercept var ones = Ones(A.Count()); var X = A.Select((aa, i) => new[] { ones[i], aa }).ToArray(); if (X.Count() != Y.Count()) { var length = Math.Min(X.Count(), Y.Count()); X = X.Reverse().Take(length).Reverse().ToArray(); Y = Y.Reverse().Take(length).Reverse().ToArray(); A = A.Reverse().Take(length).Reverse().ToArray(); } // Creating Model // Fitting training data //jameschch: this is only supported in newer Accord //var reg = new Accord.Statistics.Models.Regression.Linear.OrdinaryLeastSquares { UseIntercept = true }; //var reg2 = reg.Learn(X.Select(x => x[1]).ToArray(), Y, X.Select(x => x[0]).ToArray()); //jameschch: could alternatively use these: //var qlreg = new QLNet.LinearRegression(X.Select(x => x.ToList()).ToList(), Y.ToList()); var mathnetreg = MathNet.Numerics.LinearRegression.SimpleRegression.Fit(X.Select(x => x[1]).ToArray(), Y); var reg2 = new { Intercept = mathnetreg.Item1, Slope = mathnetreg.Item2 }; // run linear regression y = ax + b var b = reg2.Intercept; var a = reg2.Slope; // Normalized slope var slope = a / b * 252.0; // Currently how far away from regression line var delta = A.Select((aa, i) => Y[i] - (a * aa + b)).ToArray(); // Don't trade if the slope is near flat (at least %7 growth per year to trade) // jameschch: I have no idea why this means 7% var slope_min = 0.252; // Long but slope turns down, then exit if (weights[symbol] > 0 && slope < 0) { weights[symbol] = 0; } // short but slope turns upward, then exit //jameschch: some controversy about slope > 0 or slope < 0 if (weights[symbol] < 0 && slope < 0) { weights[symbol] = 0; } // Trend is up if (slope > slope_min) { // price crosses the regression line if (delta.Last() > 0 && delta[delta.Length - 2] < 0 && weights[symbol] == 0) { stops[symbol] = null; weights[symbol] = slope; } // Profit take, reaches the top of 95% bollinger band if (delta.Last() > profittake * std && weights[symbol] > 0) { weights[symbol] = 0; } } // Trend is down if (slope < -slope_min) { // price crosses the regression line if (delta.Last() < 0 && delta[delta.Length - 2] > 0 && weights[symbol] == 0) { stops[symbol] = null; weights[symbol] = slope; } // profit take, reaches the top of 95% bollinger band if (delta.Last() < profittake * std && weights[symbol] < 0) { weights[symbol] = 0; } } } } public virtual void trade() { var vol_mult = calc_vol_scalar(); var no_positions = 0; foreach (var symbol in symbols) { if (weights[symbol] != 0) { no_positions += 1; } } foreach (var symbol in symbols) { if (weights[symbol] == 0 && Portfolio[symbol].Invested) { Liquidate(symbol); } else if (weights[symbol] > 0) { SetHoldings(symbol, Math.Min(weights[symbol] * multiple, maxlever) / no_positions * vol_mult[symbol]); } else if (weights[symbol] < 0) { SetHoldings(symbol, Math.Max(weights[symbol] * multiple, -maxlever) / no_positions * vol_mult[symbol]); } } } public virtual void trail_stop() { var hist = History<TradeBar>(symbols, 3, Resolution.Daily); foreach (var symbol in symbols) { var mean_price = hist.SelectMany(s => s.Where(q => q.Key == symbol)).Select(ss => (double)ss.Value.Close).Mean(); // Stop loss percentage is the return over the lookback period var stoploss = Math.Abs(weights[symbol] * lookback / 252.0) + 1; if (weights[symbol] > 0 && stops[symbol] != null) { if (stops[symbol] != null && stops[symbol] < 0) { stops[symbol] = mean_price / stoploss; } else { stops[symbol] = Math.Max(mean_price / stoploss, stops[symbol] ?? 0); if (mean_price < stops[symbol]) { weights[symbol] = 0; Liquidate(symbol); } } } else if (weights[symbol] < 0 && stops[symbol] != null) { if (stops[symbol] != null && stops[symbol] < 0) { stops[symbol] = mean_price * stoploss; } else { stops[symbol] = Math.Min(mean_price * stoploss, stops[symbol] ?? 0); if (mean_price > stops[symbol]) { weights[symbol] = 0; Liquidate(symbol); } } } else { stops[symbol] = null; } } } public virtual void load_symbols() { var equities = new List<string> { "DIA", "SPY" }; var fixedincome = new List<string> { "IEF", "HYG" }; var alternative = new List<string> { "USO", "GLD", "VNQ", "RWX", "UNG", "DBA" }; var syl_list = equities.Concat(fixedincome).Concat(alternative); foreach (var i in syl_list) { var adding = AddEquity(i, Resolution.Minute).Symbol; symbols.Add(adding); weights.Add(adding, 0); stops.Add(adding, null); } } private double[] Range(int maximum) { var calculated = new List<double>(); for (double i = 0; i < maximum; i++) { calculated.Add(i); } return calculated.ToArray(); } private double[] Ones(int maximum) { var calculated = new List<double>(); for (int i = 0; i < maximum; i++) { calculated.Add(1d); } return calculated.ToArray(); } } public static class ExtensionMethods { public static double[] Diff(this double[] numbers) { return numbers.Select((x, i) => i >= 1 ? x - numbers[i - 1] : 0).ToArray(); } public static double[] DropNan(this double[] numbers) { return numbers.Where(n => !double.IsNaN(n)).ToArray(); } public static double[] Log(this double[] numbers) { return numbers.Select(n => Math.Log(n)).ToArray(); } } }