Стратегия IBS Internal Bar Strength
IBS Internal Bar Strength — контртрендовая стратегия, определяющая перепроданность или перекупленность по положению закрытия предыдущей свечи внутри её диапазона. Дополнительный фильтр EMA помогает торговать по тренду, а новые входы разрешены только при достаточном отклонении цены. Выход выполняется при достижении противоположного порога IBS или по истечении максимального времени удержания позиции.
Подробности
- Данные: ценовые свечи.
- Условия входа:
- Лонг: IBS ниже порога входа, условие EMA выполнено и направление разрешено.
- Шорт: IBS выше порога выхода, условие EMA выполнено и направление разрешено.
- Условия выхода: пересечение противоположного порога IBS или ограничение по времени сделки.
- Стопы: выход по времени.
- Параметры по умолчанию:
IbsEntryThreshold= 0.09IbsExitThreshold= 0.985EmaPeriod= 220MinEntryPct= 0MaxTradeDuration= 14
- Фильтры:
- Категория: контртрендовая
- Направление: лонг и шорт
- Индикаторы: IBS, EMA
- Сложность: низкая
- Уровень риска: средний
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Previous-bar IBS mean reversion with optional EMA alignment, spaced additions,
/// full-basket opposite-threshold exits and a maximum holding period in finished bars.
/// </summary>
public class IbsInternalBarStrengthStrategy : Strategy
{
private readonly StrategyParam<decimal> _ibsEntryThreshold;
private readonly StrategyParam<decimal> _ibsExitThreshold;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<bool> _useEmaFilter;
private readonly StrategyParam<bool> _allowLong;
private readonly StrategyParam<bool> _allowShort;
private readonly StrategyParam<decimal> _minEntryPct;
private readonly StrategyParam<int> _maxTradeDuration;
private readonly StrategyParam<DataType> _candleType;
private decimal? _previousIbs;
private decimal _previousClose;
private decimal _previousEma;
private bool _previousEmaReady;
private decimal _lastEntryPrice;
private int _barCount;
private int _firstEntryBar;
public decimal IbsEntryThreshold { get => _ibsEntryThreshold.Value; set => _ibsEntryThreshold.Value = value; }
public decimal IbsExitThreshold { get => _ibsExitThreshold.Value; set => _ibsExitThreshold.Value = value; }
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public bool UseEmaFilter { get => _useEmaFilter.Value; set => _useEmaFilter.Value = value; }
public bool AllowLong { get => _allowLong.Value; set => _allowLong.Value = value; }
public bool AllowShort { get => _allowShort.Value; set => _allowShort.Value = value; }
public decimal MinEntryPct { get => _minEntryPct.Value; set => _minEntryPct.Value = value; }
public int MaxTradeDuration { get => _maxTradeDuration.Value; set => _maxTradeDuration.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public IbsInternalBarStrengthStrategy()
{
_ibsEntryThreshold = Param(nameof(IbsEntryThreshold), 0.09m).SetRange(0m, 1m);
_ibsExitThreshold = Param(nameof(IbsExitThreshold), 0.985m).SetRange(0m, 1m);
_emaPeriod = Param(nameof(EmaPeriod), 220).SetGreaterThanZero();
_useEmaFilter = Param(nameof(UseEmaFilter), true);
_allowLong = Param(nameof(AllowLong), true);
_allowShort = Param(nameof(AllowShort), true);
_minEntryPct = Param(nameof(MinEntryPct), 0m).SetNotNegative();
_maxTradeDuration = Param(nameof(MaxTradeDuration), 14).SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame());
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (IbsEntryThreshold >= IbsExitThreshold)
throw new InvalidOperationException("IbsEntryThreshold must be below IbsExitThreshold.");
ResetState();
// A disabled filter must not impose a 220-bar warm-up on the IBS signal.
var ema = new ExponentialMovingAverage { Length = UseEmaFilter ? EmaPeriod : 1 };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, (candle, value) => ProcessCandle(candle, value, ema.IsFormed)).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
if (UseEmaFilter)
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ResetState()
{
_previousIbs = null;
_previousClose = _previousEma = _lastEntryPrice = 0m;
_previousEmaReady = false;
_barCount = _firstEntryBar = 0;
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue, bool emaReady)
{
if (candle.State != CandleStates.Finished)
return;
var ibs = _previousIbs;
var previousClose = _previousClose;
var previousEma = _previousEma;
var filterReady = _previousEmaReady;
var range = candle.HighPrice - candle.LowPrice;
_previousIbs = range == 0m ? 0.5m : (candle.ClosePrice - candle.LowPrice) / range;
_previousClose = candle.ClosePrice;
_previousEma = emaValue;
_previousEmaReady = emaReady;
_barCount++;
if (ibs is null || !IsFormedAndOnlineAndAllowTrading())
return;
if (Position != 0m)
{
var oppositeThreshold = Position > 0m ? ibs > IbsExitThreshold : ibs < IbsEntryThreshold;
if (oppositeThreshold || _barCount - _firstEntryBar >= MaxTradeDuration)
{
if (Position > 0m)
SellMarket(Math.Abs(Position));
else
BuyMarket(Math.Abs(Position));
_lastEntryPrice = 0m;
_firstEntryBar = 0;
return;
}
}
if (UseEmaFilter && !filterReady)
return;
if (Position != 0m && _lastEntryPrice > 0m
&& 100m * Math.Abs(candle.ClosePrice - _lastEntryPrice) / _lastEntryPrice < MinEntryPct)
return;
var buy = AllowLong && Position >= 0m && ibs < IbsEntryThreshold
&& (!UseEmaFilter || previousClose > previousEma);
var sell = AllowShort && Position <= 0m && ibs > IbsExitThreshold
&& (!UseEmaFilter || previousClose < previousEma);
if (!buy && !sell)
return;
// Added entries do not extend the lifetime of the existing basket.
if (Position == 0m)
_firstEntryBar = _barCount;
_lastEntryPrice = candle.ClosePrice;
if (buy)
BuyMarket();
else
SellMarket();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal, Convert, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ibs_internal_bar_strength_strategy(Strategy):
"""Previous-bar IBS, optional EMA trend filter, spaced additions and full-basket exits."""
def __init__(self):
super(ibs_internal_bar_strength_strategy, self).__init__()
self._ibs_entry_threshold = self.Param("IbsEntryThreshold", 0.09).SetRange(0.0, 1.0)
self._ibs_exit_threshold = self.Param("IbsExitThreshold", 0.985).SetRange(0.0, 1.0)
self._ema_period = self.Param("EmaPeriod", 220).SetGreaterThanZero()
self._use_ema_filter = self.Param("UseEmaFilter", True)
self._allow_long = self.Param("AllowLong", True)
self._allow_short = self.Param("AllowShort", True)
self._min_entry_pct = self.Param("MinEntryPct", 0.0).SetNotNegative()
self._max_trade_duration = self.Param("MaxTradeDuration", 14).SetGreaterThanZero()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1)))
self._reset_state()
def GetWorkingSecurities(self):
return [(self.Security, self._candle_type.Value)]
def OnReseted(self):
super(ibs_internal_bar_strength_strategy, self).OnReseted()
self._reset_state()
def _reset_state(self):
self._previous_ibs = None
self._previous_close = Decimal.Zero
self._previous_ema = Decimal.Zero
self._previous_ema_ready = False
self._last_entry_price = Decimal.Zero
self._bar_count = 0
self._first_entry_bar = 0
def OnStarted2(self, time):
super(ibs_internal_bar_strength_strategy, self).OnStarted2(time)
if self._ibs_entry_threshold.Value >= self._ibs_exit_threshold.Value:
raise ValueError("IbsEntryThreshold must be below IbsExitThreshold.")
self._reset_state()
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value if self._use_ema_filter.Value else 1
def process(candle, value):
self._process_candle(candle, value, ema.IsFormed)
subscription = self.SubscribeCandles(self._candle_type.Value)
subscription.Bind(ema, process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
if self._use_ema_filter.Value:
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_value, ema_ready):
if candle.State != CandleStates.Finished:
return
ibs = self._previous_ibs
previous_close = self._previous_close
previous_ema = self._previous_ema
filter_ready = self._previous_ema_ready
candle_range = candle.HighPrice - candle.LowPrice
self._previous_ibs = (Convert.ToDecimal(0.5) if candle_range == 0 else
(candle.ClosePrice - candle.LowPrice) / candle_range)
self._previous_close = candle.ClosePrice
self._previous_ema = ema_value
self._previous_ema_ready = ema_ready
self._bar_count += 1
if ibs is None or not self.IsFormedAndOnlineAndAllowTrading():
return
entry_threshold = Convert.ToDecimal(self._ibs_entry_threshold.Value)
exit_threshold = Convert.ToDecimal(self._ibs_exit_threshold.Value)
if self.Position != 0:
opposite_threshold = ibs > exit_threshold if self.Position > 0 else ibs < entry_threshold
if (opposite_threshold or
self._bar_count - self._first_entry_bar >= self._max_trade_duration.Value):
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
else:
self.BuyMarket(Math.Abs(self.Position))
self._last_entry_price = Decimal.Zero
self._first_entry_bar = 0
return
if self._use_ema_filter.Value and not filter_ready:
return
if (self.Position != 0 and self._last_entry_price > 0 and
Decimal(100) * Math.Abs(candle.ClosePrice - self._last_entry_price) / self._last_entry_price <
Convert.ToDecimal(self._min_entry_pct.Value)):
return
buy = (self._allow_long.Value and self.Position >= 0 and ibs < entry_threshold and
(not self._use_ema_filter.Value or previous_close > previous_ema))
sell = (self._allow_short.Value and self.Position <= 0 and ibs > exit_threshold and
(not self._use_ema_filter.Value or previous_close < previous_ema))
if not buy and not sell:
return
if self.Position == 0:
self._first_entry_bar = self._bar_count
self._last_entry_price = candle.ClosePrice
if buy:
self.BuyMarket()
else:
self.SellMarket()
def CreateClone(self):
return ibs_internal_bar_strength_strategy()