"Inside Bar + ZigZag automated strategy - Help with code"

Can someone help me create an automated trading strategy/bot for NinjaTrader 8 based on the following rules?

I want the strategy to run on a 5-minute chart and use Inside Bars together with a ZigZag indicator to determine the market trend.

Indicators

  • Timeframe: 5-minute chart

  • ZigZag: Period = 4

  • Overbought: 65

  • Oversold: 35

  • The ZigZag should identify Higher Highs / Higher Lows for an uptrend and Lower Highs / Lower Lows for a downtrend.

Long / Buy Entry

Only take long trades when the ZigZag is in an uptrend.

The setup is:

  1. An Inside Bar forms.

  2. Identify the candle immediately before the Inside Bar.

  3. The strategy should enter 1 contract long when a subsequent candle breaks above the high of that pre-Inside-Bar candle.

  4. No long trade should be taken if the ZigZag is not indicating an uptrend.

Short / Sell Entry

Only take short trades when the ZigZag is in a downtrend.

The setup is:

  1. An Inside Bar forms.

  2. Identify the candle immediately before the Inside Bar.

  3. The strategy should enter 1 contract short when a subsequent candle breaks below the low of that pre-Inside-Bar candle.

  4. No short trade should be taken if the ZigZag is not indicating a downtrend.

Latest Inside Bar Takes Priority

This is important:

The strategy should always use the most recently completed Inside Bar as the active setup.

For example:

  • Inside Bar #1 forms and becomes the active setup.

  • If the breakout conditions are not met and a new Inside Bar #2 forms, Inside Bar #2 immediately becomes the new active setup.

  • Inside Bar #1 must then be completely discarded.

  • The breakout reference must also be updated to the candle immediately preceding Inside Bar #2.

  • If Inside Bar #3 subsequently forms before a trade is triggered, #3 replaces #2, and so on.

In other words, there should never be multiple Inside Bar setups waiting simultaneously. The bot should only monitor the latest Inside Bar.

If the latest Inside Bar does not eventually produce a valid breakout, the strategy should simply wait for the next Inside Bar and make that one the new active setup.

The strategy should not go back to an older Inside Bar once it has been replaced.

Trade Management

For every trade:

  • Position size: 1 contract

  • Profit Target: +$200

  • Stop Loss: -$400

  • Only one position should be active at a time.

Important Implementation Requirements

I would like the strategy to be fully automated in NinjaTrader 8, preferably as a NinjaScript strategy that I can compile and run in the Strategy Analyzer and/or live in NinjaTrader.

I would also like the code to avoid look-ahead bias and repainting issues, particularly with the ZigZag indicator.

Could someone provide the actual NinjaTrader 8 NinjaScript code and explain how you would implement:

  1. Inside Bar detection

  2. Tracking only the latest Inside Bar

  3. Automatically replacing an old Inside Bar when a new one forms

  4. Identifying the candle immediately preceding the active Inside Bar

  5. HH/HL vs. LH/LL ZigZag trend detection

  6. Breakout entries

  7. $200 profit target and $400 stop loss

  8. One-contract position sizing

  9. Proper handling of ZigZag confirmation so the strategy does not use future/repainting information

The key requirement is that the newest Inside Bar always becomes the only active setup until another Inside Bar replaces it or a trade is triggered.

I tried it and came up with this, but a lot of errors and can’t get them away:

#region Using declarations
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Cbi;
using Ninja.Cbi;
using Ninja.Strategies;
#endregion

namespace Ninja.Strategies
{
public class InsideBarZigZag : Strategy
{
private double takeProfitTicks;
private double stopLossTicks;
private int contractSize;
private int zigzagPeriod;

	private int insideBarIndex;
	private int lastInsideBarIndex;
	private bool isLongPosition;
	private bool isShortPosition;
	private double highBeforeInsideBar;
	private double lowBeforeInsideBar;
	private bool zigzagUptrend;
	private double lastZigzagHigh;
	private double lastZigzagLow;

