Ver no GitHub

Small Inside Bar Strategy

Overview

The Small Inside Bar Strategy searches for a compact inside bar pattern followed by a momentum shift between two consecutive candles. The original MetaTrader 5 expert was translated to StockSharp high-level API and now operates on completed candles only. The approach is designed for traders who prefer breakout-style entries triggered by compressed volatility phases.

Pattern definition

The strategy evaluates the two most recent completed candles:

  1. Inside bar condition – the latest finished candle must be fully contained within the range of the previous one.
  2. Range ratio filter – the range of the mother bar (two bars ago) must be at least a configurable multiple of the inside bar range. The default ratio is 2:1.
  3. Directional filters
    • A long setup requires a bullish inside bar forming in the lower half of the mother bar together with a bearish mother bar.
    • A short setup requires a bearish inside bar forming in the upper half of the mother bar together with a bullish mother bar.
  4. Optional reversal swaps the long and short interpretations while retaining the same geometric requirements.

Position handling

The OpenMode parameter mirrors the behaviour of the original EA:

  • AnySignal – submit a new market order on every signal. When an opposite position exists it is partially offset because StockSharp uses netting accounts.
  • SwingWithRefill – flatten the opposite exposure before entering and allow multiple adds in the same direction.
  • SingleSwing – maintain at most one directional trade; new signals are ignored while an aligned position is open.

Both long and short entries can be independently enabled. Reversal trading simply inverts which setup produces long or short orders.

Parameters

Name Default Description
CandleType 1 hour time frame Candle subscription used for pattern detection.
RangeRatioThreshold 2.0 Minimum mother-to-inside range ratio.
EnableLong true Allow bullish trades.
EnableShort true Allow bearish trades.
ReverseSignals false Swap long and short pattern directions.
OpenMode SwingWithRefill Controls how existing exposure is handled on a new signal.

Trading logic

  1. Subscribe to the configured candle series and wait for finished bars.
  2. Maintain the last two completed candles to evaluate the pattern.
  3. When the pattern and ratio filters align, determine the directional signal, optionally applying reversal.
  4. Confirm that trading is allowed (IsFormedAndOnlineAndAllowTrading) and that the relevant direction is enabled.
  5. Compute the order size based on the selected OpenMode and send a market order using the base strategy volume.
  6. Update the internal candle history so the newest candle becomes part of the next evaluation cycle.

Implementation notes

  • The strategy uses StartProtection() to enable the built-in risk manager (without predefined stop or take-profit values). Extra filters can be added externally if needed.
  • Indicator state is not stored in collections; only the two latest candles are kept as required for the pattern.
  • The algorithm relies purely on completed candles, avoiding intra-bar calculations in line with high-level API best practices.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Implements the "Small Inside Bar" pattern strategy converted from MetaTrader 5.
/// The strategy searches for an inside bar with a small range compared to the mother bar
/// and opens positions following the direction of the pattern conditions.
/// </summary>
public class SmallInsideBarStrategy : Strategy
{
	/// <summary>
	/// Defines how the strategy manages simultaneous entries.
	/// </summary>
	public enum SmallInsideBarOpenModes
	{
		/// <summary>
		/// Open a new position on every signal without forcing opposite positions to close.
		/// </summary>
		AnySignal,

		/// <summary>
		/// Close opposite positions first and allow adding to the current swing direction.
		/// </summary>
		SwingWithRefill,

		/// <summary>
		/// Maintain a single position in the market and ignore additional entries while it is active.
		/// </summary>
		SingleSwing
	}

	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _rangeRatioThreshold;
	private readonly StrategyParam<bool> _enableLong;
	private readonly StrategyParam<bool> _enableShort;
	private readonly StrategyParam<bool> _reverseSignals;
	private readonly StrategyParam<SmallInsideBarOpenModes> _openMode;

	private ICandleMessage _previousCandle;
	private ICandleMessage _twoBackCandle;

	/// <summary>
	/// Type of candles used for pattern detection.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Minimum ratio between the mother bar range and the inside bar range.
	/// </summary>
	public decimal RangeRatioThreshold
	{
		get => _rangeRatioThreshold.Value;
		set => _rangeRatioThreshold.Value = value;
	}

	/// <summary>
	/// Allow long trades.
	/// </summary>
	public bool EnableLong
	{
		get => _enableLong.Value;
		set => _enableLong.Value = value;
	}

	/// <summary>
	/// Allow short trades.
	/// </summary>
	public bool EnableShort
	{
		get => _enableShort.Value;
		set => _enableShort.Value = value;
	}

