Die Martingale Breakout Strategy ist eine StockSharp-Portierung des MetaTrader-Expertenberaters MartinGaleBreakout.mq5. Das System
Wartet auf ungewöhnlich große Ausbruchskerzen und platziert eine einzelne Marktorder in Ausbruchsrichtung. Während das Original EA
Verfolgt eine „magische Zahl“, um seine Positionen zu verwalten. Die StockSharp-Implementierung basiert auf dem Strategiekontext, also dem Verhalten
ist praktisch derselbe, wenn die Strategie isoliert ausgeführt wird.
Der Algorithmus konzentriert sich auf zwei Kernideen:
Breakout-Erkennung – die Strategie untersucht die Größe jeder fertigen Kerze und vergleicht sie mit der durchschnittlichen Spanne der Kerze
vorherigen zehn Kerzen. Wenn die aktuelle Spanne dreimal größer als der Durchschnitt ist und die Kerze stark schließt
Je nach Richtung des Ausbruchs wird ein Handelssignal erzeugt.
Erholung im Martingale-Stil – die Strategie verfolgt schwankende Gewinne und Verluste. Immer wenn der nicht realisierte PnL den erreicht
Bei konfigurierter Verlustschwelle werden sofort alle offenen Positionen geschlossen und das nächste Gewinnziel, also der folgende Trade, erhöht
versucht, den Schaden auszugleichen. Sobald das erhöhte Ziel erreicht ist, werden die Schwellenwerte auf die ursprünglichen Werte zurückgesetzt.
Der Port behält alle Geldverwaltungsparameter aus dem MQL5-Code bei, einschließlich des für die Marge reservierten Saldoprozentsatzes
prozentuale Gewinn- und Verlustziele und der Multiplikator, der die Take-Profit-Distanz während der Erholungsphase erweitert.
Handelslogik
Abonnieren Sie die konfigurierte Kerzenserie und warten Sie auf fertige Kerzen.
Berechnen Sie den Kerzenbereich (High - Low) und pflegen Sie einen Puffer fester Größe mit den vorherigen zehn Bereichen, um den zu bestimmen
Referenzdurchschnitt, der zur Ausbruchserkennung verwendet wird.
Berechnen Sie den variablen PnL, indem Sie die durchschnittlichen Einstiegspreise für die Long- und Short-Seiten verfolgen. Wenn der nicht realisierte PnL den überschreitet
Wenn das Gewinnziel erreicht ist oder die Stop-Loss-Schwelle überschritten wird, schließen Sie sofort alle Positionen und setzen Sie den Wiederherstellungsstatus wie in der Abbildung zurück
ursprünglicher Fachberater.
Überspringen Sie die Auftragserteilung, während die Strategie bereits eine Position hält oder wenn der Verbindungsstatus den Handel nicht zulässt.
Wenn eine zinsbullische Ausbruchskerze erscheint, legen Sie die Größe der Order so fest, dass der erwartete Gewinn dem aktuellen Ziel entspricht. Der Take-Profit
Die Entfernung in Preisschritten wird während der Wiederherstellung multipliziert, genau wie der Parameter TP_Points_Multiplier aus dem Parameter EA.
Validieren Sie das berechnete Volumen anhand der Gerätegrenzen (Minimum, Maximum und Schritt) und stellen Sie sicher, dass die erforderliche Marge eingehalten wird
die konfigurierte Guthabenzuteilung oder die verfügbaren freien Mittel nicht überschreitet. Wenn die Einschränkungen eingehalten werden, reichen Sie eine ein
Marktkaufauftrag.
Wiederholen Sie den gleichen Vorgang für rückläufige Ausbrüche und erteilen Sie stattdessen einen Marktverkaufsauftrag.
Die Kombination dieser Regeln stellt das Verhalten des ursprünglichen MetaTrader-Systems wieder her, einschließlich des Übergangs hinein und heraus
des Erholungsmodus nach einem Stop-Loss-Ereignis.
Parameter
Parameter
Beschreibung
Standard
TakeProfitPoints
Abstand zwischen Einstiegspreis und Take-Profit-Preis, ausgedrückt in Preisschritten.
50
BalancePercentAvailable
Maximaler Prozentsatz des Kontostands, der für die Marge bei einem einzelnen Trade reserviert werden kann.
50
TakeProfitPercentOfBalance
Zielgewinn ausgedrückt als Prozentsatz des aktuellen Saldos.
0.1
StopLossPercentOfBalance
Stop-Loss-Größe, ausgedrückt als Prozentsatz des aktuellen Saldos.
10
RecoveryStartFraction
Bruchteil des Stop-Loss, der vor dem Wechsel in den Wiederherstellungsmodus verwendet wurde.
0.1
RecoveryPointsMultiplier
Multiplikator, der während der Erholung auf die Take-Profit-Distanz angewendet wird.
1
CandleType
Von der Strategie verwendete Kerzendatenquelle (Zeitrahmen, Tick-Kerzen usw.).
15-minute time frame
Zusätzliche Hinweise
Die Volumenberechnung repliziert den MetaTrader-Helfer CalcLotWithTP. Daraus wird die Losgröße abgeleitet, die erforderlich ist, um den aktuellen Wert zu erreichen
Gewinnziel für eine bestimmte Preisbewegung und normalisiert das Ergebnis dann auf den Volumenschritt des Instruments.
Margin-Prüfungen werden nach dem gleichen Prinzip wie CheckVolumeValue und dem in MQL verwendeten Balance-Prozentsatz-Filter durchgeführt.
Version. Aufträge werden abgelehnt, wenn die erforderliche Marge den zulässigen Anteil des Guthabens oder die von gemeldeten freien Mittel übersteigt
Das Portfolio.
Die Strategie storniert alle aktiven Orders, bevor die Positionen reduziert werden, sodass das Verhalten dem CloseAllOrders-Helfer von entspricht
der ursprüngliche Fachberater.
Der interne Bereichspuffer speichert nur zehn Werte und entspricht der Iteration über iHigh/iLow in der Quelle EA. Nein
Es sind historische Daten erforderlich, die über die letzten zehn Kerzen hinausgehen.
using System;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Breakout strategy that detects abnormally large candles and enters in the breakout direction.
/// Uses a simple martingale recovery: after a losing trade, the next entry is taken more aggressively.
/// </summary>
public class MartingaleBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _lookback;
private readonly StrategyParam<decimal> _breakoutMultiplier;
private readonly StrategyParam<decimal> _takeProfitPct;
private readonly StrategyParam<decimal> _stopLossPct;
private readonly StrategyParam<DataType> _candleType;
private readonly decimal[] _rangeBuffer = new decimal[10];
private int _rangeBufferCount;
private int _rangeBufferIndex;
private decimal _rangeBufferSum;
private decimal _entryPrice;
private Sides? _entrySide;
private bool _lastWasLoss;
public int Lookback
{
get => _lookback.Value;
set => _lookback.Value = value;
}
public decimal BreakoutMultiplier
{
get => _breakoutMultiplier.Value;
set => _breakoutMultiplier.Value = value;
}
public decimal TakeProfitPct
{
get => _takeProfitPct.Value;
set => _takeProfitPct.Value = value;
}
public decimal StopLossPct
{
get => _stopLossPct.Value;
set => _stopLossPct.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public MartingaleBreakoutStrategy()
{
_lookback = Param(nameof(Lookback), 10)
.SetDisplay("Lookback", "Number of candles for average range", "General");
_breakoutMultiplier = Param(nameof(BreakoutMultiplier), 3m)
.SetDisplay("Breakout Mult", "Multiplier above avg range for breakout", "General");
_takeProfitPct = Param(nameof(TakeProfitPct), 1m)
.SetDisplay("Take Profit %", "Take profit as percentage of entry price", "Trading");
_stopLossPct = Param(nameof(StopLossPct), 0.5m)
.SetDisplay("Stop Loss %", "Stop loss as percentage of entry price", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle type", "General");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rangeBufferCount = 0;
_rangeBufferIndex = 0;
_rangeBufferSum = 0m;
_entryPrice = 0m;
_entrySide = null;
_lastWasLoss = 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 closePrice = candle.ClosePrice;
// Check exit conditions first
if (Position != 0 && _entryPrice > 0)
{
var tp = _lastWasLoss ? TakeProfitPct * 1.5m : TakeProfitPct;
var sl = StopLossPct;
if (_entrySide == Sides.Buy)
{
var pnlPct = (closePrice - _entryPrice) / _entryPrice * 100m;
if (pnlPct >= tp || pnlPct <= -sl)
{
_lastWasLoss = pnlPct < 0;
SellMarket();
_entryPrice = 0;
_entrySide = null;
UpdateRangeStatistics(candle);
return;
}
}
else if (_entrySide == Sides.Sell)
{
var pnlPct = (_entryPrice - closePrice) / _entryPrice * 100m;
if (pnlPct >= tp || pnlPct <= -sl)
{
_lastWasLoss = pnlPct < 0;
BuyMarket();
_entryPrice = 0;
_entrySide = null;
UpdateRangeStatistics(candle);
return;
}
}
}
// Entry logic - only when flat
if (Position == 0)
{
var range = candle.HighPrice - candle.LowPrice;
if (_rangeBufferCount >= _rangeBuffer.Length)
{
var avgRange = _rangeBufferSum / _rangeBuffer.Length;
if (range > avgRange * BreakoutMultiplier)
{
var body = candle.ClosePrice - candle.OpenPrice;
if (body > 0 && body > range * 0.4m)
{
// Bullish breakout
BuyMarket();
_entryPrice = closePrice;
_entrySide = Sides.Buy;
}
else if (body < 0 && Math.Abs(body) > range * 0.4m)
{
// Bearish breakout
SellMarket();
_entryPrice = closePrice;
_entrySide = Sides.Sell;
}
}
}
}
UpdateRangeStatistics(candle);
}
private void UpdateRangeStatistics(ICandleMessage candle)
{
var range = candle.HighPrice - candle.LowPrice;
if (_rangeBufferCount < _rangeBuffer.Length)
{
_rangeBuffer[_rangeBufferIndex] = range;
_rangeBufferSum += range;
_rangeBufferCount++;
_rangeBufferIndex = (_rangeBufferIndex + 1) % _rangeBuffer.Length;
return;
}
_rangeBufferSum -= _rangeBuffer[_rangeBufferIndex];
_rangeBuffer[_rangeBufferIndex] = range;
_rangeBufferSum += range;
_rangeBufferIndex = (_rangeBufferIndex + 1) % _rangeBuffer.Length;
}
/// <inheritdoc />
protected override void OnReseted()
{
Array.Clear(_rangeBuffer);
_rangeBufferCount = 0;
_rangeBufferIndex = 0;
_rangeBufferSum = 0m;
_entryPrice = 0m;
_entrySide = null;
_lastWasLoss = 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 martingale_breakout_strategy(Strategy):
def __init__(self):
super(martingale_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._lookback = self.Param("Lookback", 10)
self._breakout_multiplier = self.Param("BreakoutMultiplier", 3.0)
self._take_profit_pct = self.Param("TakeProfitPct", 1.0)
self._stop_loss_pct = self.Param("StopLossPct", 0.5)
self._range_buffer = [0.0] * 10
self._range_buffer_count = 0
self._range_buffer_index = 0
self._range_buffer_sum = 0.0
self._entry_price = 0.0
self._entry_side = 0
self._last_was_loss = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def Lookback(self):
return self._lookback.Value
@Lookback.setter
def Lookback(self, value):
self._lookback.Value = value
@property
def BreakoutMultiplier(self):
return self._breakout_multiplier.Value
@BreakoutMultiplier.setter
def BreakoutMultiplier(self, value):
self._breakout_multiplier.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
def OnReseted(self):
super(martingale_breakout_strategy, self).OnReseted()
self._range_buffer = [0.0] * 10
self._range_buffer_count = 0
self._range_buffer_index = 0
self._range_buffer_sum = 0.0
self._entry_price = 0.0
self._entry_side = 0
self._last_was_loss = False
def OnStarted2(self, time):
super(martingale_breakout_strategy, self).OnStarted2(time)
self._range_buffer = [0.0] * 10
self._range_buffer_count = 0
self._range_buffer_index = 0
self._range_buffer_sum = 0.0
self._entry_price = 0.0
self._entry_side = 0
self._last_was_loss = False
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _update_range_statistics(self, candle):
r = float(candle.HighPrice) - float(candle.LowPrice)
buf_len = len(self._range_buffer)
if self._range_buffer_count < buf_len:
self._range_buffer[self._range_buffer_index] = r
self._range_buffer_sum += r
self._range_buffer_count += 1
self._range_buffer_index = (self._range_buffer_index + 1) % buf_len
return
self._range_buffer_sum -= self._range_buffer[self._range_buffer_index]
self._range_buffer[self._range_buffer_index] = r
self._range_buffer_sum += r
self._range_buffer_index = (self._range_buffer_index + 1) % buf_len
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
buf_len = len(self._range_buffer)
# Check exit conditions first
if self.Position != 0 and self._entry_price > 0:
tp = float(self.TakeProfitPct) * 1.5 if self._last_was_loss else float(self.TakeProfitPct)
sl = float(self.StopLossPct)
if self._entry_side == 1:
pnl_pct = (close - self._entry_price) / self._entry_price * 100.0
if pnl_pct >= tp or pnl_pct <= -sl:
self._last_was_loss = pnl_pct < 0
self.SellMarket()
self._entry_price = 0.0
self._entry_side = 0
self._update_range_statistics(candle)
return
elif self._entry_side == -1:
pnl_pct = (self._entry_price - close) / self._entry_price * 100.0
if pnl_pct >= tp or pnl_pct <= -sl:
self._last_was_loss = pnl_pct < 0
self.BuyMarket()
self._entry_price = 0.0
self._entry_side = 0
self._update_range_statistics(candle)
return
# Entry logic - only when flat
if self.Position == 0:
r = float(candle.HighPrice) - float(candle.LowPrice)
if self._range_buffer_count >= buf_len:
avg_range = self._range_buffer_sum / buf_len
if r > avg_range * float(self.BreakoutMultiplier):
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._update_range_statistics(candle)
def CreateClone(self):
return martingale_breakout_strategy()