Diese Strategie repliziert den MetaTrader 4-Experten open_close.mq4. Es funktioniert auf einem einzelnen Instrument und vergleicht den Eröffnungs- und Schlusskurs der letzten Kerze mit dem vorherigen. Wenn keine Position aktiv ist, werden starke Ein-Takt-Bewegungen (Gap-and-Reversal-Muster) ausgeblendet. Während eines Handels wird die Position entweder dann geschlossen, wenn sich das Muster umkehrt oder wenn ein Schwellenwert für den Floating-Loss-Schutz überschritten wird.
Handelslogik
Einreisebestimmungen
Wird nur gehandelt, wenn die vorherige Kerze verarbeitet wurde (der ursprüngliche Volume[0] == 1-Schutz).
Long-Einstieg: Die aktuelle Kerze öffnet sich über dem vorherigen Eröffnungskurs und schließt unter dem vorherigen Schlusskurs. Die Strategie kauft das konfigurierte Volumen zum Marktpreis.
Short-Einstieg: Die aktuelle Kerze öffnet sich unter dem vorherigen Eröffnungskurs und schließt über dem vorherigen Schlusskurs. Die Strategie verkauft Leerverkäufe zum Marktwert.
Es kann immer nur eine Position aktiv sein. Neue Signale werden ignoriert, bis die offene Position geschlossen wird.
Ausgangsregeln
Risikoschutz: Der variable PnL wird anhand des durchschnittlichen Einstiegspreises gemessen. Wenn der nicht realisierte Verlust MaximumRisk × Portfolio.CurrentValue übersteigt, schließt die Strategie die Position sofort. Die ursprüngliche MQL-Version verwendete AccountMargin, was hier mit der besten verfügbaren Portfoliobewertung angenähert wird.
Musterumkehr:
Long-Positionen werden geschlossen, wenn die nächste Kerze weiter nach unten geht (open < previous open und close < previous close).
Short-Positionen werden geschlossen, wenn die nächste Kerze weiter nach oben geht (open > previous open und close > previous close).
Positionsgrößen
Die Standardbestellgröße wird von MaximumRisk abgeleitet. Die Strategie multipliziert den verfügbaren Kontowert mit MaximumRisk und dividiert das Ergebnis durch 1000, wodurch die MetaTrader-Berechnung von AccountFreeMargin * MaximumRisk / 1000 nachgeahmt wird.
Wenn die Kontoinformationen nicht verfügbar sind, wird der Fallback-Parameter InitialVolume verwendet.
Nach mehr als einem aufeinanderfolgenden Verlustgeschäft wird die Losgröße um volume × losses / DecreaseFactor reduziert, wodurch die MetaTrader-Schleife über den Verlauf der geschlossenen Geschäfte reproduziert wird.
Es wird ein handelbares Mindestvolumen von 0.1 Lots erzwungen, bevor die Menge an den Volumenschritt des Instruments und die Börsenlimits angepasst wird.
Parameter
Name
Typ
Standard
Beschreibung
InitialVolume
decimal
0.1
Fallback-Losgröße, die verwendet wird, wenn keine Eigenkapitalinformationen verfügbar sind.
MaximumRisk
decimal
0.3
Bruchteil des Kontowerts, der sowohl die Positionsgröße als auch den maximal tolerierten Floating-Verlust steuert.
DecreaseFactor
decimal
100
Der Reduktionsfaktor wird nach mehr als einem Verlusthandel in Folge angewendet.
CandleType
DataType
15m Zeitrahmen
Zur Bewertung des Musters verwendete Kerzenserie.
Implementierungshinweise
Die Strategie abonniert die ausgewählte Kerzenserie und verarbeitet nur fertige Kerzen, die der Volume[0] > 1-Bedingung im ursprünglichen Experten entsprechen.
Der variable PnL wird anhand der aktuellen Position der Strategie und des letzten Schlusskurses geschätzt, da StockSharp die Kennzahlen AccountProfit und AccountMargin von MetaTrader nicht offenlegt.
Aufeinanderfolgende Verluste werden durch abgeschlossene Geschäfte verfolgt, sodass sich DecreaseFactor wie die ursprüngliche Schleife über den Handelsverlauf verhält.
Bei der Volumenausrichtung werden Security.VolumeStep, MinVolume und MaxVolume berücksichtigt, um mit den Exchange-Anforderungen kompatibel zu bleiben.
Charts werden mit Kerzen und eigenen Trades gefüllt, wenn ein Chartbereich für visuelles Debuggen verfügbar ist.
Nutzungstipps
Wählen Sie ein Kerzenintervall, das dem in MetaTrader bei der Kalibrierung des ursprünglichen Experten verwendeten entspricht.
Passen Sie MaximumRisk und DecreaseFactor an, um die Aggressivität der Losgrößenregel anzupassen.
Da die Strategie konträr ist, funktioniert sie am besten bei Instrumenten, die häufige Einzelbalken-Überdehnungen und Snap-Back-Bewegungen aufweisen.
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Contrarian pattern strategy converted from the MetaTrader expert "open_close".
/// Evaluates relationships between consecutive candle opens and closes.
/// Buys when a bearish candle opens above the previous open (fading the move),
/// and sells when a bullish candle opens below the previous open.
/// </summary>
public class OpenCloseStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _ema;
private bool _hasPreviousCandle;
private decimal _previousOpen;
private decimal _previousClose;
public OpenCloseStrategy()
{
_stopLossPoints = Param(nameof(StopLossPoints), 500m)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop loss in absolute points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
.SetNotNegative()
.SetDisplay("Take Profit", "Take profit in absolute points", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time-frame used to evaluate the open/close pattern.", "Data");
Volume = 1;
}
/// <summary>
/// Stop loss distance in absolute points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance in absolute points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Candle series used to evaluate the pattern.
/// </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();
_hasPreviousCandle = false;
_previousOpen = 0m;
_previousClose = 0m;
_ema = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
_ema = new ExponentialMovingAverage { Length = 20 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
var tp = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null;
var sl = StopLossPoints > 0 ? new Unit(StopLossPoints, UnitTypes.Absolute) : null;
if (tp != null || sl != null)
StartProtection(tp, sl);
base.OnStarted2(time);
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
var open = candle.OpenPrice;
var close = candle.ClosePrice;
if (!_hasPreviousCandle)
{
_previousOpen = open;
_previousClose = close;
_hasPreviousCandle = true;
return;
}
// Exit logic
if (Position > 0)
{
// Close long on bearish continuation
if (open < _previousOpen && close < _previousClose)
SellMarket(Position);
}
else if (Position < 0)
{
// Close short on bullish continuation
if (open > _previousOpen && close > _previousClose)
BuyMarket(Math.Abs(Position));
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_previousOpen = open;
_previousClose = close;
return;
}
// Entry logic
if (Position == 0)
{
// Buy: fade a bearish candle that opened above the previous open
if (open > _previousOpen && close < _previousClose)
{
BuyMarket(Volume);
}
// Sell: fade a bullish candle that opened below the previous open
else if (open < _previousOpen && close > _previousClose)
{
SellMarket(Volume);
}
}
_previousOpen = open;
_previousClose = close;
}
}
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
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class open_close_strategy(Strategy):
def __init__(self):
super(open_close_strategy, self).__init__()
self._sl_points = self.Param("StopLossPoints", 500.0).SetNotNegative().SetDisplay("Stop Loss", "Stop loss in absolute points", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 500.0).SetNotNegative().SetDisplay("Take Profit", "Take profit in absolute points", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Time-frame for open/close pattern.", "Data")
self.Volume = 1
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(open_close_strategy, self).OnReseted()
self._has_prev = False
self._prev_open = 0
self._prev_close = 0
def OnStarted2(self, time):
super(open_close_strategy, self).OnStarted2(time)
self._has_prev = False
self._prev_open = 0
self._prev_close = 0
ema = ExponentialMovingAverage()
ema.Length = 20
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(ema, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
tp = Unit(self._tp_points.Value, UnitTypes.Absolute) if self._tp_points.Value > 0 else None
sl = Unit(self._sl_points.Value, UnitTypes.Absolute) if self._sl_points.Value > 0 else None
if tp is not None or sl is not None:
self.StartProtection(tp, sl)
def OnProcess(self, candle, ema_val):
if candle.State != CandleStates.Finished:
return
open_price = candle.OpenPrice
close = candle.ClosePrice
if not self._has_prev:
self._prev_open = open_price
self._prev_close = close
self._has_prev = True
return
# Exit logic
if self.Position > 0:
if open_price < self._prev_open and close < self._prev_close:
self.SellMarket(self.Position)
elif self.Position < 0:
if open_price > self._prev_open and close > self._prev_close:
self.BuyMarket(Math.Abs(self.Position))
# Entry logic
if self.Position == 0:
if open_price > self._prev_open and close < self._prev_close:
self.BuyMarket(self.Volume)
elif open_price < self._prev_open and close > self._prev_close:
self.SellMarket(self.Volume)
self._prev_open = open_price
self._prev_close = close
def CreateClone(self):
return open_close_strategy()