#region Using declarations using System; using NinjaTrader.Cbi; using NinjaTrader.Core.FloatingPoint; using NinjaTrader.NinjaScript.Indicators.TDU; using System.ComponentModel.DataAnnotations; using NinjaTrader.Data; #endregion namespace NinjaTrader.NinjaScript.Strategies { /// /// Sample automated strategy for the TDU PATS Indicator /// https://tradedevils-indicators.com/products/tdu-price-action /// public class PATSExampleStrategy : Strategy { private enum TduOrderState { Idle, Waiting, Filled, } private TDUPriceAction _patsIndicator; private int _lastOrderBar; private Order _runnerOrder; private Order _scalpOrder; private double _trailingStop; private double _trailPrice; private double _entryPrice; private int _breakEvenBar; private TduOrderState _state = TduOrderState.Idle; protected override void OnStateChange() { if (State == State.SetDefaults) { Description = @"PATS Example Strategy."; Name = "PATS Example Strategy"; Calculate = Calculate.OnEachTick; EntriesPerDirection = 2; EntryHandling = EntryHandling.AllEntries; IsExitOnSessionCloseStrategy = false; ExitOnSessionCloseSeconds = 30; IsFillLimitOnTouch = false; MaximumBarsLookBack = MaximumBarsLookBack.Infinite; Slippage = 0; StartBehavior = StartBehavior.WaitUntilFlat; TimeInForce = TimeInForce.Gtc; TraceOrders = false; RealtimeErrorHandling = RealtimeErrorHandling.IgnoreAllErrors; StopTargetHandling = StopTargetHandling.PerEntryExecution; BarsRequiredToTrade = 10; IsInstantiatedOnEachOptimizationIteration = true; PositionType = TDUPATSPositionSizing.Contracts; MinRunnerContracts = 1; MaxContracts = 20; FixedContracts = 3; FixedAmount = 200; PercentCapital = 2; ScalpsPercentage = 66; TargetTicks = 4; StopLossTicks = 1; MaxStopLossTicks = 300; } else if (State == State.Configure) { // needed to get intrabar order fills AddDataSeries(Data.BarsPeriodType.Tick, 1); } else if (State == State.DataLoaded) { // add the PATS indicator _patsIndicator = TDUPriceAction(Close); _patsIndicator.MaximumBarsLookBack = MaximumBarsLookBack.Infinite; _patsIndicator.LegCountMethod = TDUPatsRules.Mack; _patsIndicator.PositionType = PositionType; _patsIndicator.FixedContracts = FixedContracts; _patsIndicator.FixedAmount = FixedAmount; _patsIndicator.PercentCapital = PercentCapital; _patsIndicator.ScalpsPercentage = ScalpsPercentage; _patsIndicator.MinRunnerContracts = MinRunnerContracts; _patsIndicator.MaxContracts = MaxContracts; _patsIndicator.CashValue = Account.Get(AccountItem.CashValue, Currency.UsDollar); _patsIndicator.TargetTicks = TargetTicks; _patsIndicator.StopLossTicks = StopLossTicks; _patsIndicator.MaxStopLossTicks = MaxStopLossTicks; _patsIndicator.ShowOrderPanel = false; AddChartIndicator(_patsIndicator); } } protected override void OnBarUpdate() { if (CurrentBar < 10) { return; } if (BarsInProgress != 0) { return; } //S Print(string.Format("#{0} {1}", CurrentBar, Position.MarketPosition)); CreateOrders(); ManageOrders(); } /// /// Create new orders when the PATS indicator sees a 2nd entry long or short /// private void CreateOrders() { if (_state != TduOrderState.Idle) return; if (Position.MarketPosition != MarketPosition.Flat) { return; } // Check for 2EL long if (_patsIndicator.SignalLong[0].ApproxCompare(1) == 0) { // We don't want multiple trades / bar if (CurrentBar > _lastOrderBar + 1) { var contracts = (int)_patsIndicator.ContractsLong[0]; var stopLoss = _patsIndicator.StopLossLong[0]; _trailingStop = stopLoss; _trailPrice = Math.Max(High[0], High[1]); _entryPrice = _trailPrice + TickSize; if (_entryPrice > GetCurrentAsk() + TickSize) { Print("-"); Print(string.Format("#{0} 2EL-S O:{1} SL:{2} ask:{3}", CurrentBar, _entryPrice, _trailingStop, GetCurrentAsk())); _scalpOrder = EnterLongStopMarket(0, true, GetNumberOfScalpContracts(contracts), _entryPrice, "Scalp"); _runnerOrder = null; if (_scalpOrder != null) { var runnerContracts = GetNumberRunnerContracts(contracts); if (runnerContracts > 0) { Print(string.Format("#{0} 2EL-R O:{1} SL:{2} ask:{3}", CurrentBar, _entryPrice, _trailingStop, GetCurrentAsk())); _runnerOrder = EnterLongStopMarket(0, true, runnerContracts, _entryPrice, "Runner"); } _state = TduOrderState.Waiting; _lastOrderBar = CurrentBar; } } return; } } // Check for 2ES short if (_patsIndicator.SignalShort[0].ApproxCompare(-1) == 0) { // We don't want multiple trades / bar if (CurrentBar > _lastOrderBar + 1) { var contracts = (int)_patsIndicator.ContractsShort[0]; var stopLoss = _patsIndicator.StopLossShort[0]; _trailingStop = stopLoss; _trailPrice = Math.Min(Low[0], Low[1]); _entryPrice = _trailPrice - TickSize; if (_entryPrice < GetCurrentBid() - TickSize) { Print("-"); Print(string.Format("#{0} 2ES-S O:{1} SL:{2} ask:{3}, {4}", CurrentBar, _entryPrice, _trailingStop, GetCurrentAsk(), High[0])); _scalpOrder = EnterShortStopMarket(0, true, GetNumberOfScalpContracts(contracts), _entryPrice, "Scalp"); _runnerOrder = null; if (_scalpOrder != null) { var runnerContracts = GetNumberRunnerContracts(contracts); if (runnerContracts > 0) { Print(string.Format("#{0} 2ES-R O:{1} SL:{2} ask:{3}, {4}", CurrentBar, _entryPrice, _trailingStop, GetCurrentAsk(), High[0])); _runnerOrder = EnterShortStopMarket(0, true, runnerContracts, _entryPrice, "Runner"); } _state = TduOrderState.Waiting; _lastOrderBar = CurrentBar; } } return; } } } /// /// Manages the runners by first moving the stop to break even when 4 ticks in profit /// and then trailing the previous candle low/hi /// private void ManageOrders() { if (_state == TduOrderState.Idle) return; if (_scalpOrder == null && _runnerOrder == null) { _state = TduOrderState.Idle; return; } if (_state == TduOrderState.Waiting) { if (Position.MarketPosition != MarketPosition.Flat) { if (_scalpOrder.Filled > 0 && (_runnerOrder == null || (_runnerOrder != null & _runnerOrder.Filled > 0))) { // When order is filled, set the stoploss and targets _state = TduOrderState.Filled; Print(string.Format("#{0} {1} filled O:{2} SL:{3}", CurrentBar, _scalpOrder.IsLong ? "2EL-S" : "2ES-S", _scalpOrder.AverageFillPrice, GetDistanceInTicks(_trailingStop, _scalpOrder.AverageFillPrice))); SetStopLoss("Scalp", CalculationMode.Ticks, GetDistanceInTicks(_trailingStop, _scalpOrder.AverageFillPrice), false); SetProfitTarget("Scalp", CalculationMode.Ticks, TargetTicks); if (_runnerOrder != null) { Print(string.Format("#{0} {1} filled O:{2} SL:{3}", CurrentBar, _runnerOrder.IsLong ? "2EL-R" : "2ES-R", _runnerOrder.AverageFillPrice, GetDistanceInTicks(_trailingStop, _runnerOrder.AverageFillPrice))); SetStopLoss("Runner", CalculationMode.Ticks, GetDistanceInTicks(_trailingStop, _runnerOrder.AverageFillPrice), false); } return; } } if (Position.MarketPosition == MarketPosition.Flat) { if (_scalpOrder != null && _scalpOrder.Filled > 0) { _scalpOrder = null; if (_runnerOrder != null && _runnerOrder.Filled > 0) { _runnerOrder = null; } _state = TduOrderState.Idle; return; } } // trail entry if (_scalpOrder.Filled == 0) { // if order is not filled yet, then trail the entry price if (CurrentBar != _lastOrderBar && IsFirstTickOfBar && BarsInProgress == 0) { // longs if (_scalpOrder.IsLong && Low[1] < _trailPrice) { // set new entry price for long order _lastOrderBar = CurrentBar; _trailPrice = High[1]; _entryPrice = High[1] + TickSize; _trailingStop = Math.Min(Low[0], Low[1]) - TickSize; if (_entryPrice.ApproxCompare(_scalpOrder.StopPrice) != 0 && _entryPrice > GetCurrentAsk() + TickSize) { Print(string.Format("#{0} 2EL-S Trail Entry O:{1}->{2} SL:{3}", CurrentBar, _scalpOrder.StopPrice, _entryPrice, _trailingStop)); _scalpOrder.StopPriceChanged = _entryPrice; ChangeOrder(_scalpOrder, _scalpOrder.Quantity, _scalpOrder.LimitPrice, _scalpOrder.StopPriceChanged); if (_runnerOrder != null) { Print(string.Format("#{0} 2EL-R Trail Entry O:{1}->{2} SL:{3}", CurrentBar, _scalpOrder.StopPrice, _entryPrice, _trailingStop)); _runnerOrder.StopPriceChanged = _entryPrice; ChangeOrder(_runnerOrder, _runnerOrder.Quantity, _runnerOrder.LimitPrice, _runnerOrder.StopPriceChanged); } } return; } //shorts if (_scalpOrder.IsShort && High[1] > _trailPrice) { // set new entry price for short order _lastOrderBar = CurrentBar; _trailPrice = Low[1]; _entryPrice = Low[1] - TickSize; _trailingStop = Math.Max(High[0], High[1]) + TickSize; if (_entryPrice.ApproxCompare(_scalpOrder.StopPrice) != 0 && _entryPrice < GetCurrentBid() - TickSize) { Print(string.Format("#{0} 2ES-S Trail Entry O:{1}->{2} SL:{3}", CurrentBar, _scalpOrder.StopPrice, _entryPrice, _trailingStop)); _scalpOrder.StopPriceChanged = _entryPrice; ChangeOrder(_scalpOrder, _scalpOrder.Quantity, _scalpOrder.LimitPrice, _scalpOrder.StopPriceChanged); if (_runnerOrder != null) { Print(string.Format("#{0} 2ES-R Trail Entry O:{1}->{2} SL:{3}", CurrentBar, _scalpOrder.StopPrice, _entryPrice, _trailingStop)); _runnerOrder.StopPriceChanged = _entryPrice; ChangeOrder(_runnerOrder, _runnerOrder.Quantity, _runnerOrder.LimitPrice, _runnerOrder.StopPriceChanged); } } return; } } } } // if order is filled trail stops if (_state == TduOrderState.Filled) { if (_scalpOrder != null && _scalpOrder.IsLong && _scalpOrder.Filled > 0) { // did we hit the stoloss ? if (Closes[0][0] <= _trailingStop) { // stoploss hit, close long position Print(string.Format("#{0} 2EL-S SL hit {1} {2} {3} ", CurrentBar, Times[0][0], Closes[0][0], _trailingStop)); _runnerOrder = null; _scalpOrder = null; _state = TduOrderState.Idle; ExitLong(); return; } if (_runnerOrder == null && Position.MarketPosition == MarketPosition.Flat) { Print(string.Format("#{0} 2ES-S exit {1} {2} {3} ", CurrentBar, Times[0][0], Closes[0][0], _trailingStop)); _runnerOrder = null; _scalpOrder = null; _state = TduOrderState.Idle; ExitLong(); return; } if (_runnerOrder != null) { var breakEvenPrice = _runnerOrder.AverageFillPrice; var firstTarget = _runnerOrder.AverageFillPrice + 4 * TickSize; if (Closes[0][0] > firstTarget) { if (_trailingStop < breakEvenPrice) { // move stop to break even after 4 ticks Print(string.Format("#{0} 2EL-R move stop to BE {1}", CurrentBar, _trailingStop)); _trailingStop = breakEvenPrice; _breakEvenBar = CurrentBar; SetStopLoss("Runner", CalculationMode.Ticks, GetDistanceInTicks(_trailingStop, _runnerOrder.AverageFillPrice), false); } else if (CurrentBar > _breakEvenBar) { var previousCandleLow = Low[1]; if (previousCandleLow > _trailingStop) { // trail previous candle low _trailingStop = previousCandleLow; Print(string.Format("#{0} 2EL-R trail stop to {1}", CurrentBar, _trailingStop)); SetStopLoss("Runner", CalculationMode.Ticks, GetDistanceInTicks(_trailingStop, _runnerOrder.AverageFillPrice), false); } } } } } if (_scalpOrder != null && _scalpOrder.IsShort && _scalpOrder.Filled > 0) { // did we hit the stoloss ? if (Closes[0][0] >= _trailingStop) { // stoploss hit, close short position Print(string.Format("#{0} 2EL SL hit {1} {2} {3} ", CurrentBar, Times[0][0], Closes[0][0], _trailingStop)); _runnerOrder = null; _scalpOrder = null; _state = TduOrderState.Idle; ExitShort(); return; } if (_runnerOrder == null && Position.MarketPosition == MarketPosition.Flat) { Print(string.Format("#{0} 2ES exit {1} {2} {3} ", CurrentBar, Times[0][0], Closes[0][0], _trailingStop)); _runnerOrder = null; _scalpOrder = null; _state = TduOrderState.Idle; ExitShort(); return; } if (_runnerOrder != null) { var breakEvenPrice = _runnerOrder.AverageFillPrice; var firstTarget = _runnerOrder.AverageFillPrice - 4 * TickSize; if (Closes[0][0] < firstTarget) { if (_trailingStop > breakEvenPrice) { // move stop to break even after 4 ticks _trailingStop = breakEvenPrice; _breakEvenBar = CurrentBar; Print(string.Format("#{0} 2ES-R move stop to BE {1}", CurrentBar, _trailingStop)); SetStopLoss("Runner", CalculationMode.Ticks, GetDistanceInTicks(_trailingStop, _runnerOrder.AverageFillPrice), false); } else if (CurrentBar > _breakEvenBar) { var previousCandleHigh = High[1]; if (previousCandleHigh < _trailingStop) { // trail previous candle low _trailingStop = previousCandleHigh; Print(string.Format("#{0} 2ES-R trail stop to {1}", CurrentBar, _trailingStop)); SetStopLoss("Runner", CalculationMode.Ticks, GetDistanceInTicks(_trailingStop, _runnerOrder.AverageFillPrice), false); } } } } } } } private double GetDistanceInTicks(double price1, double price2) { return (int)(Math.Abs(price1 - price2) / TickSize); } /// /// Gets the number of contracts to use for the runners /// /// The total number of contracts private int GetNumberRunnerContracts(int totalContracts) { return totalContracts - GetNumberOfScalpContracts(totalContracts); } /// /// Gets the number of contracts to use for the scalps /// /// The total number of contracts private int GetNumberOfScalpContracts(int totalContracts) { var onePercent = totalContracts / 100d; var scalps = (int)(0.5 + ScalpsPercentage * onePercent); var runners = totalContracts - scalps; if (runners < MinRunnerContracts && totalContracts > MinRunnerContracts) { runners = MinRunnerContracts; scalps = totalContracts - runners; } return scalps; } [Display(ResourceType = typeof(Custom.Resource), Name = "Position sizing type", GroupName = "01. Position Sizing", Order = 1)] public TDUPATSPositionSizing PositionType { get; set; } [Display(ResourceType = typeof(Custom.Resource), Name = "Fixed # contracts", GroupName = "01. Position Sizing", Order = 2)] public int FixedContracts { get; set; } [Display(ResourceType = typeof(Custom.Resource), Name = "Fixed $ amount", GroupName = "01. Position Sizing", Order = 3)] public int FixedAmount { get; set; } [Display(ResourceType = typeof(Custom.Resource), Name = "% of account", GroupName = "01. Position Sizing", Order = 4)] public double PercentCapital { get; set; } [Display(ResourceType = typeof(Custom.Resource), Name = "Contracts Scalps %", GroupName = "01. Position Sizing", Order = 5)] public double ScalpsPercentage { get; set; } [Display(ResourceType = typeof(Custom.Resource), Name = "Max # contracts", GroupName = "01. Position Sizing", Order = 6)] public int MaxContracts { get; set; } [Display(ResourceType = typeof(Custom.Resource), Name = "Min. # runner contracts", GroupName = "01. Position Sizing", Order = 7)] public int MinRunnerContracts { get; set; } [Display(ResourceType = typeof(Custom.Resource), Name = "Target (ticks)", GroupName = "01. Position Sizing", Order = 9)] public int TargetTicks { get; set; } [Display(ResourceType = typeof(Custom.Resource), Name = "Stoploss # ticks above/below signal bar", GroupName = "01. Position Sizing", Order = 10)] public int StopLossTicks { get; set; } [Display(ResourceType = typeof(Custom.Resource), Name = "Max. stoploss (ticks)", GroupName = "01. Position Sizing", Order = 12)] public int MaxStopLossTicks { get; set; } } }