	protected override void OnStateChange()
	{
		if (State == State.SetDefaults)
		{
			Description									= @"Inside Bar + ZigZag Strategy (Latest IB)";
			Name										= "InsideBarZigZag";
			Calculate									= Calculate.OnBarClose;
			EntriesPerDirection							= 1;
			EntryHandling								= EntryHandling.AllEntries;
			IsExitOnSessionCloseStrategy				= true;
			ExitOnSessionCloseSeconds					= 30;
			IsFillLimitOnFlatReject						= true;
			AllowMimicOrders							= false;
			MaxDrawdown									= 20;
			MaxIntrabarOrderHandling					= MaxIntrabarOrderHandling.AllowMultipleNettingOnly;
			OrderFillResolution							= OrderFillResolution.Standard;
			Slippage									= 0;
			StartBehavior								= StartBehavior.WaitUntilFlat;
			TimeInForce									= TimeInForce.Day;
			TraceOrders									= true;
			RealtimeErrorHandling						= RealtimeErrorHandling.StopCancelClose;
			StopTargetHandling							= StopTargetHandling.PerEntryExecution;
			BarsRequiredToTrade							= 10;
			
			takeProfitTicks = 20;
			stopLossTicks = 40;
			contractSize = 1;
			zigzagPeriod = 4;
		}
		else if (State == State.Configure)
		{
		}
		else if (State == State.Active)
		{
			insideBarIndex = -1;
			lastInsideBarIndex = -1;
			isLongPosition = false;
			isShortPosition = false;
		}
	}

	protected override void OnBarUpdate()
	{
		if (CurrentBar < 5)
			return;

		UpdateZigZagTrend();
		
		// ALWAYS CHECK FOR INSIDE BAR AND UPDATE TO LATEST
		bool insideBarDetected = IsInsideBar();
		
		if (insideBarDetected)
		{
			// NEW INSIDE BAR DETECTED - UPDATE TO LATEST (replaces previous)
			if (CurrentBar >= 2 && CurrentBar != lastInsideBarIndex)
			{
				highBeforeInsideBar = High[2];
				lowBeforeInsideBar = Low[2];
				insideBarIndex = CurrentBar;
				lastInsideBarIndex = CurrentBar;
				
				Print("=== NEW Inside Bar at bar " + CurrentBar + " | H=" + highBeforeInsideBar.ToString("F2") + " | L=" + lowBeforeInsideBar.ToString("F2") + " ===");
			}
		}

		// LONG EXECUTION - USE LATEST INSIDE BAR
		if (!isLongPosition && !isShortPosition && insideBarIndex >= 0 && zigzagUptrend)
		{
			// Only execute if we have a valid inside bar stored and current bar is AFTER the inside bar
			if (insideBarIndex < CurrentBar)
			{
				if (Close[0] > highBeforeInsideBar)
				{
					Print(">>> BUY SIGNAL at bar " + CurrentBar + " | Entry Price: " + Close[0].ToString("F2") + " | Above IB High: " + highBeforeInsideBar.ToString("F2"));
					Buy("BUY", contractSize);
					
					isLongPosition = true;
					
					double tpPrice = Close[0] + takeProfitTicks * TickSize;
					double slPrice = Close[0] - stopLossTicks * TickSize;
					
					Print("    TP Set at: " + tpPrice.ToString("F2") + " | SL Set at: " + slPrice.ToString("F2"));
					
					SellLimit(contractSize, tpPrice, "TP_Long");
					SellStop(contractSize, slPrice, "SL_Long");
					
					insideBarIndex = -1;  // Reset - wait for next inside bar
				}
			}
		}

		// SHORT EXECUTION - USE LATEST INSIDE BAR
		if (!isLongPosition && !isShortPosition && insideBarIndex >= 0 && !zigzagUptrend)
		{
			// Only execute if we have a valid inside bar stored and current bar is AFTER the inside bar
			if (insideBarIndex < CurrentBar)
			{
				if (Close[0] < lowBeforeInsideBar)
				{
					Print(">>> SELL SIGNAL at bar " + CurrentBar + " | Entry Price: " + Close[0].ToString("F2") + " | Below IB Low: " + lowBeforeInsideBar.ToString("F2"));
					Sell("SELL", contractSize);
					
					isShortPosition = true;
					
					double tpPrice = Close[0] - takeProfitTicks * TickSize;
					double slPrice = Close[0] + stopLossTicks * TickSize;
					
					Print("    TP Set at: " + tpPrice.ToString("F2") + " | SL Set at: " + slPrice.ToString("F2"));
					
					BuyLimit(contractSize, tpPrice, "TP_Short");
					BuyStop(contractSize, slPrice, "SL_Short");
					
					insideBarIndex = -1;  // Reset - wait for next inside bar
				}
			}
		}

		// POSITION STATUS
		if (Position.MarketPosition == MarketPosition.Long)
			isLongPosition = true;
		else if (Position.MarketPosition == MarketPosition.Short)
			isShortPosition = true;
		else
		{
			isLongPosition = false;
			isShortPosition = false;
		}
	}

