Overall Statistics
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data.Consolidators;
using QuantConnect.Indicators;
using QuantConnect.Data.Market;
using Accord.MachineLearning.VectorMachines;
using Accord.MachineLearning.VectorMachines.Learning;
using Accord.Statistics.Kernels;
using AForge;

namespace QuantConnect.Algorithm
{
    /*
    *   QuantConnect University: FOREX - Using Currency Data
    *
    *   QuantConnect allows you to use currency data for your backtest with a 
    *   simple line of code. See the SecurityType.Forex below.
    */
    public class ExampleMachineLearning : QCAlgorithm
    {
        static int trainSize = 1000;
        static int historyBarCount = 2;
        
        static decimal targetPct = 2m;
        static decimal stopPct = 2m;
        
        static decimal targetOverShoot = 0.1m;
        static int mainPeriod = 21;
        static int slowEmaPeriod = 55;
        static int middleEmaPeriod = 21;
        static int fastEmaPeriod = 8;
        static int waitPeriod = 20;
        string symbol = "SPY";
        static decimal tradePct = 1m;

        bool useComplexInputs = true;
        bool useComplexTargets = true;
        bool useLinearSVM = false;
        bool useWickStop = false;
        bool expandTakeProfit = true;

        bool refreshBiquarterly = trainSize > 800;
        bool refreshQuarterly = trainSize <= 800 && trainSize > 400;
        bool refreshMonthly = trainSize <= 400 && trainSize > 120;

        DateTime enterDate;

        int windowSize = historyBarCount + trainSize + 2 + waitPeriod - 1;

        bool printedSupportVectors = true;
        bool printedInputs = true;
        bool printedDayWindow = true;
        bool printedError = true;

        decimal stopPrice;
        decimal targetPrice;

        RollingWindow<TradeBar> dayWindow;
        TradeBarIndicatorRollingWindow atrWindow;
        RollingWindow<decimal> wickStopWindow;
        DataPointIndicatorRollingWindow fastEmaWindow;
        DataPointIndicatorRollingWindow middleEmaWindow;
        DataPointIndicatorRollingWindow slowEmaWindow;

        IndicatorFeatureCollection featureCollection;

        IndicatorBase<IndicatorDataPoint> wickStop;
        IndicatorBase<TradeBar> wickStopPoint;
        IndicatorBase<TradeBar> closingPrice;

        DataPointIndicatorRollingWindow redLine;

        Queue<double[]> samples = new Queue<double[]>(trainSize);
        SupportVectorMachine svm;

        Queue<TimeSpan> trainingTimes = new Queue<TimeSpan>();
        Queue<TimeSpan> computeTimes = new Queue<TimeSpan>();

        List<string> logStrings = new List<string>();

        double error = 0;
        int targetPositives = 0;
        int targetNegatives = 0;

        /// <summary>
        ///   Initialize QuantConnect Strategy.
        /// </summary>

        private void LogLater(string message)
        {
            logStrings.Add(message);
        }

        public override void OnEndOfAlgorithm()
        {
            base.OnEndOfAlgorithm();

            var totalTrainingTime = trainingTimes.Aggregate(TimeSpan.FromMilliseconds(0), (acc, next) => acc + next);
            var averageTrainingTime = totalTrainingTime.TotalSeconds / trainingTimes.Count;

            var totalComputeTime = computeTimes.Aggregate(TimeSpan.FromMilliseconds(0), (acc, next) => acc + next);
            var averageComputeTime = totalComputeTime.TotalSeconds / computeTimes.Count;

            Log(string.Format("SVM Training (seconds) Total: {0:0.##} Average: {1:0.####} Count: {2}", totalTrainingTime.TotalSeconds, averageTrainingTime, trainingTimes.Count));
            Log(string.Format("SVM Compute (seconds) Total: {0:0.##} Average: {1:0.####} Count: {2}", totalComputeTime.TotalSeconds, averageComputeTime, computeTimes.Count));

            foreach (var message in logStrings)
            {
                Log(message);
            }
        }

