Стратегия Range Expansion Index
Стратегия использует индикатор Томаса ДеМарка Range Expansion Index (REI) для оценки силы и слабости цены. Индикатор сравнивает текущие максимумы и минимумы с прошлыми ценами и колеблется в диапазоне положительных и отрицательных значений.
Принцип работы
- Когда REI поднимается выше нижнего уровня (по умолчанию
-60) после нахождения ниже него, стратегия открывает длинную позицию. - Когда REI опускается ниже верхнего уровня (по умолчанию
60) после нахождения выше него, стратегия открывает короткую позицию. - Противоположные позиции закрываются автоматически при появлении обратного сигнала.
Параметры
REI Period– количество баров в расчёте REI (по умолчанию8).Up Level– верхний порог, пробой вниз которого указывает на слабость цены (по умолчанию60).Down Level– нижний порог, пробой вверх которого указывает на силу цены (по умолчанию-60).Candle Type– таймфрейм свечей для расчёта индикатора (по умолчанию8 часов).
Использование
Привяжите стратегию к инструменту и запустите её. Стратегия подпишется на указанный ряд свечей и будет использовать рыночные заявки для входа и выхода из позиции по сигналам REI.
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();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class range_expansion_index_strategy(Strategy):
def __init__(self):
super(range_expansion_index_strategy, self).__init__()
self._rei_period = self.Param("ReiPeriod", 8) \
.SetDisplay("REI Period", "Length of REI indicator", "Parameters")
self._up_level = self.Param("UpLevel", 70.0) \
.SetDisplay("Up Level", "Upper threshold", "Parameters")
self._down_level = self.Param("DownLevel", -70.0) \
.SetDisplay("Down Level", "Lower threshold", "Parameters")
self._cooldown_bars = self.Param("CooldownBars", 1) \
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_rei = None
self._bars_since_trade = 0
self._buffer = []
@property
def ReiPeriod(self):
return self._rei_period.Value
@ReiPeriod.setter
def ReiPeriod(self, value):
self._rei_period.Value = value
@property
def UpLevel(self):
return self._up_level.Value
@UpLevel.setter
def UpLevel(self, value):
self._up_level.Value = value
@property
def DownLevel(self):
return self._down_level.Value
@DownLevel.setter
def DownLevel(self, value):
self._down_level.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _compute_rei(self, candle):
self._buffer.append(candle)
length = self.ReiPeriod
need = length + 8
if len(self._buffer) > need:
self._buffer.pop(0)
if len(self._buffer) < need:
return None
last = len(self._buffer) - 1
sub_sum = 0.0
abs_sum = 0.0
for i in range(last, last - length, -1):
hi = float(self._buffer[i].HighPrice)
hi2 = float(self._buffer[i - 2].HighPrice)
lo = float(self._buffer[i].LowPrice)
lo2 = float(self._buffer[i - 2].LowPrice)
diff1 = hi - hi2
diff2 = lo - lo2
cond1 = (float(self._buffer[i - 2].HighPrice) < float(self._buffer[i - 7].ClosePrice)
and float(self._buffer[i - 2].HighPrice) < float(self._buffer[i - 8].ClosePrice)
and hi < float(self._buffer[i - 5].HighPrice)
and hi < float(self._buffer[i - 6].HighPrice))
num1 = 0.0 if cond1 else 1.0
cond2 = (float(self._buffer[i - 2].LowPrice) > float(self._buffer[i - 7].ClosePrice)
and float(self._buffer[i - 2].LowPrice) > float(self._buffer[i - 8].ClosePrice)
and lo > float(self._buffer[i - 5].LowPrice)
and lo > float(self._buffer[i - 6].LowPrice))
num2 = 0.0 if cond2 else 1.0
sub_sum += num1 * num2 * (diff1 + diff2)
abs_sum += abs(diff1) + abs(diff2)
if abs_sum == 0.0:
return 0.0
return sub_sum / abs_sum * 100.0
def OnStarted2(self, time):
super(range_expansion_index_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
rei_value = self._compute_rei(candle)
if rei_value is None:
return
if self._bars_since_trade < self.CooldownBars:
self._bars_since_trade += 1
if self._prev_rei is not None:
prev = self._prev_rei
down_level = float(self.DownLevel)
up_level = float(self.UpLevel)
if self._bars_since_trade >= self.CooldownBars:
pos = self.Position
if prev < down_level and rei_value >= down_level and pos <= 0:
self.BuyMarket(self.Volume + abs(pos))
self._bars_since_trade = 0
elif prev > up_level and rei_value <= up_level and pos >= 0:
self.SellMarket(self.Volume + abs(pos))
self._bars_since_trade = 0
self._prev_rei = rei_value
def OnReseted(self):
super(range_expansion_index_strategy, self).OnReseted()
self._prev_rei = None
self._bars_since_trade = self.CooldownBars
self._buffer = []
def CreateClone(self):
return range_expansion_index_strategy()