A Estratégia de Breakout MartinGale é um sistema de acompanhamento de breakout convertido do consultor especialista MetaTrader 4 MartinGaleBreakout. O robô original entra em posições após detectar velas anormalmente grandes e aplica um mecanismo de recuperação estilo martingale para recuperar perdas anteriores. Esta porta StockSharp reproduz o comportamento usando a estratégia de alto nível API com assinaturas de velas e parâmetros de gerenciamento de dinheiro.
A estratégia monitora uma série de velas configuráveis, procurando velas cujo intervalo seja pelo menos três vezes maior que o intervalo médio das dez barras anteriores. Quando tal vela fecha fortemente em uma direção, a estratégia abre uma posição de mercado nessa direção. Se a posição for fechada com uma perda que exceda um limite configurável, o modo de recuperação aumenta a distância de obtenção de lucro para compensar o rebaixamento realizado.
Lógica de negociação
Assine a série de velas selecionada (velas de 15 minutos por padrão).
Mantenha as 11 velas concluídas mais recentes para avaliar a volatilidade anormal.
Detecte um rompimento de alta quando:
A vela atual é três vezes maior que o intervalo médio das dez velas anteriores.
A vela fecha na metade superior do seu alcance.
Detecte um rompimento de baixa usando condições simétricas.
Abra uma posição de mercado na direção do rompimento se:
Nenhuma outra posição está aberta no momento.
A exposição de capital estimada está abaixo do percentual de saldo configurado.
Fechar posições e redefinir metas de lucros/perdas quando:
O lucro flutuante atinge o limite de lucro.
A perda flutuante atinge o limite de stop loss.
Quando ocorrer um stop loss, mude para o modo de recuperação:
Aumente a distância de lucro pelo multiplicador configurado.
Expanda o limite de stop loss para a porcentagem máxima permitida.
Continue negociando até que a próxima meta seja alcançada e, em seguida, redefina para a configuração básica.
Parâmetros
Nome
Descrição
Padrão
TakeProfitPoints
Distância básica de take-profit expressa em pontos de instrumento.
50
BalancePercentageAvailable
Parcela máxima do saldo da conta que pode ser alocada para uma única negociação.
50%
TakeProfitBalancePercent
Lucro alvo como porcentagem do saldo da conta.
0.1%
StopLossBalancePercent
Rebaixamento máximo antes de acionar a recuperação.
10%
StartRecoveryFactor
Parte do stop loss usada antes de ativar o modo de recuperação.
0.2
TakeProfitPointsMultiplier
Multiplicador aplicado à distância de lucro durante a recuperação.
1
CandleType
Série de velas usada para cálculos de breakout.
15-minute
Dimensionamento de posição e controle de risco
A estratégia calcula o volume necessário para atingir o take-profit monetário configurado usando o tamanho e o valor do tick do instrumento.
Os volumes são normalizados para as restrições de troca (passo, mínimo, máximo).
A exposição de capital estimada não deve exceder o percentual de saldo configurado.
O modo de recuperação expande dinamicamente a meta de lucro após uma perda, emulando o comportamento original do martingale enquanto mantém as posições limitadas a uma única negociação aberta.
Notas
A estratégia depende de informações sobre o saldo da carteira; inicialize-o com uma conexão de portfólio antes de começar.
O tratamento de comissões reflete o EA original, concentrando-se em lucros e perdas flutuantes derivados da posição atual.
Não são utilizadas ordens pendentes – as entradas e saídas são realizadas apenas com ordens 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()