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

    }

}