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