	/// <summary>
	/// Reverse long and short signals.
	/// </summary>
	public bool ReverseSignals
	{
		get => _reverseSignals.Value;
		set => _reverseSignals.Value = value;
	}

	/// <summary>
	/// Mode for handling position entries.
	/// </summary>
	public SmallInsideBarOpenModes OpenMode
	{
		get => _openMode.Value;
		set => _openMode.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public SmallInsideBarStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Time frame used for pattern detection", "General");

		_rangeRatioThreshold = Param(nameof(RangeRatioThreshold), 2.25m)
			.SetGreaterThanZero()
			.SetDisplay("Range Ratio", "Minimum mother-to-inside bar range ratio", "Pattern")
			
			.SetOptimize(1.5m, 3m, 0.25m);

		_enableLong = Param(nameof(EnableLong), true)
			.SetDisplay("Enable Long", "Allow bullish trades", "Trading");

		_enableShort = Param(nameof(EnableShort), true)
			.SetDisplay("Enable Short", "Allow bearish trades", "Trading");

		_reverseSignals = Param(nameof(ReverseSignals), false)
			.SetDisplay("Reverse Signals", "Invert long and short signals", "Trading");

		_openMode = Param(nameof(OpenMode), SmallInsideBarOpenModes.SwingWithRefill)
			.SetDisplay("Open Mode", "Position management mode", "Trading");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_previousCandle = null;
		_twoBackCandle = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_previousCandle == null)
		{
			_previousCandle = candle;
			return;
		}

		if (_twoBackCandle == null)
		{
			_twoBackCandle = _previousCandle;
			_previousCandle = candle;
			return;
		}

		var insideHigh = _previousCandle.HighPrice;
		var insideLow = _previousCandle.LowPrice;
		var motherHigh = _twoBackCandle.HighPrice;
		var motherLow = _twoBackCandle.LowPrice;

		if (insideHigh <= insideLow || motherHigh <= motherLow)
		{
			ShiftHistory(candle);
			return;
		}

		if (!(insideHigh < motherHigh && insideLow > motherLow))
		{
			ShiftHistory(candle);
			return;
		}

		var insideRange = insideHigh - insideLow;
		var motherRange = motherHigh - motherLow;
		var ratio = insideRange == 0 ? decimal.MaxValue : motherRange / insideRange;

		if (ratio <= RangeRatioThreshold)
		{
			ShiftHistory(candle);
			return;
		}

		var midpoint = (motherHigh + motherLow) / 2m;

		var bullishInside = _previousCandle.ClosePrice > _previousCandle.OpenPrice && insideHigh < midpoint && _twoBackCandle.ClosePrice < _twoBackCandle.OpenPrice;
		var bearishInside = _previousCandle.ClosePrice < _previousCandle.OpenPrice && insideLow < midpoint && _twoBackCandle.ClosePrice > _twoBackCandle.OpenPrice;

		if (ReverseSignals)
		{
			(bullishInside, bearishInside) = (bearishInside, bullishInside);
		}

		var shouldOpenLong = bullishInside && EnableLong;
		var shouldOpenShort = bearishInside && EnableShort;

		if (shouldOpenLong)
		{
			var volume = CalculateOrderVolume(true);

			if (volume > 0)
				BuyMarket(volume);
		}

		if (shouldOpenShort)
		{
			var volume = CalculateOrderVolume(false);

			if (volume > 0)
				SellMarket(volume);
		}

		ShiftHistory(candle);
	}

	private decimal CalculateOrderVolume(bool isLong)
	{
		var baseVolume = Volume;

		if (baseVolume <= 0)
			return 0;

		var position = Position;

		if (isLong)
		{
			if (OpenMode == SmallInsideBarOpenModes.SingleSwing && position > 0)
				return 0;

			if (position < 0 && OpenMode != SmallInsideBarOpenModes.AnySignal)
				baseVolume += Math.Abs(position);
		}
		else
		{
			if (OpenMode == SmallInsideBarOpenModes.SingleSwing && position < 0)
				return 0;

			if (position > 0 && OpenMode != SmallInsideBarOpenModes.AnySignal)
				baseVolume += Math.Abs(position);
		}

		return baseVolume;
	}

	private void ShiftHistory(ICandleMessage candle)
	{
		// Keep track of the last two finished candles for pattern evaluation.
		_twoBackCandle = _previousCandle;
		_previousCandle = candle;
	}
}