Ver no GitHub

IU Bigger Than Range Strategy

Breakout strategy that opens trades when the candle body is larger than the previous range of recent candles.

The system compares the current candle body with the range between the highest open/close and lowest open/close over a configurable lookback period. If the body exceeds the previous range, it enters in the candle direction and manages risk via configurable stop methods.

Details

  • Entry Criteria: Candle body larger than previous range; direction based on candle body.
  • Long/Short: Both.
  • Exit Criteria: Stop loss or take profit.
  • Stops: Previous candle, ATR or swing levels.
  • Default Values:
    • LookbackPeriod = 22
    • RiskToReward = 3
    • StopLossMethod = PreviousHighLow
    • AtrLength = 14
    • AtrFactor = 2m
    • SwingLength = 10
    • CandleType = TimeSpan.FromMinutes(1)
  • Filters:
    • Category: Breakout
    • Direction: Both
    • Indicators: Highest, Lowest, ATR
    • Stops: Yes
    • Complexity: Medium
    • Timeframe: Any
    • Seasonality: No
    • Neural Networks: No
    • Divergence: No
    • Risk Level: Medium
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Breakout strategy that enters when the candle body exceeds the previous range.
/// </summary>
public class IuBiggerThanRangeStrategy : Strategy
{
	private readonly StrategyParam<int> _lookbackPeriod;
	private readonly StrategyParam<int> _riskToReward;
	private readonly StrategyParam<decimal> _atrFactor;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevRangeSize;
	private decimal _prevCandleHigh;
	private decimal _prevCandleLow;
	private decimal _stopPrice;
	private decimal _targetPrice;
	private decimal _entryPrice;
	private int _barCount;

	/// <summary>
	/// Lookback period for range calculation.
	/// </summary>
	public int LookbackPeriod
	{
		get => _lookbackPeriod.Value;
		set => _lookbackPeriod.Value = value;
	}

	/// <summary>
	/// Risk to reward ratio.
	/// </summary>
	public int RiskToReward
	{
		get => _riskToReward.Value;
		set => _riskToReward.Value = value;
	}

	/// <summary>
	/// ATR multiplier factor.
	/// </summary>
	public decimal AtrFactor
	{
		get => _atrFactor.Value;
		set => _atrFactor.Value = value;
	}

	/// <summary>
	/// Type of candles to process.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="IuBiggerThanRangeStrategy"/> class.
	/// </summary>
	public IuBiggerThanRangeStrategy()
	{
		_lookbackPeriod = Param(nameof(LookbackPeriod), 22)
			.SetDisplay("Lookback Period", "Length for range calculation.", "Parameters");

		_riskToReward = Param(nameof(RiskToReward), 3)
			.SetDisplay("Risk To Reward", "Risk to reward ratio.", "Parameters");

		_atrFactor = Param(nameof(AtrFactor), 2m)
			.SetDisplay("ATR Factor", "ATR multiplier.", "Risk Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles.", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevRangeSize = 0m;
		_prevCandleHigh = 0m;
		_prevCandleLow = 0m;
		_stopPrice = 0m;
		_targetPrice = 0m;
		_entryPrice = 0m;
		_barCount = 0;
	}

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

		_prevRangeSize = 0m;
		_prevCandleHigh = 0m;
		_prevCandleLow = 0m;
		_stopPrice = 0m;
		_targetPrice = 0m;
		_entryPrice = 0m;
		_barCount = 0;

		var atr = new AverageTrueRange { Length = LookbackPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(atr, ProcessCandle)
			.Start();

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

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

		_barCount++;

		var rangeSize = candle.HighPrice - candle.LowPrice;
		var candleBody = Math.Abs(candle.ClosePrice - candle.OpenPrice);

		if (_barCount < LookbackPeriod)
		{
			_prevRangeSize = rangeSize;
			_prevCandleHigh = candle.HighPrice;
			_prevCandleLow = candle.LowPrice;
			return;
		}

		// Exit logic first
		if (Position > 0)
		{
			if (candle.LowPrice <= _stopPrice || candle.ClosePrice >= _targetPrice)
			{
				SellMarket();
				_stopPrice = 0m;
				_targetPrice = 0m;
				_entryPrice = 0m;
			}
		}
		else if (Position < 0)
		{
			if (candle.HighPrice >= _stopPrice || candle.ClosePrice <= _targetPrice)
			{
				BuyMarket();
				_stopPrice = 0m;
				_targetPrice = 0m;
				_entryPrice = 0m;
			}
		}

		// Entry logic
		var isBodyStrong = candleBody >= _prevRangeSize && candleBody >= atrValue * 0.8m;

		if (Position == 0 && isBodyStrong)
		{
			if (candle.ClosePrice > candle.OpenPrice && candle.ClosePrice > _prevCandleHigh)
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = _entryPrice - atrValue * AtrFactor;
				_targetPrice = _entryPrice + (_entryPrice - _stopPrice) * RiskToReward;
			}
			else if (candle.ClosePrice < candle.OpenPrice && candle.ClosePrice < _prevCandleLow)
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = _entryPrice + atrValue * AtrFactor;
				_targetPrice = _entryPrice - (_stopPrice - _entryPrice) * RiskToReward;
			}
		}

		_prevRangeSize = rangeSize;
		_prevCandleHigh = candle.HighPrice;
		_prevCandleLow = candle.LowPrice;
	}
}