	private bool IsInsideBar()
	{
		if (CurrentBar < 2)
			return false;

		return (High[0] < High[1] && Low[0] > Low[1]);
	}

	private void UpdateZigZagTrend()
	{
		if (CurrentBar < zigzagPeriod)
			return;

		double recentHigh = High[0];
		double recentLow = Low[0];

		for (int i = 1; i < zigzagPeriod; i++)
		{
			if (High[i] > recentHigh)
				recentHigh = High[i];
			if (Low[i] < recentLow)
				recentLow = Low[i];
		}

		if (recentHigh > lastZigzagHigh && recentLow > lastZigzagLow)
		{
			zigzagUptrend = true;
		}
		else if (recentHigh < lastZigzagHigh && recentLow < lastZigzagLow)
		{
			zigzagUptrend = false;
		}

		lastZigzagHigh = recentHigh;
		lastZigzagLow = recentLow;
	}
}

}

Hello SJanz, i write some fixes fast, but the strategy needs some many fix in it code, i gonna explain you, write me a mesagge

#region Using declarations
using NinjaTrader.Cbi;
using NinjaTrader.Gui;
using NinjaTrader.NinjaScript.Indicators;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Windows.Media;
#endregion

namespace NinjaTrader.NinjaScript.Strategies
{
public class InsideBarZigZag : Strategy
{
private double takeProfitTicks;
private double stopLossTicks;
private int contractSize;
private int zigzagPeriod;
private int insideBarIndex;
private int lastInsideBarIndex;
private bool isLongPosition;
private bool isShortPosition;
private double highBeforeInsideBar;
private double lowBeforeInsideBar;
private bool zigzagUptrend;
private double lastZigzagHigh;
private double lastZigzagLow;

protected override void OnStateChange()
{
	if (State == State.SetDefaults)
	{
		Description									= @"Inside Bar + ZigZag Strategy (Latest IB)";
		Name										= "InsideBarZigZag";
		Calculate									= Calculate.OnBarClose;
		EntriesPerDirection							= 1;
		EntryHandling								= EntryHandling.AllEntries;
		IsExitOnSessionCloseStrategy				= true;
		ExitOnSessionCloseSeconds					= 30;
		//IsFillLimitOnFlatReject						= true;
		//AllowMimicOrders							= false;
		//MaxDrawdown									= 20;
		//MaxIntrabarOrderHandling					= MaxIntrabarOrderHandling.AllowMultipleNettingOnly;
		OrderFillResolution							= OrderFillResolution.Standard;
		Slippage									= 0;
		StartBehavior								= StartBehavior.WaitUntilFlat;
		TimeInForce									= TimeInForce.Day;
		TraceOrders									= true;
		RealtimeErrorHandling						= RealtimeErrorHandling.StopCancelClose;
		StopTargetHandling							= StopTargetHandling.PerEntryExecution;
		BarsRequiredToTrade							= 10;
		
		takeProfitTicks = 20;
		stopLossTicks = 40;
		contractSize = 1;
		zigzagPeriod = 4;
	}
	else if (State == State.Configure)
	{
	}
	else if (State == State.Active)
	{
		insideBarIndex = -1;
		lastInsideBarIndex = -1;
		isLongPosition = false;
		isShortPosition = false;
	}
}

protected override void OnBarUpdate()
{
	if (CurrentBar < 5)
		return;

	UpdateZigZagTrend();
	
	// ALWAYS CHECK FOR INSIDE BAR AND UPDATE TO LATEST
	bool insideBarDetected = IsInsideBar();
	
	if (insideBarDetected)
	{
		// NEW INSIDE BAR DETECTED - UPDATE TO LATEST (replaces previous)
		if (CurrentBar >= 2 && CurrentBar != lastInsideBarIndex)
		{
			highBeforeInsideBar = High\[2\];
			lowBeforeInsideBar = Low\[2\];
			insideBarIndex = CurrentBar;
			lastInsideBarIndex = CurrentBar;
			
			Print("=== NEW Inside Bar at bar " + CurrentBar + " | H=" + highBeforeInsideBar.ToString("F2") + " | L=" + lowBeforeInsideBar.ToString("F2") + " ===");
		}
	}

	// LONG EXECUTION - USE LATEST INSIDE BAR
	if (!isLongPosition && !isShortPosition && insideBarIndex >= 0 && zigzagUptrend)
	{
		// Only execute if we have a valid inside bar stored and current bar is AFTER the inside bar
		if (insideBarIndex < CurrentBar)
		{
			if (Close\[0\] > highBeforeInsideBar)
			{
				Print(">>> BUY SIGNAL at bar " + CurrentBar + " | Entry Price: " + Close\[0\].ToString("F2") + " | Above IB High: " + highBeforeInsideBar.ToString("F2"));
				EnterLong(contractSize);
				
				isLongPosition = true;
				
				double tpPrice = Close\[0\] + takeProfitTicks \* TickSize;
				double slPrice = Close\[0\] - stopLossTicks \* TickSize;
				
				Print("    TP Set at: " + tpPrice + " | SL Set at: " + slPrice);
				
				ExitShortLimit(contractSize, tpPrice , "TP_Long");
				ExitShortStopMarket(contractSize, slPrice, "SL_Long");
				
				insideBarIndex = -1;  // Reset - wait for next inside bar
			}
		}
	}

	// SHORT EXECUTION - USE LATEST INSIDE BAR
	if (!isLongPosition && !isShortPosition && insideBarIndex >= 0 && !zigzagUptrend)
	{
		// Only execute if we have a valid inside bar stored and current bar is AFTER the inside bar
		if (insideBarIndex < CurrentBar)
		{
			if (Close\[0\] < lowBeforeInsideBar)
			{
				Print(">>> SELL SIGNAL at bar " + CurrentBar + " | Entry Price: " + Close\[0\].ToString("F2") + " | Below IB Low: " + lowBeforeInsideBar.ToString("F2"));
				EnterShort(contractSize);
				
				isShortPosition = true;
				
				double tpPrice = Close\[0\] - takeProfitTicks \* TickSize;
				double slPrice = Close\[0\] + stopLossTicks \* TickSize;
				
				Print("    TP Set at: " + tpPrice.ToString("F2") + " | SL Set at: " + slPrice.ToString("F2"));
				
				BuyLimit(contractSize, tpPrice, "TP_Short");
				BuyStop(contractSize, slPrice, "SL_Short");
				
				insideBarIndex = -1;  // Reset - wait for next inside bar
			}
		}
	}

	// POSITION STATUS
	if (Position.MarketPosition == MarketPosition.Long)
		isLongPosition = true;
	else if (Position.MarketPosition == MarketPosition.Short)
		isShortPosition = true;
	else
	{
		isLongPosition = false;
		isShortPosition = false;
	}
}

private bool IsInsideBar()
{
	if (CurrentBar < 2)
		return false;

	return (High\[0\] < High\[1\] && Low\[0\] > Low\[1\]);
}

private void UpdateZigZagTrend()
{
	if (CurrentBar < zigzagPeriod)
		return;

	double recentHigh = High\[0\];
	double recentLow = Low\[0\];

	for (int i = 1; i < zigzagPeriod; i++)
	{
		if (High\[i\] > recentHigh)
			recentHigh = High\[i\];
		if (Low\[i\] < recentLow)
			recentLow = Low\[i\];
	}

	if (recentHigh > lastZigzagHigh && recentLow > lastZigzagLow)
	{
		zigzagUptrend = true;
	}
	else if (recentHigh < lastZigzagHigh && recentLow < lastZigzagLow)
	{
		zigzagUptrend = false;
	}

	lastZigzagHigh = recentHigh;
	lastZigzagLow = recentLow;
}

}

here is a little of you strategy checked, with ai securelly fix the another errors, i wait your message

Hello SJanz, here is the full code only for compile and run

#region Using declarations
using NinjaTrader.Cbi;
using NinjaTrader.Gui;
using NinjaTrader.NinjaScript.Indicators;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Windows.Media;
#endregion

namespace NinjaTrader.NinjaScript.Strategies
{
public class InsideBarZigZag : Strategy
{
private double takeProfitTicks;
private double stopLossTicks;
private int contractSize;
private int zigzagPeriod;
private int insideBarIndex;
private int lastInsideBarIndex;
private bool isLongPosition;
private bool isShortPosition;
private double highBeforeInsideBar;
private double lowBeforeInsideBar;
private bool zigzagUptrend;
private double lastZigzagHigh;
private double lastZigzagLow;

