La Estrategia de ruptura de MartinGale es un sistema de seguimiento de rupturas convertido del MetaTrader 4 asesor experto MartinGaleBreakout. El robot original ingresa a posiciones después de detectar velas anormalmente grandes y aplica un mecanismo de recuperación estilo martingala para recuperar pérdidas anteriores. Este puerto StockSharp reproduce el comportamiento utilizando la estrategia de alto nivel API con suscripciones de velas y parámetros de administración de dinero.
La estrategia monitorea una serie de velas configurables, buscando velas cuyo rango sea al menos tres veces mayor que el rango promedio de las diez barras anteriores. Cuando dicha vela se cierra con fuerza en una dirección, la estrategia abre una posición de mercado en esa dirección. Si la posición se cierra con una pérdida que excede un umbral configurable, el modo de recuperación aumenta la distancia de toma de ganancias para compensar la reducción realizada.
Lógica de trading
Suscríbete a la serie de velas seleccionada (velas de 15 minutos por defecto).
Mantenga las 11 velas terminadas más recientes para evaluar una volatilidad anormal.
Detecta una ruptura alcista cuando:
La vela actual es tres veces mayor que el rango promedio de las diez velas anteriores.
La vela cierra en la mitad superior de su rango.
Detecte una ruptura bajista utilizando las condiciones simétricas.
Abra una posición de mercado en la dirección de ruptura si:
Actualmente no hay ningún otro puesto disponible.
La exposición de capital estimada está por debajo del porcentaje de saldo configurado.
Cerrar posiciones y restablecer objetivos de pérdidas/ganancias cuando:
El beneficio flotante alcanza el umbral de obtención de beneficios.
La pérdida flotante alcanza el umbral de stop-loss.
Cuando se produzca un stop-loss, cambie al modo de recuperación:
Aumente la distancia de toma de ganancias mediante el multiplicador configurado.
Amplíe el límite de stop-loss al porcentaje máximo permitido.
Continúe operando hasta alcanzar el siguiente objetivo, luego restablezca la configuración base.
Parámetros
Nombre
Descripción
Predeterminado
TakeProfitPoints
Distancia base de toma de ganancias expresada en puntos del instrumento.
50
BalancePercentageAvailable
Participación máxima del saldo de la cuenta que se puede asignar a una sola operación.
50%
TakeProfitBalancePercent
Beneficio objetivo como porcentaje del saldo de la cuenta.
0.1%
StopLossBalancePercent
Reducción máxima antes de desencadenar la recuperación.
10%
StartRecoveryFactor
Parte del stop-loss utilizado antes de activar el modo de recuperación.
0.2
TakeProfitPointsMultiplier
Multiplicador aplicado a la distancia de obtención de beneficios durante la recuperación.
1
CandleType
Serie de velas utilizadas para cálculos de ruptura.
15-minute
Dimensionamiento de posiciones y control de riesgos
La estrategia calcula el volumen requerido para lograr la toma de ganancias monetaria configurada utilizando el tamaño y el valor del tick del instrumento.
Los volúmenes están normalizados para las restricciones de intercambio (paso, mínimo, máximo).
La exposición de capital estimada no debe exceder el porcentaje de saldo configurado.
El modo de recuperación expande dinámicamente el objetivo de obtención de ganancias después de una pérdida, emulando el comportamiento martingala original mientras mantiene las posiciones limitadas a una única operación abierta.
Notas
La estrategia se basa en la información del saldo de la cartera; inicialícelo con una conexión de cartera antes de comenzar.
El manejo de comisiones refleja el EA original al centrarse en las pérdidas y ganancias flotantes derivadas de la posición actual.
No se utilizan órdenes pendientes; las entradas y salidas se realizan únicamente con órdenes de mercado.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Breakout strategy with martingale-style recovery.
/// Detects abnormally large candles relative to recent history and enters in the breakout direction.
/// After a stop-loss, enters recovery mode with a wider take-profit target.
/// </summary>
public class MartinGaleBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _requiredHistory;
private readonly StrategyParam<decimal> _breakoutFactor;
private readonly StrategyParam<decimal> _takeProfitPct;
private readonly StrategyParam<decimal> _stopLossPct;
private readonly StrategyParam<decimal> _recoveryMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _ranges = new();
private decimal _entryPrice;
private Sides? _entrySide;
private bool _recovering;
public int RequiredHistory
{
get => _requiredHistory.Value;
set => _requiredHistory.Value = value;
}
public decimal BreakoutFactor
{
get => _breakoutFactor.Value;
set => _breakoutFactor.Value = value;
}
public decimal TakeProfitPct
{
get => _takeProfitPct.Value;
set => _takeProfitPct.Value = value;
}
public decimal StopLossPct
{
get => _stopLossPct.Value;
set => _stopLossPct.Value = value;
}
public decimal RecoveryMultiplier
{
get => _recoveryMultiplier.Value;
set => _recoveryMultiplier.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public MartinGaleBreakoutStrategy()
{
_requiredHistory = Param(nameof(RequiredHistory), 10)
.SetDisplay("Lookback", "Number of candles for average range", "General");
_breakoutFactor = Param(nameof(BreakoutFactor), 2.5m)
.SetDisplay("Breakout Factor", "Multiplier for abnormal candle detection", "General");
_takeProfitPct = Param(nameof(TakeProfitPct), 0.5m)
.SetDisplay("TP %", "Take profit percent of entry", "Trading");
_stopLossPct = Param(nameof(StopLossPct), 0.3m)
.SetDisplay("SL %", "Stop loss percent of entry", "Trading");
_recoveryMultiplier = Param(nameof(RecoveryMultiplier), 1.5m)
.SetDisplay("Recovery Mult", "TP multiplier during recovery", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle series", "General");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ranges.Clear();
_entryPrice = 0;
_entrySide = null;
_recovering = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var range = candle.HighPrice - candle.LowPrice;
// Check exit
if (Position != 0 && _entryPrice > 0)
{
var tpPct = _recovering ? TakeProfitPct * RecoveryMultiplier : TakeProfitPct;
if (_entrySide == Sides.Buy)
{
var pnl = (close - _entryPrice) / _entryPrice * 100m;
if (pnl >= tpPct || pnl <= -StopLossPct)
{
var wasLoss = pnl < 0;
SellMarket();
_entryPrice = 0;
_entrySide = null;
_recovering = wasLoss;
AddRange(range);
return;
}
}
else if (_entrySide == Sides.Sell)
{
var pnl = (_entryPrice - close) / _entryPrice * 100m;
if (pnl >= tpPct || pnl <= -StopLossPct)
{
var wasLoss = pnl < 0;
BuyMarket();
_entryPrice = 0;
_entrySide = null;
_recovering = wasLoss;
AddRange(range);
return;
}
}
}
// Entry - only when flat
if (Position == 0 && _ranges.Count >= RequiredHistory)
{
decimal sum = 0;
for (int i = 0; i < _ranges.Count; i++)
sum += _ranges[i];
var avgRange = sum / _ranges.Count;
if (avgRange > 0 && range > avgRange * BreakoutFactor)
{
var body = candle.ClosePrice - candle.OpenPrice;
if (body > 0 && body > range * 0.4m)
{
BuyMarket();
_entryPrice = close;
_entrySide = Sides.Buy;
}
else if (body < 0 && Math.Abs(body) > range * 0.4m)
{
SellMarket();
_entryPrice = close;
_entrySide = Sides.Sell;
}
}
}
AddRange(range);
}
private void AddRange(decimal range)
{
_ranges.Add(range);
while (_ranges.Count > RequiredHistory)
_ranges.RemoveAt(0);
}
/// <inheritdoc />
protected override void OnReseted()
{
_ranges.Clear();
_entryPrice = 0;
_entrySide = null;
_recovering = false;
base.OnReseted();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class martin_gale_breakout_strategy(Strategy):
def __init__(self):
super(martin_gale_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._required_history = self.Param("RequiredHistory", 10)
self._breakout_factor = self.Param("BreakoutFactor", 2.5)
self._take_profit_pct = self.Param("TakeProfitPct", 0.5)
self._stop_loss_pct = self.Param("StopLossPct", 0.3)
self._recovery_multiplier = self.Param("RecoveryMultiplier", 1.5)
self._ranges = []
self._entry_price = 0.0
self._entry_side = 0
self._recovering = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RequiredHistory(self):
return self._required_history.Value
@RequiredHistory.setter
def RequiredHistory(self, value):
self._required_history.Value = value
@property
def BreakoutFactor(self):
return self._breakout_factor.Value
@BreakoutFactor.setter
def BreakoutFactor(self, value):
self._breakout_factor.Value = value
@property
def TakeProfitPct(self):
return self._take_profit_pct.Value
@TakeProfitPct.setter
def TakeProfitPct(self, value):
self._take_profit_pct.Value = value
@property
def StopLossPct(self):
return self._stop_loss_pct.Value
@StopLossPct.setter
def StopLossPct(self, value):
self._stop_loss_pct.Value = value
@property
def RecoveryMultiplier(self):
return self._recovery_multiplier.Value
@RecoveryMultiplier.setter
def RecoveryMultiplier(self, value):
self._recovery_multiplier.Value = value
def OnReseted(self):
super(martin_gale_breakout_strategy, self).OnReseted()
self._ranges = []
self._entry_price = 0.0
self._entry_side = 0
self._recovering = False
def OnStarted2(self, time):
super(martin_gale_breakout_strategy, self).OnStarted2(time)
self._ranges = []
self._entry_price = 0.0
self._entry_side = 0
self._recovering = False
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _add_range(self, r):
self._ranges.append(r)
req = self.RequiredHistory
while len(self._ranges) > req:
self._ranges.pop(0)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
r = float(candle.HighPrice) - float(candle.LowPrice)
# Check exit
if self.Position != 0 and self._entry_price > 0:
tp_pct = float(self.TakeProfitPct) * float(self.RecoveryMultiplier) if self._recovering else float(self.TakeProfitPct)
sl_pct = float(self.StopLossPct)
if self._entry_side == 1:
pnl = (close - self._entry_price) / self._entry_price * 100.0
if pnl >= tp_pct or pnl <= -sl_pct:
was_loss = pnl < 0
self.SellMarket()
self._entry_price = 0.0
self._entry_side = 0
self._recovering = was_loss
self._add_range(r)
return
elif self._entry_side == -1:
pnl = (self._entry_price - close) / self._entry_price * 100.0
if pnl >= tp_pct or pnl <= -sl_pct:
was_loss = pnl < 0
self.BuyMarket()
self._entry_price = 0.0
self._entry_side = 0
self._recovering = was_loss
self._add_range(r)
return
# Entry - only when flat
req = self.RequiredHistory
if self.Position == 0 and len(self._ranges) >= req:
total = sum(self._ranges)
avg_range = total / len(self._ranges)
if avg_range > 0 and r > avg_range * float(self.BreakoutFactor):
body = float(candle.ClosePrice) - float(candle.OpenPrice)
if body > 0 and body > r * 0.4:
self.BuyMarket()
self._entry_price = close
self._entry_side = 1
elif body < 0 and abs(body) > r * 0.4:
self.SellMarket()
self._entry_price = close
self._entry_side = -1
self._add_range(r)
def CreateClone(self):
return martin_gale_breakout_strategy()