        public override void Initialize()
        {
            dayWindow = new RollingWindow<TradeBar>(windowSize);

            atrWindow = new TradeBarIndicatorRollingWindow(windowSize, new AverageTrueRange(mainPeriod), false);
            fastEmaWindow = new DataPointIndicatorRollingWindow(windowSize, new ExponentialMovingAverage(fastEmaPeriod), null, false);
            slowEmaWindow = new DataPointIndicatorRollingWindow(windowSize, new ExponentialMovingAverage(slowEmaPeriod), null, false);
            middleEmaWindow = new DataPointIndicatorRollingWindow(windowSize, new ExponentialMovingAverage(middleEmaPeriod), null, false);
            redLine = new DataPointIndicatorRollingWindow(windowSize, new SimpleMovingAverage(200), null, false);

            featureCollection = new IndicatorFeatureCollection(
                atrWindow,
                fastEmaWindow,
                middleEmaWindow,
                slowEmaWindow,
                redLine
                );

            wickStopWindow = new RollingWindow<decimal>(windowSize);
            wickStopPoint = new FunctionalIndicator<TradeBar>("WickStop",
                t => t.Close > t.Open
                    ? t.Open - t.Low
                    : t.Close < t.Open
                        ? t.High - t.Open
                        : Math.Max(t.High - t.Open, t.Open - t.Low), i => i.Samples > 0);

            closingPrice = new FunctionalIndicator<TradeBar>("Close", t => t.Close, i => i.Samples > 0);

            var wickStopDev = new StandardDeviation(mainPeriod).Of(wickStopPoint);
            var wickStopMA = new SimpleMovingAverage(mainPeriod).Of(wickStopPoint);

            wickStop = new CompositeIndicator<IndicatorDataPoint>(wickStopDev, wickStopMA, (left, right) => 2 * left + right);

            var endDate = new DateTime(2015, 1, 1);
            var startDate = endDate.AddYears(-10).AddDays((-trainSize - historyBarCount - 9) * 9 / 5);
            SetStartDate(startDate);
            SetEndDate(endDate);

            Log(String.Format("{0} - {1}", startDate, endDate));
            SetCash(10000);
            AddSecurity(SecurityType.Equity, symbol, Resolution.Minute);
            //_atr = ATR(_symbol, 20,  MovingAverageType.Simple, Resolution.Daily);

            // Construct Day Consolidator
            var dayConsolidator = new TradeBarConsolidator(TimeSpan.FromDays(1));
            dayConsolidator.DataConsolidated += OnDataDay;

            SubscriptionManager.AddConsolidator(symbol, dayConsolidator);
        }