	protected override void OnStateChange()
	{
		if (State == State.SetDefaults)
		{
			Description = @"Inside Bar + ZigZag Strategy (Latest IB)";
			Name = "InsideBarZigZag";
			Calculate = Calculate.OnBarClose;
			EntriesPerDirection = 1;
			EntryHandling = EntryHandling.AllEntries;
			IsExitOnSessionCloseStrategy = true;
			ExitOnSessionCloseSeconds = 30;
			//IsFillLimitOnFlatReject						= true;
			//AllowMimicOrders							= false;
			//MaxDrawdown									= 20;
			//MaxIntrabarOrderHandling					= MaxIntrabarOrderHandling.AllowMultipleNettingOnly;
			OrderFillResolution = OrderFillResolution.Standard;
			Slippage = 0;
			StartBehavior = StartBehavior.WaitUntilFlat;
			TimeInForce = TimeInForce.Day;
			TraceOrders = true;
			RealtimeErrorHandling = RealtimeErrorHandling.StopCancelClose;
			StopTargetHandling = StopTargetHandling.PerEntryExecution;
			BarsRequiredToTrade = 10;

			takeProfitTicks = 20;
			stopLossTicks = 40;
			contractSize = 1;
			zigzagPeriod = 4;
		}
		else if (State == State.Configure)
		{
		}
		else if (State == State.Active)
		{
			insideBarIndex = -1;
			lastInsideBarIndex = -1;
			isLongPosition = false;
			isShortPosition = false;
		}
	}

