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