Estrategia de Retroceso y Rebote
Descripción general
La Estrategia de Retroceso y Rebote es una conversión C# del asesor experto MQL5 "TST (barabashkakvn's edition)". Monitorea un único instrumento en el marco temporal especificado por el parámetro CandleType y busca movimientos fuertes que retrocedan de vuelta dentro del rango de la barra. Cuando una barra alcista se desvanece desde su máximo más del umbral de retroceso, la estrategia compra, mientras que un retroceso bajista equivalente desencadena una venta. La implementación usa la API de suscripción de velas de alto nivel de StockSharp y gestiona todas las órdenes de protección en unidades de pip que se convierten en desplazamientos de precio absolutos.
Las distancias en pip se calculan desde el PriceStep del instrumento. Para los símbolos que cotizan con tres o cinco decimales, la estrategia automáticamente multiplica el paso por diez para coincidir con la definición de pip de MetaTrader. Todo el dimensionamiento de posición se toma de la propiedad base Volume de la estrategia.
Lógica de entrada
- Procesar solo velas terminadas de la serie
CandleType configurada.
- Con
ReverseSignal = false (predeterminado):
- Configuración larga: la vela cierra por debajo de su apertura y la diferencia entre el máximo de la vela y el cierre excede
RollbackRatePips (convertido a precio). Esto indica que el precio se expandió hacia arriba y luego retrocedió lo suficiente para calificar para una entrada contraria larga.
- Configuración corta: la vela cierra por encima de su apertura y la diferencia entre el cierre y el mínimo de la vela excede
RollbackRatePips. Esto refleja la lógica larga en el lado bajista.
- Cuando
ReverseSignal = true, los roles de las condiciones larga y corta se intercambian, permitiendo al trader cambiar la dirección sin cambiar los otros parámetros.
- Las nuevas entradas solo se colocan cuando la posición actual está plana o en la dirección opuesta. El volumen ejecutado es igual a
Volume + |Position| para que una posición opuesta se cierre antes de establecer el nuevo trade.
Lógica de salida
- En la entrada, la estrategia almacena los niveles de stop-loss y take-profit basados en los desplazamientos de pip configurados. Cuando el rango de la vela toca un nivel, la posición se cierra con una orden de mercado.
StopLossPips = 0 o TakeProfitPips = 0 deshabilita el nivel de protección correspondiente.
- La lógica de trailing se activa una vez que la ganancia flotante supera
TrailingStopPips + TrailingStepPips (en términos de precio).
- Para trades largos, el stop se mueve escalonadamente a
precio más alto - TrailingStopPips siempre que el nuevo nivel esté al menos TrailingStepPips por encima del stop anterior.
- Para trades cortos, el stop se mueve escalonadamente a
precio más bajo + TrailingStopPips cuando el nuevo nivel está al menos TrailingStepPips por debajo del stop anterior.
- Si el mercado se revierte y cruza el trailing stop, la posición se cierra inmediatamente.
- Cuando no hay posición abierta, todas las variables de estado internas se borran para evitar datos obsoletos.
Parámetros
| Parámetro |
Descripción |
Valor predeterminado |
CandleType |
Serie de velas utilizada para el cálculo de señales. |
Marco temporal de 15 minutos |
StopLossPips |
Distancia del stop de protección en pips. Establecer en cero para deshabilitar. |
30 |
TakeProfitPips |
Distancia del take-profit en pips. Establecer en cero para deshabilitar. |
90 |
TrailingStopPips |
Offset del trailing stop en pips. Establecer en cero para deshabilitar el trailing. |
1 |
TrailingStepPips |
Ganancia extra (en pips) requerida antes de que el trailing stop pueda moverse de nuevo. Debe ser positivo cuando el trailing está habilitado. |
15 |
RollbackRatePips |
Retroceso mínimo desde el extremo de la barra que valida una señal. |
15 |
ReverseSignal |
Invierte la dirección de entrada (las señales largas se convierten en cortas y viceversa). |
false |
Notas de uso
- Establecer la propiedad
Volume antes de iniciar la estrategia; define la cantidad operada para cada orden.
- El trailing requiere
TrailingStopPips > 0 y TrailingStepPips > 0. La estrategia lanza un error al inicio si esta relación se viola.
- Debido a que el experto original evaluaba ticks dentro de la barra activa, el porto C# usa el máximo/mínimo/cierre de la vela terminada para aproximar el mismo comportamiento. La diferencia es insignificante para la mayoría de los backtests y mantiene la implementación alineada con la API de alto nivel de StockSharp.
- La estrategia funciona con un solo instrumento. Para operar múltiples instrumentos, crear instancias de estrategia separadas.
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()