	protected override void OnBarUpdate()
	{
		if (CurrentBar < 5)
			return;

		UpdateZigZagTrend();

		// ALWAYS CHECK FOR INSIDE BAR AND UPDATE TO LATEST
		bool insideBarDetected = IsInsideBar();

		if (insideBarDetected)
		{
			// NEW INSIDE BAR DETECTED - UPDATE TO LATEST (replaces previous)
			if (CurrentBar >= 2 && CurrentBar != lastInsideBarIndex)
			{
				highBeforeInsideBar = High\[2\];
				lowBeforeInsideBar = Low\[2\];
				insideBarIndex = CurrentBar;
				lastInsideBarIndex = CurrentBar;

				Print("=== NEW Inside Bar at bar " + CurrentBar + " | H=" + highBeforeInsideBar.ToString("F2") + " | L=" + lowBeforeInsideBar.ToString("F2") + " ===");
			}
		}

		// LONG EXECUTION - USE LATEST INSIDE BAR
		if (!isLongPosition && !isShortPosition && insideBarIndex >= 0 && zigzagUptrend)
		{
			// Only execute if we have a valid inside bar stored and current bar is AFTER the inside bar
			if (insideBarIndex < CurrentBar)
			{
				if (Close\[0\] > highBeforeInsideBar)
				{
					Print(">>> BUY SIGNAL at bar " + CurrentBar + " | Entry Price: " + Close\[0\].ToString("F2") + " | Above IB High: " + highBeforeInsideBar.ToString("F2"));
					EnterLong(contractSize);

					isLongPosition = true;

					double tpPrice = Close\[0\] + takeProfitTicks \* TickSize;
					double slPrice = Close\[0\] - stopLossTicks \* TickSize;

					Print("    TP Set at: " + tpPrice + " | SL Set at: " + slPrice);

					ExitShortLimit(contractSize, tpPrice, "TP_Long", "");
					ExitShortStopMarket(contractSize, slPrice, "SL_Long", "");

					insideBarIndex = -1;  // Reset - wait for next inside bar
				}
			}
		}

		// SHORT EXECUTION - USE LATEST INSIDE BAR
		if (!isLongPosition && !isShortPosition && insideBarIndex >= 0 && !zigzagUptrend)
		{
			// Only execute if we have a valid inside bar stored and current bar is AFTER the inside bar
			if (insideBarIndex < CurrentBar)
			{
				if (Close\[0\] < lowBeforeInsideBar)
				{
					Print(">>> SELL SIGNAL at bar " + CurrentBar + " | Entry Price: " + Close\[0\].ToString("F2") + " | Below IB Low: " + lowBeforeInsideBar.ToString("F2"));
					EnterShort(contractSize);

					isShortPosition = true;

					double tpPrice = Close\[0\] - takeProfitTicks \* TickSize;
					double slPrice = Close\[0\] + stopLossTicks \* TickSize;

					Print("    TP Set at: " + tpPrice + " | SL Set at: " + slPrice);

					ExitLongLimit(contractSize, tpPrice, "TP_Short", "");
					ExitLongStopMarket(contractSize, slPrice, "SL_Short", "");

					insideBarIndex = -1;  // Reset - wait for next inside bar
				}
			}
		}

		// POSITION STATUS
		if (Position.MarketPosition == MarketPosition.Long)
			isLongPosition = true;
		else if (Position.MarketPosition == MarketPosition.Short)
			isShortPosition = true;
		else
		{
			isLongPosition = false;
			isShortPosition = false;
		}
	}

