The MartinGale Breakout Strategy is a breakout-following system converted from the MetaTrader 4 expert advisor MartinGaleBreakout. The original robot enters positions after detecting abnormally large candles and applies a martingale-style recovery mechanism to regain previous losses. This StockSharp port reproduces the behaviour using the high-level strategy API with candle subscriptions and money-management parameters.
The strategy monitors a configurable candle series, looking for candles whose range is at least three times greater than the average range of the previous ten bars. When such a candle closes strongly in one direction, the strategy opens a market position in that direction. If the position is closed with a loss that exceeds a configurable threshold, the recovery mode increases the take-profit distance to compensate for the realised drawdown.
Trading Logic
Subscribe to the selected candle series (15-minute candles by default).
Maintain the most recent 11 finished candles to evaluate abnormal volatility.
Detect a bullish breakout when:
The current candle is three times larger than the average range of the previous ten candles.
The candle closes in the upper half of its range.
Detect a bearish breakout using the symmetric conditions.
Open a market position in the breakout direction if:
No other position is currently open.
The estimated capital exposure is below the configured balance percentage.
Close positions and reset profit/loss targets when:
Floating profit reaches the take-profit threshold.
Floating loss reaches the stop-loss threshold.
When a stop-loss occurs, switch to recovery mode:
Increase the take-profit distance by the configured multiplier.
Expand the stop-loss limit to the maximum allowed percentage.
Continue trading until the next target is reached, then reset to the base configuration.
Parameters
Name
Description
Default
TakeProfitPoints
Base take-profit distance expressed in instrument points.
50
BalancePercentageAvailable
Maximum share of the account balance that can be allocated to a single trade.
50%
TakeProfitBalancePercent
Target profit as a percentage of account balance.
0.1%
StopLossBalancePercent
Maximum drawdown before triggering recovery.
10%
StartRecoveryFactor
Portion of the stop-loss used before activating recovery mode.
0.2
TakeProfitPointsMultiplier
Multiplier applied to the take-profit distance while recovering.
1
CandleType
Candle series used for breakout calculations.
15-minute
Position Sizing and Risk Control
The strategy calculates the required volume to achieve the configured monetary take-profit using the instrument tick size and tick value.
Volumes are normalised to exchange constraints (step, minimum, maximum).
Estimated capital exposure must not exceed the configured balance percentage.
Recovery mode dynamically expands the take-profit target after a loss, emulating the original martingale behaviour while keeping positions limited to a single open trade.
Notes
The strategy relies on portfolio balance information; initialise it with a portfolio connection before starting.
Commission handling mirrors the original EA by focusing on floating P&L derived from the current position.
No pending orders are used—entries and exits are performed with market orders only.
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()