Typische Breakout-Strategie für den Montag
Überblick
Die Monday-Typical-Breakout-Strategie ist eine C#-Portierung des MetaTrader-Expertenberaters yi1ywioff50qr6 (Repository-ID 8187). Der ursprüngliche Roboter überwacht stündliche Kerzen und eröffnet jeden Montag eine Long-Position, wenn die neue Sitzung über dem typischen Preis des vorherigen Balkens (high + low + close) / 3 beginnt. Diese Implementierung reproduziert die Einstiegslogik innerhalb des StockSharp-Strategierahmens auf hoher Ebene und fügt detaillierte Konfigurationsparameter für die Positionsgröße und Risikokontrolle hinzu.
Handelslogik
- Die Strategie abonniert die konfigurierte Kerzenserie (standardmäßig stündlich).
- Zu Beginn jeder fertigen Kerze wird geprüft, ob:
- Die Kerze gehört zum Montag.
- Die Öffnungszeit der Kerze entspricht dem konfigurierten Parameter Open Hour (Standard 09:00).
- Es sind keine offenen Positionen oder aktiven Aufträge vorhanden.
- Der Eröffnungspreis der Kerze ist höher als der typische Preis des vorherigen Balkens.
- Wenn alle Bedingungen erfüllt sind, sendet die Strategie eine Marktkauforder mit einem vom Money-Management-Block berechneten Volumen. Durch
StartProtection werden schützende Stop-Loss- und Take-Profit-Distanzen angewendet.
Die Strategie eröffnet niemals Short-Positionen und platziert nur einen Trade pro qualifizierter Montagskerze.
Parameter
| Parameter |
Beschreibung |
Standard |
FixedVolume |
Losgröße für Einträge. Auf 0 setzen, um die Equity-Skalierungstabelle zu aktivieren. |
0.1 |
OpenHour |
Handelssitzungsstunde (0-23), in der Montagssignale ausgewertet werden. |
9 |
StopLossPoints |
Abstand in Preispunkten für den Schutzstopp. 0 deaktiviert den Stopp. |
50 |
TakeProfitPoints |
Abstand in Preispunkten zum Gewinnziel. 0 deaktiviert das Ziel. |
20 |
InitialEquity |
Eigenkapitalschwelle, die die eigenkapitalbasierte Losskalierung aktiviert. |
600 |
EquityStep |
Zur Erhöhung der Handelsgröße ist eine Eigenkapitalerhöhung erforderlich. |
300 |
InitialStepVolume |
Die Losgröße wird verwendet, wenn das Eigenkapital mindestens InitialEquity beträgt. |
0.4 |
VolumeStep |
Zusätzliche Losgröße für jeden erreichten EquityStep hinzugefügt. |
0.2 |
CandleType |
Kerzendatentyp, der die Strategie steuert (standardmäßig stündlich). |
1 hour time-frame |
Money-Management
- Wenn
FixedVolume größer als Null ist, verwendet die Strategie immer die feste Losgröße.
- Wenn
FixedVolume gleich Null ist, prüft die Strategie das Portfolio-Eigenkapital:
- Wenn das Eigenkapital unter
InitialEquity liegt, wird die Mindestmenge des Instruments verwendet.
- Andernfalls beginnt das Volumen bei
InitialStepVolume und erhöht sich um VolumeStep für jedes EquityStep zusätzliches Eigenkapital.
- Die endgültige Lautstärke richtet sich nach den Mindest- und Stufenbeschränkungen des Instruments.
Risikomanagement
StartProtection wird während OnStarted aktiviert. Die Stop-Loss- und Take-Profit-Abstände werden mithilfe des Instruments PriceStep automatisch von Punkten in Preis-Offsets übersetzt. Setzen Sie einen der beiden Distanzen auf Null, um diese Komponente zu deaktivieren.
Nutzungshinweise
- Das Original EA ist für Stundenkerzen konzipiert. Bei kürzeren Zeitrahmen können mehrere Montagskerzen zur gleichen Stunde entstehen. Der Port behält das Verhalten eines einzelnen Eintrags pro Kerze bei und ignoriert weiterhin zusätzliche Signale, während eine Position offen ist.
- Stellen Sie sicher, dass die Portfolioinformationen (
Portfolio.CurrentValue) verfügbar sind, wenn der dynamische Größenblock aktiviert ist.
- Die Strategie erfordert Level-1-Daten zur Ausführung von Marktaufträgen und das entsprechende Kerzenabonnement für den konfigurierten
CandleType.
Konvertierungshinweise
- Die Filterung nach magischen Zahlen von MQL wird durch die Positions- und Reihenfolgeprüfungen von StockSharp (
Position und ActiveOrders) ersetzt.
- Zeitvergleiche nutzen
DateTimeOffset aus der Kerzeneröffnungszeit mit .ToLocalTime(), um mit der Chartzeit in Einklang zu bleiben.
- Schutzbestellungen werden vom High-Level-Helper
StartProtection statt einer manuellen Auftragserteilung bearbeitet.
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;
using StockSharp.Algo;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Port of the MetaTrader strategy yi1ywioff50qr6 (ID 8187).
/// Buys on Monday when the hourly open breaks above the prior bar's typical price.
/// Applies equity-based position sizing when the fixed lot is disabled.
/// </summary>
public class MondayTypicalBreakoutStrategy : Strategy
{
private readonly StrategyParam<decimal> _fixedVolume;
private readonly StrategyParam<int> _openHour;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<decimal> _initialEquity;
private readonly StrategyParam<decimal> _equityStep;
private readonly StrategyParam<decimal> _initialStepVolume;
private readonly StrategyParam<decimal> _volumeStep;
private readonly StrategyParam<DataType> _candleType;
private ICandleMessage _previousCandle;
private DateTimeOffset? _lastSignalTime;
private decimal _priceStep;
/// <summary>
/// Initializes parameters to mirror the MQL expert defaults.
/// </summary>
public MondayTypicalBreakoutStrategy()
{
_fixedVolume = Param(nameof(FixedVolume), 0.1m)
.SetNotNegative()
.SetDisplay("Fixed Volume", "Lot size used for entries (set to 0 to enable equity scaling)", "Risk");
_openHour = Param(nameof(OpenHour), 9)
.SetRange(0, 23)
.SetDisplay("Open Hour", "Hour of the session to evaluate Monday breakout entries", "Session");
_stopLossPoints = Param(nameof(StopLossPoints), 50)
.SetNotNegative()
.SetDisplay("Stop Loss (points)", "Protective stop distance expressed in price points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 20)
.SetNotNegative()
.SetDisplay("Take Profit (points)", "Profit target distance expressed in price points", "Risk");
_initialEquity = Param(nameof(InitialEquity), 600m)
.SetGreaterThanZero()
.SetDisplay("Initial Equity", "Account equity threshold that triggers the first scaling tier", "Money Management");
_equityStep = Param(nameof(EquityStep), 300m)
.SetGreaterThanZero()
.SetDisplay("Equity Step", "Incremental equity required to raise the position size", "Money Management");
_initialStepVolume = Param(nameof(InitialStepVolume), 0.4m)
.SetGreaterThanZero()
.SetDisplay("Initial Step Volume", "Lot size used once the equity threshold is met", "Money Management");
_volumeStep = Param(nameof(VolumeStep), 0.2m)
.SetNotNegative()
.SetDisplay("Volume Step", "Additional lot size added for each equity step", "Money Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for detecting the Monday breakout", "General");
}
/// <summary>
/// Fixed lot size used for entries (set to zero to enable scaling).
/// </summary>
public decimal FixedVolume
{
get => _fixedVolume.Value;
set => _fixedVolume.Value = value;
}
/// <summary>
/// Hour (0-23) when Monday entries are evaluated.
/// </summary>
public int OpenHour
{
get => _openHour.Value;
set => _openHour.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in price points.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance expressed in price points.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Minimum equity required before the scaling table becomes active.
/// </summary>
public decimal InitialEquity
{
get => _initialEquity.Value;
set => _initialEquity.Value = value;
}
/// <summary>
/// Equity increment that increases the trade size by <see cref="VolumeStep"/>.
/// </summary>
public decimal EquityStep
{
get => _equityStep.Value;
set => _equityStep.Value = value;
}
/// <summary>
/// Volume applied when the first equity threshold is met.
/// </summary>
public decimal InitialStepVolume
{
get => _initialStepVolume.Value;
set => _initialStepVolume.Value = value;
}
/// <summary>
/// Additional volume added for each equity tier.
/// </summary>
public decimal VolumeStep
{
get => _volumeStep.Value;
set => _volumeStep.Value = value;
}
/// <summary>
/// Candle type used for detecting breakout conditions.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousCandle = null;
_lastSignalTime = null;
_priceStep = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_priceStep = Security?.PriceStep ?? 0m;
if (_priceStep <= 0m)
_priceStep = 0.0001m;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
if (TakeProfitPoints > 0 || StopLossPoints > 0)
{
var takeDistance = TakeProfitPoints > 0
? new Unit(TakeProfitPoints * _priceStep, UnitTypes.Absolute)
: new Unit(0m);
var stopDistance = StopLossPoints > 0
? new Unit(StopLossPoints * _priceStep, UnitTypes.Absolute)
: new Unit(0m);
StartProtection(takeProfit: takeDistance, stopLoss: stopDistance);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var previous = _previousCandle;
_previousCandle = candle;
if (previous is null)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position != 0m)
return;
var candleTime = candle.OpenTime.ToLocalTime();
if (candleTime.DayOfWeek != DayOfWeek.Monday)
return;
if (candleTime.Hour != OpenHour)
return;
if (_lastSignalTime is DateTimeOffset last && last == candle.OpenTime)
return;
var typicalPrice = (previous.HighPrice + previous.LowPrice + previous.ClosePrice) / 3m;
if (candle.OpenPrice <= typicalPrice)
return;
var volume = CalculateOrderVolume();
volume = AlignVolume(volume);
if (volume <= 0m)
return;
BuyMarket(volume);
_lastSignalTime = candle.OpenTime;
}
private decimal CalculateOrderVolume()
{
var fixedVolume = FixedVolume;
if (fixedVolume > 0m)
return fixedVolume;
var security = Security;
var portfolio = Portfolio;
var minVolume = security?.MinVolume ?? 0m;
if (minVolume <= 0m)
minVolume = 0.01m;
var equity = portfolio?.CurrentValue ?? portfolio?.BeginValue ?? 0m;
if (equity <= 0m)
return minVolume;
if (equity < InitialEquity)
return minVolume;
if (EquityStep <= 0m)
return InitialStepVolume;
var stepsDecimal = (equity - InitialEquity) / EquityStep;
if (stepsDecimal < 0m)
stepsDecimal = 0m;
var steps = (int)Math.Floor(stepsDecimal);
var dynamicVolume = InitialStepVolume + VolumeStep * steps;
if (dynamicVolume < minVolume)
dynamicVolume = minVolume;
return dynamicVolume;
}
private decimal AlignVolume(decimal volume)
{
var security = Security;
if (security == null)
return volume;
var minVolume = security.MinVolume ?? 0m;
var maxVolume = security.MaxVolume ?? 0m;
var step = security.VolumeStep ?? 0m;
if (minVolume > 0m && volume < minVolume)
volume = minVolume;
if (maxVolume > 0m && volume > maxVolume)
volume = maxVolume;
if (step > 0m)
{
var steps = Math.Round(volume / step, MidpointRounding.AwayFromZero);
volume = steps * step;
}
return volume;
}
}
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, DayOfWeek
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Strategies import Strategy
class monday_typical_breakout_strategy(Strategy):
def __init__(self):
super(monday_typical_breakout_strategy, self).__init__()
self._fixed_volume = self.Param("FixedVolume", 0.1) \
.SetDisplay("Fixed Volume", "Lot size used for entries (set to 0 to enable equity scaling)", "Risk")
self._open_hour = self.Param("OpenHour", 9) \
.SetDisplay("Open Hour", "Hour of the session to evaluate Monday breakout entries", "Session")
self._stop_loss_points = self.Param("StopLossPoints", 50) \
.SetDisplay("Stop Loss (points)", "Protective stop distance expressed in price points", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 20) \
.SetDisplay("Take Profit (points)", "Profit target distance expressed in price points", "Risk")
self._initial_equity = self.Param("InitialEquity", 600.0) \
.SetDisplay("Initial Equity", "Account equity threshold that triggers the first scaling tier", "Money Management")
self._equity_step = self.Param("EquityStep", 300.0) \
.SetDisplay("Equity Step", "Incremental equity required to raise the position size", "Money Management")
self._initial_step_volume = self.Param("InitialStepVolume", 0.4) \
.SetDisplay("Initial Step Volume", "Lot size used once the equity threshold is met", "Money Management")
self._volume_step = self.Param("VolumeStep", 0.2) \
.SetDisplay("Volume Step", "Additional lot size added for each equity step", "Money Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe used for detecting the Monday breakout", "General")
self._previous_candle = None
self._last_signal_time = None
self._price_step = 0.0
@property
def FixedVolume(self):
return self._fixed_volume.Value
@FixedVolume.setter
def FixedVolume(self, value):
self._fixed_volume.Value = value
@property
def OpenHour(self):
return self._open_hour.Value
@OpenHour.setter
def OpenHour(self, value):
self._open_hour.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def InitialEquity(self):
return self._initial_equity.Value
@InitialEquity.setter
def InitialEquity(self, value):
self._initial_equity.Value = value
@property
def EquityStep(self):
return self._equity_step.Value
@EquityStep.setter
def EquityStep(self, value):
self._equity_step.Value = value
@property
def InitialStepVolume(self):
return self._initial_step_volume.Value
@InitialStepVolume.setter
def InitialStepVolume(self, value):
self._initial_step_volume.Value = value
@property
def VolumeStep(self):
return self._volume_step.Value
@VolumeStep.setter
def VolumeStep(self, value):
self._volume_step.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(monday_typical_breakout_strategy, self).OnStarted2(time)
ps = self.Security.PriceStep if self.Security is not None else 0
self._price_step = float(ps) if ps is not None and float(ps) > 0 else 0.0001
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
tp = int(self.TakeProfitPoints)
sl = int(self.StopLossPoints)
if tp > 0 or sl > 0:
take_dist = Unit(tp * self._price_step, UnitTypes.Absolute) if tp > 0 else Unit(0)
stop_dist = Unit(sl * self._price_step, UnitTypes.Absolute) if sl > 0 else Unit(0)
self.StartProtection(takeProfit=take_dist, stopLoss=stop_dist)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
previous = self._previous_candle
self._previous_candle = candle
if previous is None:
return
if self.Position != 0:
return
candle_time = candle.OpenTime.ToLocalTime()
if candle_time.DayOfWeek != DayOfWeek.Monday:
return
if candle_time.Hour != self.OpenHour:
return
if self._last_signal_time is not None and self._last_signal_time == candle.OpenTime:
return
typical_price = (float(previous.HighPrice) + float(previous.LowPrice) + float(previous.ClosePrice)) / 3.0
if float(candle.OpenPrice) <= typical_price:
return
volume = self._calculate_order_volume()
if volume <= 0:
return
self.BuyMarket(volume)
self._last_signal_time = candle.OpenTime
def _calculate_order_volume(self):
fixed_vol = float(self.FixedVolume)
if fixed_vol > 0:
return fixed_vol
min_volume = 0.01
if self.Security is not None and self.Security.MinVolume is not None and float(self.Security.MinVolume) > 0:
min_volume = float(self.Security.MinVolume)
equity = 0.0
if self.Portfolio is not None:
cv = self.Portfolio.CurrentValue
bv = self.Portfolio.BeginValue
if cv is not None and float(cv) > 0:
equity = float(cv)
elif bv is not None and float(bv) > 0:
equity = float(bv)
if equity <= 0:
return min_volume
initial_eq = float(self.InitialEquity)
if equity < initial_eq:
return min_volume
eq_step = float(self.EquityStep)
if eq_step <= 0:
return float(self.InitialStepVolume)
steps_decimal = (equity - initial_eq) / eq_step
if steps_decimal < 0:
steps_decimal = 0
steps = int(steps_decimal)
dynamic_volume = float(self.InitialStepVolume) + float(self.VolumeStep) * steps
if dynamic_volume < min_volume:
dynamic_volume = min_volume
return dynamic_volume
def OnReseted(self):
super(monday_typical_breakout_strategy, self).OnReseted()
self._previous_candle = None
self._last_signal_time = None
self._price_step = 0.0
def CreateClone(self):
return monday_typical_breakout_strategy()