	private bool IsInsideBar()
	{
		if (CurrentBar < 2)
			return false;

		return (High\[0\] < High\[1\] && Low\[0\] > Low\[1\]);
	}

	private void UpdateZigZagTrend()
	{
		if (CurrentBar < zigzagPeriod)
			return;

		double recentHigh = High\[0\];
		double recentLow = Low\[0\];

		for (int i = 1; i < zigzagPeriod; i++)
		{
			if (High\[i\] > recentHigh)
				recentHigh = High\[i\];
			if (Low\[i\] < recentLow)
				recentLow = Low\[i\];
		}

		if (recentHigh > lastZigzagHigh && recentLow > lastZigzagLow)
		{
			zigzagUptrend = true;
		}
		else if (recentHigh < lastZigzagHigh && recentLow < lastZigzagLow)
		{
			zigzagUptrend = false;
		}

		lastZigzagHigh = recentHigh;
		lastZigzagLow = recentLow;
	}
}

}

Thanks …but it’s NOT working!

How can I write you?

Two things usually break this setup. First, ZigZag repaints: its last leg keeps moving until the swing is confirmed, so a trend filter that reads the current leg looks right on history and behaves differently live. Base the trend on confirmed swings only (for example Swing(4), comparing the last two confirmed highs and lows). Second, the Period/Overbought/Oversold inputs aren’t the built-in ZigZag’s, so check which ZigZag you’re using. For the entry, store the pre-inside-bar high/low when the inside bar closes and use EnterLongStopMarket at that level. Happy to help further, feel free to DM me.