        /// <summary>
        ///   OnData Callback (called every minute) - Prediction is made once per day, 1m after market opens
        ///   StopLoss is handled on a minutely basis.
        /// </summary>
        public void OnData(TradeBars data)
        {
            // Wait for data to become ready
            if (!dayWindow.IsReady || !featureCollection.IsReady) return;

            Func<int, double[]> inputGenerator;

            //inputGenerator = (lookback) =>
            //    Enumerable.Range(lookback, inputSize)
            //    .Select(i=> (double) ((_daywindow[i].Close - _daywindow[i+1].Close)/_daywindow[i+1].Close > 0 ? 1 : -1))
            //    .ToArray();

            //Func<int, decimal> getSL = (lookback) => wickStopWindow[lookback];

            var stopWindow = useWickStop ? wickStopWindow : atrWindow;

            Func<int, decimal> getSL = (lookback) => stopPct * stopWindow[lookback];
            Func<int, decimal> getTP = (lookback) => atrWindow[lookback] * targetPct;
            Func<int, decimal> getTrainTP = (lookback) => stopWindow[lookback] * (targetPct + targetPct * targetOverShoot);

            Func<int, double[]> simpleInputGenerator = (lookback) =>
                Enumerable.Range(lookback, historyBarCount)
                .Select(i => new double[] 
                {
                   (double) ((dayWindow[i].Close - dayWindow[i+1].Close)/dayWindow[i+1].Close)
                }).SelectMany<double[], double>(i => i)
                .ToArray();

            Func<int, double[]> complexInputGenerator = (lookback) =>
                Enumerable.Range(lookback, historyBarCount)
                .Select(i => new double[] 
                {
                   (double) ((dayWindow[i].Open - dayWindow[i+1].Close)/dayWindow[i+1].Close),
                   (double) ((dayWindow[i].Close - dayWindow[i+1].Close)/dayWindow[i+1].Close),
                   (double) ((dayWindow[i].High - dayWindow[i].Open)/dayWindow[i].Open),
                   (double) ((dayWindow[i].Open - dayWindow[i].Low)/dayWindow[i].Open),
                   (double) ((dayWindow[i].Close - dayWindow[i].Open)/dayWindow[i].Open),
                }).SelectMany<double[], double>(i => i)
                .Concat(featureCollection[lookback])
                .Concat(new double[] { 
                    (double) ((atrWindow[lookback] - dayWindow[lookback].Close) / dayWindow[lookback].Close),
                    (double) ((dayWindow[lookback].Close - slowEmaWindow[lookback])/slowEmaWindow[lookback]),
                    (double) ((fastEmaWindow[lookback]-slowEmaWindow[lookback])/slowEmaWindow[lookback]),
                    (double) ((fastEmaWindow[lookback]-fastEmaWindow[lookback+1])/fastEmaWindow[lookback+1]),
                    (double) ((middleEmaWindow[lookback]-slowEmaWindow[lookback])/slowEmaWindow[lookback]),
                    (double) ((middleEmaWindow[lookback]-middleEmaWindow[lookback+1])/middleEmaWindow[lookback+1]),
                    (double) ((slowEmaWindow[lookback]-slowEmaWindow[lookback+1])/slowEmaWindow[lookback+1])
                })
                .ToArray();

            inputGenerator = useComplexInputs ? complexInputGenerator : simpleInputGenerator;

            Func<int, IEnumerable<TradeBar>> waitWindow = (lookback) =>
                dayWindow.Skip(lookback - 1).Take(waitPeriod).OrderBy(t => t.Time);

            Func<int, int[]> complexTargetGenerator = (lookback) =>
                Enumerable.Range(lookback, trainSize)
                .Select(i => new { Window = waitWindow(i), SL = getSL(i + waitPeriod - 1), TP = getTrainTP(i + waitPeriod - 1) })
                .Select(w => new { SLPrice = w.Window.First().Open - w.SL, TPPrice = w.Window.First().Open + w.TP, Window = w.Window })
                .Select(v => v.Window.Select(b => b.High >= v.TPPrice && (b.Low > v.SLPrice || b.Close < b.Open) ? 1 : b.Low <= v.SLPrice ? -1 : 0).FirstOrDefault(r => r != 0) > 0 ? 1 : -1)
                .ToArray();

            Func<int, int[]> simpleTargetGenerator = (lookback) =>
                Enumerable.Range(lookback, trainSize)
                .Select(i => new { Window = waitWindow(i) })
                .Select(v => v.Window.Last().Close > v.Window.First().Open ? 1 : -1)
                .ToArray();

            var targetGenerator = useComplexTargets ? complexTargetGenerator : simpleTargetGenerator;

            var urProfit = Math.Round(Securities[symbol].Holdings.UnrealizedProfit, 2);

            // At Market Open use historical close data to make a prediction
            if ((data.Time.Hour == 9) && (data.Time.Minute == 31))
            {
                if (svm == null || (refreshMonthly == false && refreshQuarterly == false && refreshBiquarterly == false)
                    || data.Time.Month != dayWindow[0].Time.Month
                        && (refreshMonthly
                            || refreshQuarterly && (dayWindow[0].Time.Month - 1) % 3 == 0
                            || refreshBiquarterly && (dayWindow[0].Time.Month - 1) % 6 == 0))
                {
                    var beforeTrainingSVM = DateTime.Now;

                    var targets = targetGenerator(1);

                    targetPositives = targets.Count(t => t > 0);
                    targetNegatives = targets.Count(t => t <= 0);

                    samples.Clear();
                    for (int i = 0; i < trainSize; i++)
                    {
                        double[] returns = inputGenerator(i + waitPeriod);

                        samples.Enqueue(returns);
                    }

                    if (!printedDayWindow)
                    {
                        printedDayWindow = true;
                        Log("Day Window: " + String.Join(", ", dayWindow.Select(t => t.Close.ToString()).ToArray()));
                    }
                    double[][] inputs = samples.ToArray();

                    var actualInputSize = inputs[0].Length;

                    if (inputs.Length != targets.Length)
                        Log(String.Format("Inputs: {0} Targets: {1}", inputs.Length, targets.Length));

                    if (inputs != null && printedInputs == false)
                    {
                        printedInputs = true;
                        Log(String.Format("Inputs: {0}", inputs.Length));


                        foreach (var input in inputs.Take(6))
                        {
                            foreach (var value in input)
                            {
                                Log("" + value);
                            }

                            Log("--------------");
                        }

                        return;
                    }

                    ISupportVectorMachineLearning teacher;

                    if (useLinearSVM == false)
                    {
                        //var kernel = Gaussian.Estimate(inputs);
                        var kernel = Laplacian.Estimate(inputs);
                        var sigma = kernel.Sigma;
                        svm = new KernelSupportVectorMachine(kernel, actualInputSize);
                        teacher = new SequentialMinimalOptimization(svm, inputs, targets);
                        ((SequentialMinimalOptimization)teacher).UseComplexityHeuristic = true;
                    }
                    else
                    {
                        svm = new SupportVectorMachine(inputs: actualInputSize);
                        teacher = new LinearCoordinateDescent(svm, inputs, targets);
                        ((LinearCoordinateDescent)teacher).UseComplexityHeuristic = true;
                    }

                    error = teacher.Run(true);

                    trainingTimes.Enqueue(DateTime.Now - beforeTrainingSVM);

                    if (printedError == false)
                    {
                        printedError = true;

                        Log(string.Format("Error: {0} Positives: {1} Negatives: {2}", error, targetPositives, targetNegatives));
                    }
                }

                var beforeCompute = DateTime.Now;

                double[] currentInputs = inputGenerator(0);
                double output = svm.Compute(currentInputs);

                computeTimes.Enqueue(DateTime.Now - beforeCompute);

                var supportVectors = svm.SupportVectors;

                if (supportVectors != null && printedSupportVectors == false)
                {
                    printedSupportVectors = true;
                    Log(String.Format("Support Vectors: {0}", supportVectors.Length));


                    foreach (var value in supportVectors[0])
                    {
                        Log("" + value);
                    }

                    Log("--------------");

                    return;
                }

                if (output > 0)
                {
                    var newStopPrice = data[symbol].Close - getSL(0);
                    var newTargetPrice = data[symbol].Close + getTP(0);

                    if (Securities[symbol].Holdings.Quantity != 0)
                    {
                        stopPrice = newStopPrice > stopPrice ? newStopPrice : stopPrice;
                        targetPrice = expandTakeProfit && newTargetPrice > targetPrice ? newTargetPrice : targetPrice;
                    }
                    else
                    {
                        stopPrice = newStopPrice;
                        targetPrice = newTargetPrice;
                    }
                    LogLater(String.Format("O: {0:0.##} E: {1:0.##} P: {2} N: {3} SL: {4:0.##} TP: {5:0.##}", Math.Round(output, 2), Math.Round(error, 2), targetPositives, targetNegatives, stopPrice, targetPrice));
                    // update stoploss
                    enterDate = DateTime.Now;

                    // Go Long
                    if (Securities[symbol].Holdings.Quantity == 0)
                    {
                        Order(symbol, Portfolio.TotalPortfolioValue / data[symbol].Close * tradePct);
                        LogLater("Buy Shares: " + Securities[symbol].Holdings.Quantity);
                    }
                    else if (Securities[symbol].Holdings.Quantity < 0)
                    {
                        // if we are in a short position: calculate how many shares needed to go long
                        Order(symbol, ((decimal)-Securities[symbol].Holdings.Quantity) + (Portfolio.TotalPortfolioValue / data[symbol].Close * tradePct));
                        LogLater("Buy Shares: " + Securities[symbol].Holdings.Quantity);
                    }
                }
                else if ((output < 0 && Math.Round((DateTime.Now - enterDate).TotalDays / waitPeriod) >= 1)
                        && Securities[symbol].Holdings.Quantity != 0)
                {
                    // Exit Market

                    Liquidate(symbol);
                    LogLater("Exiting Market:" + urProfit);
                }
            }

            // Handle Stop Loss
            if (Securities[symbol].Holdings.Quantity != 0)
            {
                if (Securities[symbol].Holdings.IsLong)
                {
                    if (data[symbol].Low <= stopPrice)
                    {
                        Liquidate(symbol);
                        LogLater("StopLoss: " + urProfit);
                    }
                    if (data[symbol].High >= targetPrice)
                    {
                        Liquidate(symbol);
                        LogLater("TakeProfit: " + urProfit);
                    }
                }
                if (Securities[symbol].Holdings.IsShort)
                {
                    if (data[symbol].High >= stopPrice)
                    {
                        Liquidate(symbol);
                        Log("Hit StopLoss: " + data[symbol].High);
                    }
                }
            }
        }

