Rücksetzer-Rebound-Strategie
Übersicht
Die Rücksetzer-Rebound-Strategie ist eine C#-Konvertierung des MQL5-Expertenberaters "TST (barabashkakvn's Edition)". Sie überwacht ein einzelnes Instrument auf dem durch den Parameter CandleType angegebenen Zeitrahmen und sucht nach starken Bewegungen, die zurück in den Balken-Range zurücksetzen. Wenn ein bullischer Balken von seinem Hoch um mehr als den Rücksetzer-Schwellenwert verblasst, kauft die Strategie, während ein äquivalenter bärischer Rücksetzer einen Verkauf auslöst. Die Implementierung verwendet StockSharp's High-Level-Kerzenabonnement-API und verwaltet alle Schutzorders in Pip-Einheiten, die in absolute Preisoffsets umgerechnet werden.
Pip-Abstände werden aus dem Instrument-PriceStep berechnet. Für Symbole, die mit drei oder fünf Dezimalstellen notieren, multipliziert die Strategie den Schritt automatisch mit zehn, um der MetaTrader-Definition eines Pips zu entsprechen. Alle Positionsgrößenbestimmungen werden von der Basis-Volume-Eigenschaft der Strategie übernommen.
Einstiegslogik
- Nur abgeschlossene Kerzen aus der konfigurierten
CandleType-Serie verarbeiten.
- Mit
ReverseSignal = false (Standard):
- Long-Setup: die Kerze schließt unter ihrer Eröffnung und die Differenz zwischen dem Kerzenhoch und dem Schluss überschreitet
RollbackRatePips (in Preis umgerechnet). Dies zeigt an, dass sich der Preis nach oben ausdehnte und dann tief genug zurücksetzte, um für einen konträren Long-Einstieg zu qualifizieren.
- Short-Setup: die Kerze schließt über ihrer Eröffnung und die Differenz zwischen dem Schluss und dem Kerzentief überschreitet
RollbackRatePips. Dies spiegelt die Long-Logik auf der bärischen Seite wider.
- Wenn
ReverseSignal = true sind die Rollen der Long- und Short-Bedingungen getauscht, was dem Händler ermöglicht, die Richtung ohne Änderung der anderen Parameter zu wechseln.
- Neue Einstiege werden nur platziert, wenn die aktuelle Position flach oder in der entgegengesetzten Richtung ist. Das ausgeführte Volumen entspricht
Volume + |Position|, damit eine entgegengesetzte Position geschlossen wird, bevor der neue Trade eröffnet wird.
Ausstiegslogik
- Beim Einstieg speichert die Strategie Stop-Loss- und Take-Profit-Levels basierend auf den konfigurierten Pip-Offsets. Wenn die Kerzenspanne einen Level berührt, wird die Position mit einer Market-Order geschlossen.
StopLossPips = 0 oder TakeProfitPips = 0 deaktiviert den entsprechenden Schutz-Level.
- Die Trailing-Logik wird aktiv, sobald der schwebende Gewinn
TrailingStopPips + TrailingStepPips (in Preis-Einheiten) überschreitet.
- Für Long-Trades ratschiert der Stop auf
höchsten Preis - TrailingStopPips, wenn der neue Level mindestens TrailingStepPips über dem vorherigen Stop liegt.
- Für Short-Trades ratschiert der Stop auf
niedrigsten Preis + TrailingStopPips, wenn der neue Level mindestens TrailingStepPips unter dem vorherigen Stop liegt.
- Wenn der Markt sich umkehrt und den Trailing-Stop kreuzt, wird die Position sofort ausgestiegen.
- Wenn keine Position offen ist, werden alle internen Zustandsvariablen geleert, um veraltete Daten zu vermeiden.
Parameter
| Parameter |
Beschreibung |
Standard |
CandleType |
Kerzenserie für die Signalberechnung. |
15-Minuten-Zeitrahmen |
StopLossPips |
Abstand des Schutz-Stops in Pips. Auf null setzen zum Deaktivieren. |
30 |
TakeProfitPips |
Abstand des Take-Profits in Pips. Auf null setzen zum Deaktivieren. |
90 |
TrailingStopPips |
Trailing-Stop-Offset in Pips. Auf null setzen zum Deaktivieren des Trailings. |
1 |
TrailingStepPips |
Extra-Gewinn (in Pips) erforderlich, bevor der Trailing-Stop wieder bewegt werden kann. Muss positiv sein, wenn Trailing aktiviert ist. |
15 |
RollbackRatePips |
Minimaler Rücksetzer vom Balken-Extrempunkt, der ein Signal validiert. |
15 |
ReverseSignal |
Kehrt die Einstiegsrichtung um (Long-Signale werden zu Short und umgekehrt). |
false |
Verwendungshinweise
- Die
Volume-Eigenschaft vor dem Start der Strategie setzen; sie definiert die gehandelte Menge für jede Order.
- Trailing erfordert
TrailingStopPips > 0 und TrailingStepPips > 0. Die Strategie wirft beim Start einen Fehler, wenn diese Beziehung verletzt wird.
- Da der ursprüngliche Experte Ticks innerhalb des aktiven Balkens auswertete, verwendet der C#-Port das Hoch/Tief/Schluss der abgeschlossenen Kerze, um dasselbe Verhalten anzunähern. Der Unterschied ist für die meisten Backtests vernachlässigbar und hält die Implementierung mit StockSharp's High-Level-API ausgerichtet.
- Die Strategie funktioniert mit einem einzelnen Wertpapier. Um mehrere Instrumente zu handeln, separate Strategie-Instanzen erstellen.
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>
/// Rollback Rebound strategy that follows the TST (barabashkakvn's edition) expert advisor logic.
/// The strategy buys after a bullish bar pulls back from its high and sells after a bearish bar rebounds from its low.
/// Protective orders are managed in pips and include optional trailing logic with a rollback filter.
/// </summary>
public class RollbackReboundStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<decimal> _trailingStepPips;
private readonly StrategyParam<decimal> _rollbackRatePips;
private readonly StrategyParam<bool> _reverseSignal;
private decimal _pipSize;
private decimal _stopLossOffset;
private decimal _takeProfitOffset;
private decimal _trailingStopOffset;
private decimal _trailingStepOffset;
private decimal _rollbackOffset;
private decimal _longEntryPrice;
private decimal _longStopPrice;
private decimal _longTakeProfitPrice;
private decimal _shortEntryPrice;
private decimal _shortStopPrice;
private decimal _shortTakeProfitPrice;
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Stop loss distance in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take profit distance in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Additional profit in pips required before the trailing stop moves.
/// </summary>
public decimal TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Pullback threshold in pips to validate signals.
/// </summary>
public decimal RollbackRatePips
{
get => _rollbackRatePips.Value;
set => _rollbackRatePips.Value = value;
}
/// <summary>
/// Inverts entry direction.
/// </summary>
public bool ReverseSignal
{
get => _reverseSignal.Value;
set => _reverseSignal.Value = value;
}
/// <summary>
/// Initialize parameters with defaults derived from the original MQL expert.
/// </summary>
public RollbackReboundStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Candle series used for calculations.", "General");
_stopLossPips = Param(nameof(StopLossPips), 30m)
.SetNotNegative()
.SetDisplay("Stop Loss (pips)", "Distance of the protective stop in pips.", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 90m)
.SetNotNegative()
.SetDisplay("Take Profit (pips)", "Distance of the take profit in pips.", "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 20m)
.SetNotNegative()
.SetDisplay("Trailing Stop (pips)", "Trailing stop offset in pips.", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 15m)
.SetNotNegative()
.SetDisplay("Trailing Step (pips)", "Additional profit required before trailing adjusts.", "Risk");
_rollbackRatePips = Param(nameof(RollbackRatePips), 40m)
.SetNotNegative()
.SetDisplay("Rollback Threshold (pips)", "Minimum pullback from the bar extreme to trigger entries.", "Signal");
_reverseSignal = Param(nameof(ReverseSignal), false)
.SetDisplay("Reverse Signal", "Invert entry logic (buy becomes sell).", "Signal");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_pipSize = 0m;
_stopLossOffset = 0m;
_takeProfitOffset = 0m;
_trailingStopOffset = 0m;
_trailingStepOffset = 0m;
_rollbackOffset = 0m;
ResetLongState();
ResetShortState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Validate that trailing configuration matches the behaviour of the original expert.
if (TrailingStopPips > 0m && TrailingStepPips <= 0m)
throw new InvalidOperationException("Trailing step must be greater than zero when trailing stop is enabled.");
// Convert pip-based parameters into absolute price offsets.
_pipSize = Security?.PriceStep ?? 1m;
if (Security != null && (Security.Decimals == 3 || Security.Decimals == 5))
_pipSize *= 10m;
_stopLossOffset = StopLossPips * _pipSize;
_takeProfitOffset = TakeProfitPips * _pipSize;
_trailingStopOffset = TrailingStopPips * _pipSize;
_trailingStepOffset = TrailingStepPips * _pipSize;
_rollbackOffset = RollbackRatePips * _pipSize;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
// Work only with finished candles to emulate the IsNewBar check from the MQL expert.
if (candle.State != CandleStates.Finished)
return;
// Update trailing stops and exit conditions before generating new signals.
ManageOpenPosition(candle);
// Skip signal generation until the strategy is online and allowed to trade.
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Translate the rollback filters from the original EA using candle statistics.
var open = candle.OpenPrice;
var close = candle.ClosePrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
var longCondition = open > close && high - close > _rollbackOffset;
var shortCondition = close > open && close - low > _rollbackOffset;
if (ReverseSignal)
{
(longCondition, shortCondition) = (shortCondition, longCondition);
}
if (longCondition && Position <= 0)
{
// Enter long when the rollback condition is met and the strategy is not already in a long position.
var volume = Volume + Math.Abs(Position);
if (volume <= 0m)
return;
BuyMarket(volume);
InitializeLongState(candle);
}
else if (shortCondition && Position >= 0)
{
// Enter short when the bearish rollback occurs and we are not currently short.
var volume = Volume + Math.Abs(Position);
if (volume <= 0m)
return;
SellMarket(volume);
InitializeShortState(candle);
}
}
private void ManageOpenPosition(ICandleMessage candle)
{
// Mirror the trailing and exit logic from the MetaTrader expert to keep behaviour identical.
if (Position > 0)
{
// Use the candle high as the most optimistic price for long positions.
var extreme = candle.HighPrice;
if (_longEntryPrice == 0m)
// Store the actual entry price once the trade is filled.
_longEntryPrice = candle.ClosePrice;
if (_trailingStopOffset > 0m)
{
// Apply the trailing algorithm for the active position.
// Move the stop only when profit exceeds trailing stop plus step, exactly as in the MQL code.
if (extreme - _longEntryPrice > _trailingStopOffset + _trailingStepOffset)
{
var threshold = extreme - (_trailingStopOffset + _trailingStepOffset);
if (_longStopPrice == 0m || _longStopPrice < threshold)
_longStopPrice = extreme - _trailingStopOffset;
}
}
if (_longTakeProfitPrice > 0m && candle.HighPrice >= _longTakeProfitPrice)
{
// Exit the long position once the take-profit level is touched.
SellMarket(Math.Abs(Position));
ResetLongState();
return;
}
if (_longStopPrice > 0m && candle.LowPrice <= _longStopPrice)
{
// Close the long position if the initial or trailing stop is triggered.
SellMarket(Math.Abs(Position));
ResetLongState();
return;
}
}
else if (Position < 0)
{
// Use the candle low as the best price in favour of the short position.
var extreme = candle.LowPrice;
if (_shortEntryPrice == 0m)
// Capture the short entry price after execution.
_shortEntryPrice = candle.ClosePrice;
if (_trailingStopOffset > 0m)
{
// Apply the trailing algorithm for short positions.
// Move the stop only when profit exceeds trailing stop plus step, exactly as in the MQL code.
if (_shortEntryPrice - extreme > _trailingStopOffset + _trailingStepOffset)
{
var threshold = extreme + (_trailingStopOffset + _trailingStepOffset);
if (_shortStopPrice == 0m || _shortStopPrice > threshold)
_shortStopPrice = extreme + _trailingStopOffset;
}
}
if (_shortTakeProfitPrice > 0m && candle.LowPrice <= _shortTakeProfitPrice)
{
// Exit the short position when the take-profit is hit.
BuyMarket(Math.Abs(Position));
ResetShortState();
return;
}
if (_shortStopPrice > 0m && candle.HighPrice >= _shortStopPrice)
{
// Cover the short position if the stop level is breached.
BuyMarket(Math.Abs(Position));
ResetShortState();
return;
}
}
else
{
// Clear cached levels when no position is open.
ResetLongState();
ResetShortState();
}
}
private void InitializeLongState(ICandleMessage candle)
{
// Clear short-side state because the strategy operates in netting mode.
ResetShortState();
var entry = candle.ClosePrice;
// Save reference prices for managing the long position.
_longEntryPrice = entry;
_longStopPrice = StopLossPips > 0m ? entry - _stopLossOffset : 0m;
_longTakeProfitPrice = TakeProfitPips > 0m ? entry + _takeProfitOffset : 0m;
}
private void InitializeShortState(ICandleMessage candle)
{
// Clear long-side state before opening a short position.
ResetLongState();
var entry = candle.ClosePrice;
// Store price references for the short position.
_shortEntryPrice = entry;
_shortStopPrice = StopLossPips > 0m ? entry + _stopLossOffset : 0m;
_shortTakeProfitPrice = TakeProfitPips > 0m ? entry - _takeProfitOffset : 0m;
}
private void ResetLongState()
{
_longEntryPrice = 0m;
_longStopPrice = 0m;
_longTakeProfitPrice = 0m;
}
private void ResetShortState()
{
_shortEntryPrice = 0m;
_shortStopPrice = 0m;
_shortTakeProfitPrice = 0m;
}
}
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
from datatype_extensions import *
from indicator_extensions import *
class rollback_rebound_strategy(Strategy):
def __init__(self):
super(rollback_rebound_strategy, self).__init__()
self._sl_pips = self.Param("StopLossPips", 30.0).SetNotNegative().SetDisplay("Stop Loss (pips)", "SL distance", "Risk")
self._tp_pips = self.Param("TakeProfitPips", 90.0).SetNotNegative().SetDisplay("Take Profit (pips)", "TP distance", "Risk")
self._trailing_stop_pips = self.Param("TrailingStopPips", 20.0).SetNotNegative().SetDisplay("Trailing Stop (pips)", "Trailing offset", "Risk")
self._trailing_step_pips = self.Param("TrailingStepPips", 15.0).SetNotNegative().SetDisplay("Trailing Step (pips)", "Trailing step", "Risk")
self._rollback_pips = self.Param("RollbackRatePips", 40.0).SetNotNegative().SetDisplay("Rollback Threshold (pips)", "Pullback threshold", "Signal")
self._reverse_signal = self.Param("ReverseSignal", False).SetDisplay("Reverse Signal", "Invert entry logic", "Signal")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))).SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(rollback_rebound_strategy, self).OnReseted()
self._pip_size = 0
self._long_entry = 0
self._long_stop = 0
self._long_tp = 0
self._short_entry = 0
self._short_stop = 0
self._short_tp = 0
def OnStarted2(self, time):
super(rollback_rebound_strategy, self).OnStarted2(time)
self._pip_size = 1.0
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
self._pip_size = float(self.Security.PriceStep)
if self.Security.Decimals == 3 or self.Security.Decimals == 5:
self._pip_size *= 10.0
self._sl_offset = self._sl_pips.Value * self._pip_size
self._tp_offset = self._tp_pips.Value * self._pip_size
self._trail_offset = self._trailing_stop_pips.Value * self._pip_size
self._trail_step_offset = self._trailing_step_pips.Value * self._pip_size
self._rollback_offset = self._rollback_pips.Value * self._pip_size
self._long_entry = 0
self._long_stop = 0
self._long_tp = 0
self._short_entry = 0
self._short_stop = 0
self._short_tp = 0
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
self._manage_position(candle)
o = candle.OpenPrice
c = candle.ClosePrice
h = candle.HighPrice
l = candle.LowPrice
long_cond = o > c and h - c > self._rollback_offset
short_cond = c > o and c - l > self._rollback_offset
if self._reverse_signal.Value:
long_cond, short_cond = short_cond, long_cond
if long_cond and self.Position <= 0:
vol = self.Volume + abs(self.Position)
if vol <= 0:
return
self.BuyMarket(vol)
self._short_entry = 0
self._short_stop = 0
self._short_tp = 0
self._long_entry = float(c)
self._long_stop = self._long_entry - self._sl_offset if self._sl_pips.Value > 0 else 0
self._long_tp = self._long_entry + self._tp_offset if self._tp_pips.Value > 0 else 0
elif short_cond and self.Position >= 0:
vol = self.Volume + abs(self.Position)
if vol <= 0:
return
self.SellMarket(vol)
self._long_entry = 0
self._long_stop = 0
self._long_tp = 0
self._short_entry = float(c)
self._short_stop = self._short_entry + self._sl_offset if self._sl_pips.Value > 0 else 0
self._short_tp = self._short_entry - self._tp_offset if self._tp_pips.Value > 0 else 0
def _manage_position(self, candle):
if self.Position > 0:
extreme = float(candle.HighPrice)
if self._long_entry == 0:
self._long_entry = float(candle.ClosePrice)
if self._trail_offset > 0 and self._long_entry > 0:
if extreme - self._long_entry > self._trail_offset + self._trail_step_offset:
threshold = extreme - (self._trail_offset + self._trail_step_offset)
if self._long_stop == 0 or self._long_stop < threshold:
self._long_stop = extreme - self._trail_offset
if self._long_tp > 0 and candle.HighPrice >= self._long_tp:
self.SellMarket(abs(self.Position))
self._long_entry = 0
self._long_stop = 0
self._long_tp = 0
return
if self._long_stop > 0 and candle.LowPrice <= self._long_stop:
self.SellMarket(abs(self.Position))
self._long_entry = 0
self._long_stop = 0
self._long_tp = 0
return
elif self.Position < 0:
extreme = float(candle.LowPrice)
if self._short_entry == 0:
self._short_entry = float(candle.ClosePrice)
if self._trail_offset > 0 and self._short_entry > 0:
if self._short_entry - extreme > self._trail_offset + self._trail_step_offset:
threshold = extreme + (self._trail_offset + self._trail_step_offset)
if self._short_stop == 0 or self._short_stop > threshold:
self._short_stop = extreme + self._trail_offset
if self._short_tp > 0 and candle.LowPrice <= self._short_tp:
self.BuyMarket(abs(self.Position))
self._short_entry = 0
self._short_stop = 0
self._short_tp = 0
return
if self._short_stop > 0 and candle.HighPrice >= self._short_stop:
self.BuyMarket(abs(self.Position))
self._short_entry = 0
self._short_stop = 0
self._short_tp = 0
return
else:
self._long_entry = 0
self._long_stop = 0
self._long_tp = 0
self._short_entry = 0
self._short_stop = 0
self._short_tp = 0
def CreateClone(self):
return rollback_rebound_strategy()