Estratégia de Índice de Expansão de Intervalo
Esta estratégia usa o Índice de Expansão de Intervalo (REI) de Tom DeMark para avaliar a força e a fraqueza do preço. O indicador compara máximas e mínimas atuais com preços anteriores e oscila entre valores positivos e negativos.
Como Funciona
- Quando o REI sobe acima do Nível Inferior (padrão
-60) após ter estado abaixo dele, a estratégia abre uma posição comprada. - Quando o REI cai abaixo do Nível Superior (padrão
60) após ter estado acima dele, a estratégia abre uma posição vendida. - As posições opostas são fechadas automaticamente quando ocorre um sinal oposto.
Parâmetros
REI Period– número de barras usadas no cálculo do REI (padrão8).Up Level– limiar superior que indica fraqueza do preço quando cruzado para baixo (padrão60).Down Level– limiar inferior que indica força do preço quando cruzado para cima (padrão-60).Candle Type– período dos candles para o cálculo do indicador (padrão8 horas).
Uso
Anexe a estratégia a um ativo e inicie-a. A estratégia se inscreve na série de candles especificada e usa ordens a mercado para entrar ou sair de posições com base nos sinais do 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()