Ver en GitHub

Range Expansion Index Strategy

This strategy uses Tom DeMark's Range Expansion Index (REI) to evaluate price strength and weakness. The indicator compares current highs and lows with previous prices and oscillates between positive and negative values.

How It Works

  • When the REI rises above the Down Level (default -60) after being below it, the strategy opens a long position.
  • When the REI falls below the Up Level (default 60) after being above it, the strategy opens a short position.
  • Opposite positions are closed automatically when an opposite signal occurs.

Parameters

  • REI Period – number of bars used in the REI calculation (default 8).
  • Up Level – upper threshold that indicates price weakness when crossed downward (default 60).
  • Down Level – lower threshold that indicates price strength when crossed upward (default -60).
  • Candle Type – timeframe of candles for indicator calculation (default 8 hours).

Usage

Attach the strategy to a security and start it. The strategy subscribes to the specified candle series and uses market orders to enter or exit positions based on REI signals.

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>
/// Strategy based on the Range Expansion Index (REI).
/// Opens long positions when REI crosses above the down level (-60)
/// and short positions when it crosses below the up level (+60).
/// </summary>
public class RangeExpansionIndexStrategy : Strategy
{
	private readonly StrategyParam<int> _reiPeriod;
	private readonly StrategyParam<decimal> _upLevel;
	private readonly StrategyParam<decimal> _downLevel;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<DataType> _candleType;

	private RangeExpansionIndex _rei;
	private decimal? _prevRei;
	private int _barsSinceTrade;

	/// <summary>
	/// REI calculation period.
	/// </summary>
	public int ReiPeriod
	{
		get => _reiPeriod.Value;
		set => _reiPeriod.Value = value;
	}

	/// <summary>
	/// Upper indicator level.
	/// </summary>
	public decimal UpLevel
	{
		get => _upLevel.Value;
		set => _upLevel.Value = value;
	}

	/// <summary>
	/// Lower indicator level.
	/// </summary>
	public decimal DownLevel
	{
		get => _downLevel.Value;
		set => _downLevel.Value = value;
	}

	/// <summary>
	/// Bars to wait after a completed trade.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Candle type used for analysis.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="RangeExpansionIndexStrategy"/>.
	/// </summary>
	public RangeExpansionIndexStrategy()
	{
		_reiPeriod = Param(nameof(ReiPeriod), 8)
			.SetGreaterThanZero()
			.SetDisplay("REI Period", "Length of REI indicator", "Parameters")
			;

		_upLevel = Param(nameof(UpLevel), 70m)
			.SetDisplay("Up Level", "Upper threshold", "Parameters")
			;

		_downLevel = Param(nameof(DownLevel), -70m)
			.SetDisplay("Down Level", "Lower threshold", "Parameters")
			;

		_cooldownBars = Param(nameof(CooldownBars), 1)
			.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Parameters");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_rei?.Reset();
		_prevRei = null;
		_barsSinceTrade = CooldownBars;
	}

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

		_rei = new RangeExpansionIndex { Length = ReiPeriod };

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

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

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

		if (!_rei.IsFormed)
		{
			_prevRei = reiValue;
			return;
		}

		if (_barsSinceTrade < CooldownBars)
			_barsSinceTrade++;

		if (_prevRei is decimal prev)
		{
			if (_barsSinceTrade >= CooldownBars)
			{
				if (prev < DownLevel && reiValue >= DownLevel && Position <= 0)
				{
					BuyMarket(Volume + Math.Abs(Position));
					_barsSinceTrade = 0;
				}
				else if (prev > UpLevel && reiValue <= UpLevel && Position >= 0)
				{
					SellMarket(Volume + Math.Abs(Position));
					_barsSinceTrade = 0;
				}
			}
		}

		_prevRei = reiValue;
	}

	private class RangeExpansionIndex : BaseIndicator
	{
		public int Length { get; set; } = 8;

		private readonly List<ICandleMessage> _buffer = new();

		protected override IIndicatorValue OnProcess(IIndicatorValue input)
		{
			var candle = input.GetValue<ICandleMessage>();

			if (candle == null)
			{
				IsFormed = false;
				return new DecimalIndicatorValue(this, 0m, input.Time);
			}

			_buffer.Add(candle);

			var need = Length + 8;
			if (_buffer.Count > need)
				_buffer.RemoveAt(0);

			if (_buffer.Count < need)
			{
				IsFormed = false;
				return new DecimalIndicatorValue(this, 0m, input.Time);
			}

			var last = _buffer.Count - 1;
			decimal subSum = 0m;
			decimal absSum = 0m;

			for (var i = last; i > last - Length; i--)
			{
				if (_buffer[i] == null || _buffer[i - 2] == null || _buffer[i - 5] == null || _buffer[i - 6] == null || _buffer[i - 7] == null || _buffer[i - 8] == null)
				{
					IsFormed = false;
					return new DecimalIndicatorValue(this, 0m, input.Time);
				}

				var hi = _buffer[i].HighPrice;
				var hi2 = _buffer[i - 2].HighPrice;
				var lo = _buffer[i].LowPrice;
				var lo2 = _buffer[i - 2].LowPrice;

				var diff1 = hi - hi2;
				var diff2 = lo - lo2;

				var num1 = (_buffer[i - 2].HighPrice < _buffer[i - 7].ClosePrice &&
					_buffer[i - 2].HighPrice < _buffer[i - 8].ClosePrice &&
					hi < _buffer[i - 5].HighPrice &&
					hi < _buffer[i - 6].HighPrice) ? 0m : 1m;

				var num2 = (_buffer[i - 2].LowPrice > _buffer[i - 7].ClosePrice &&
					_buffer[i - 2].LowPrice > _buffer[i - 8].ClosePrice &&
					lo > _buffer[i - 5].LowPrice &&
					lo > _buffer[i - 6].LowPrice) ? 0m : 1m;

				subSum += num1 * num2 * (diff1 + diff2);
				absSum += Math.Abs(diff1) + Math.Abs(diff2);
			}

			var rei = absSum == 0m ? 0m : subSum / absSum * 100m;
			IsFormed = true;
			return new DecimalIndicatorValue(this, rei, input.Time);
		}

		public override void Reset()
		{
			base.Reset();
			_buffer.Clear();
		}
	}
}