        /// <summary>
        ///   OnDataDay Callback (called every day) - Used to build the daily history (rolling window) of close prices
        /// </summary>
        private void OnDataDay(object sender, TradeBar consolidated)
        {
            //Inject data into the rolling window.
            if (consolidated.Close > consolidated.Open)
                wickStopPoint.Update(consolidated);

            closingPrice.Update(consolidated);
            featureCollection.Update(consolidated);
            dayWindow.Add(consolidated);

            if (IndicatorsReady())
            {
                wickStopWindow.Add(wickStop);
            }
        }

        private bool IndicatorsReady()
        {
            return wickStop.IsReady;
        }

    }

    public abstract class RollingWindowIndicator<T> : RollingWindow<decimal>
    {
        #region Constructors

        public RollingWindowIndicator(int windowSize, bool includeInFeatureCollection = true)
            : base(windowSize)
        {
            this.IncludeInFeatureCollection = includeInFeatureCollection;
        }

        #endregion

        #region Properties

        public bool IncludeInFeatureCollection
        {
            get;
            set;
        }

        #endregion

        #region Methods

        public abstract void Update(T data);

        #endregion
    }

    public class IndicatorFeatureCollection
    {
        #region Fields

        private List<RollingWindowIndicator<TradeBar>> indicatorList = new List<RollingWindowIndicator<TradeBar>>();

