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