IBS Internal Bar Strength-Strategie
IBS Internal Bar Strength ist eine Mean-Reversion-Strategie, die den Schlusskurs der vorherigen Kerze innerhalb ihrer Range nutzt, um überkaufte oder überverkaufte Bedingungen zu erkennen. Ein optionaler EMA-Filter richtet die Trades an der Trendrichtung aus, und Einstiege werden nur erlaubt, wenn sich der Preis um einen Mindestprozentsatz vom letzten Einstieg entfernt. Positionen werden geschlossen, wenn der IBS den entgegengesetzten Schwellenwert kreuzt oder die maximale Haltedauer erreicht wird.
Details
- Daten: Kurskerzen.
- Einstiegskriterien:
- Long: IBS unterhalb des Einstiegs-Schwellenwerts, EMA-Bedingung erfüllt und Richtung erlaubt.
- Short: IBS oberhalb des Ausstiegs-Schwellenwerts, EMA-Bedingung erfüllt und Richtung erlaubt.
- Ausstiegskriterien: IBS kreuzt den entgegengesetzten Schwellenwert oder Handelsdauerlimit.
- Stops: Zeitbasierter Ausstieg.
- Standardwerte:
IbsEntryThreshold= 0.09IbsExitThreshold= 0.985EmaPeriod= 220MinEntryPct= 0MaxTradeDuration= 14
- Filter:
- Kategorie: Mean Reversion
- Richtung: Long & Short
- Indikatoren: IBS, EMA
- Komplexität: Niedrig
- Risikolevel: Mittel
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()