        #endregion

        #region Constructors

        public IndicatorFeatureCollection(params RollingWindowIndicator<TradeBar>[] initialList)
        {
            indicatorList.AddRange(initialList);
        }

        #endregion

        #region Properties

        public bool IsReady
        {
            get { return indicatorList.All(i => i.IsReady); }
        }

        public double[] this[int index]
        {
            get { return indicatorList.Where(i => i.IncludeInFeatureCollection).Select(i => (double)i[index]).ToArray(); }
        }

        #endregion

        #region Methods

        public void Add(RollingWindowIndicator<TradeBar> indicator)
        {
            indicatorList.Add(indicator);
        }

        public void Update(TradeBar data)
        {
            foreach (var indicator in indicatorList)
            {
                indicator.Update(data);
            }
        }

        #endregion
    }

    public class TradeBarIndicatorRollingWindow : RollingWindowIndicator<TradeBar>
    {
        IndicatorBase<TradeBar> indicator;

        public TradeBarIndicatorRollingWindow(
            int windowSize,
            IndicatorBase<TradeBar> indicator,
            bool includeInFeatureCollection = true)
            : base(windowSize, includeInFeatureCollection)
        {
            this.indicator = indicator;
        }

        public override void Update(TradeBar data)
        {
            indicator.Update(data);

            if (indicator.IsReady)
                Add(indicator);
        }
    }

    public class DataPointIndicatorRollingWindow : RollingWindowIndicator<TradeBar>
    {
        IndicatorBase<IndicatorDataPoint> indicator;
        Func<TradeBar, decimal> projection;

        public DataPointIndicatorRollingWindow(
            int windowSize,
            IndicatorBase<IndicatorDataPoint> indicator,
            Func<TradeBar, decimal> projection = null,
            bool includeInFeatureCollection = true)
            : base(windowSize, includeInFeatureCollection)
        {
            this.indicator = indicator;

            this.projection = projection ?? (t => t.Value);
        }

        public override void Update(TradeBar data)
        {
            indicator.Update(data.Time, projection(data));

            if (indicator.IsReady)
                Add(indicator);
        }
    }
}