#region Using declarations
using NinjaTrader.Cbi;
using NinjaTrader.NinjaScript.Indicators;
using System;
using System.ComponentModel.DataAnnotations;
#endregion

//This namespace holds Strategies in this folder and is required. Do not change it. 
namespace NinjaTrader.NinjaScript.Strategies
{
    public class PairsTradingBot : Strategy
    {
        private const int FirstSymbolBars = 0;
        private const int SecondSymbolBars = 1;

        private const double EntryZScore = 2.0;
        private const double ExitZScore = 0.5;

        private double spread;
        private double meanSpread;
        private double stdDevSpread;
        private Series<double> spreadSeries;

        protected override void OnStateChange()
        {
            if (State == State.SetDefaults)
            {
                Description = @"Simple pairs trading strategy";
                Name = "Pairs Trading Bot";
                Calculate = Calculate.OnBarClose;
                EntriesPerDirection = 1;
                EntryHandling = EntryHandling.AllEntries;
                IsExitOnSessionCloseStrategy = true;
                ExitOnSessionCloseSeconds = 30;
                IsFillLimitOnTouch = false;
                MaximumBarsLookBack = MaximumBarsLookBack.TwoHundredFiftySix;
                OrderFillResolution = OrderFillResolution.Standard;
                Slippage = 0;
                StartBehavior = StartBehavior.WaitUntilFlat;
                TimeInForce = TimeInForce.Gtc;
                TraceOrders = false;
                RealtimeErrorHandling = RealtimeErrorHandling.StopCancelClose;
                StopTargetHandling = StopTargetHandling.PerEntryExecution;
                BarsRequiredToTrade = 20;
                IsInstantiatedOnEachOptimizationIteration = true;

                SecondSymbol = "AAPL";
                LookbackPeriod = 20;
            }
            else if (State == State.Configure)
            {
                // First symbol in the pair is the primary data series.
                AddDataSeries(SecondSymbol, BarsPeriod);
            }
            else if (State == State.DataLoaded)
            {
                spreadSeries = new Series<double>(this);
            }
        }

        protected override void OnBarUpdate()
        {
            if (BarsInProgress != 0)
                return;

            double instrument1Price = Closes[FirstSymbolBars][0];
            double instrument2Price = Closes[SecondSymbolBars][0];

            spread = instrument1Price - instrument2Price;
            spreadSeries[0] = spread;

            if (CurrentBar < LookbackPeriod)
                return;

            meanSpread = SMA(spreadSeries, LookbackPeriod)[0];
            stdDevSpread = StdDev(spreadSeries, LookbackPeriod)[0];

            double zScore = (spread - meanSpread) / stdDevSpread;

            if (zScore > EntryZScore)
            {
                if (Position.MarketPosition != MarketPosition.Short)
                {
                    EnterShort(FirstSymbolBars, 1, "Symbol 1 Short");
                    EnterLong(SecondSymbolBars, 1, "Symbol 2 Short");
                }
            }
            else if (zScore < -EntryZScore)
            {
                if (Position.MarketPosition != MarketPosition.Long)
                {
                    EnterLong(FirstSymbolBars, 1, "Symbol 1 Long");
                    EnterShort(SecondSymbolBars, 1, "Symbol 2 Long");
                }
            }
            else if (Math.Abs(zScore) < ExitZScore)
            {
                ExitLong();
                ExitShort();
            }
        }

        public override string DisplayName
        {
            get { return Name; }
        }

        [Display(Name = "Second Symbol", GroupName = "Pairs Trading Bot", Order = 1)]
        public string SecondSymbol { get; set; }

        [Range(1, int.MaxValue), NinjaScriptProperty]
        [Display(Name = "Lookback Period", GroupName = "Pairs Trading Bot", Order = 2)]
        public int LookbackPeriod { get; set; }
    }
}