Die Doji-Pfeile-Strategie konvertiert den ursprünglichen MetaTrader "Doji Arrows" Expert Advisor in die StockSharp-High-Level-API. Die Idee ist, auf eine echte Doji-Kerze zu warten und dann einen Ausbruch aus ihrem Bereich zu handeln. Eine Doji-Kerze repräsentiert Unentschlossenheit, daher deutet ein Schluss über dem Doji-Hoch auf bullische Stärke hin, während ein Schluss unter dem Doji-Tief bärische Kontrolle anzeigt.
Die Strategie verarbeitet nur abgeschlossene Kerzen aus dem konfigurierten CandleType-Abonnement.
Die vorherige Kerze wird analysiert, um zu bestimmen, ob es sich um ein Doji handelt. Die Kerze wird als Doji klassifiziert, wenn die absolute Differenz zwischen Eröffnung und Schluss kleiner oder gleich DojiBodyPoints multipliziert mit dem Sicherheitspreisschritt ist. Wenn der Parameter auf 0 gesetzt ist, wird ein einzelner Preisschritt als Toleranz verwendet, was der strikten Gleichheitsprüfung in der MQL5-Version entspricht.
Wenn die nächste Kerze über dem Doji-Hoch schließt, sendet die Strategie eine Market-Kauforder. Wenn die nächste Kerze unter dem Doji-Tief schließt, wird eine Market-Verkauforder ausgegeben. Bestehende entgegengesetzte Positionen werden automatisch durch das Market-Order-Volumen ausgeglichen.
Diese Sequenz spiegelt den ursprünglichen Expert Advisor wider, der einmal bei der Eröffnung jedes neuen Balkens reagierte.
Risikomanagement
Die konvertierte Implementierung behält das Schutzverhalten des MQL-Skripts:
Stop Loss: StopLossPoints kontrolliert, wie weit in Preisschritten der anfängliche Stop Loss vom Einstiegspreis entfernt platziert wird. Auf null setzen, um den festen Stop zu deaktivieren.
Take Profit: TakeProfitPoints definiert die Distanz zum Gewinnziel in Preisschritten. Auf null setzen, um das Ziel zu überspringen.
Trailing Stop: TrailingStopPoints und TrailingStepPoints reproduzieren den Trailing-Mechanismus. Sobald der Trade mehr als TrailingStopPoints + TrailingStepPoints gewinnt, wird das Stop-Niveau auf TrailingStopPoints vom letzten Schluss (höchster Schluss für Long, niedrigster Schluss für Short) gezogen. Trailing ist optional und aktiviert sich nur, wenn TrailingStopPoints größer als null ist.
Stops und Ziele werden bei jeder abgeschlossenen Kerze bewertet. Wenn ein Niveau verletzt wird (unter Verwendung von Kerzenhoch/-tief), verlässt die Strategie die Position mit einer Market Order und setzt den Schutzstatus zurück.
Parameter
Parameter
Standard
Beschreibung
StopLossPoints
30
Distanz des anfänglichen Stop Loss in Preisschritten.
TakeProfitPoints
90
Distanz des Take Profit in Preisschritten.
TrailingStopPoints
15
Vom Trailing Stop verwendete Distanz in Preisschritten.
TrailingStepPoints
5
Zusätzlicher Gewinn erforderlich, bevor der Trailing Stop angepasst wird, in Preisschritten.
DojiBodyPoints
1
Maximal erlaubte Körpergröße der vorherigen Kerze in Preisschritten, um sie als Doji zu behandeln. 0 verwendet einen Preisschritt als Toleranz.
CandleType
1 Stunde
Kerzentyp für die Signalgenerierung abonniert.
Implementierungshinweise
Die Strategie abonniert Kerzen durch SubscribeCandles(CandleType).Bind(ProcessCandle) und behält nur die letzte abgeschlossene Kerze im Speicher.
Der Sicherheitspreisschritt wird über Security?.PriceStep abgerufen. Wenn er nicht verfügbar ist, wird ein Fallback-Wert von 1 verwendet, damit die Strategie weiterhin mit synthetischen oder historischen Daten arbeiten kann.
Schutzlevels werden nach jedem Einstieg neu berechnet, und die Trailing-Logik kann einen Stop erstellen, auch wenn der feste Stop Loss deaktiviert ist (was dem MQL-Verhalten entspricht, bei dem der Trailing Stop von null aus starten konnte).
Alle Aktionen werden mit Market Orders ausgeführt, um mit dem ursprünglichen Advisor, der auf sofortige Marktausführung angewiesen war, konform zu bleiben.
Nutzungstipps
Konfigurieren Sie die Eigenschaften Security, Portfolio und Volume, bevor Sie die Strategie starten.
Passen Sie die punktbasierten Parameter entsprechend dem gehandelten Instrument an. Für mit fraktionalen Pips quotierte Instrumente erhöhen Sie die Werte, um zur Tick-Größe des Brokers zu passen.
Kombinieren Sie die Strategie mit StockSharp-Risikokontrollen oder Analysemodule, wenn eine fortgeschrittenere Positionsgrößenbestimmung erforderlich ist, da die Konvertierung die Fixed-Volume-Logik des ursprünglichen Codes beibehält.
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>
/// Doji breakout strategy with optional fixed and trailing protection.
/// </summary>
public class DojiArrowsStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _trailingStepPoints;
private readonly StrategyParam<decimal> _dojiBodyPoints;
private readonly StrategyParam<DataType> _candleType;
private bool _hasPreviousCandle;
private decimal _prevOpen;
private decimal _prevClose;
private decimal _prevHigh;
private decimal _prevLow;
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
public decimal TrailingStepPoints
{
get => _trailingStepPoints.Value;
set => _trailingStepPoints.Value = value;
}
public decimal DojiBodyPoints
{
get => _dojiBodyPoints.Value;
set => _dojiBodyPoints.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public DojiArrowsStrategy()
{
_stopLossPoints = Param(nameof(StopLossPoints), 30m)
.SetNotNegative()
.SetDisplay("Stop Loss Points", "Stop loss distance in price steps.", "Risk")
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 90m)
.SetNotNegative()
.SetDisplay("Take Profit Points", "Take profit distance in price steps.", "Risk")
;
_trailingStopPoints = Param(nameof(TrailingStopPoints), 15m)
.SetNotNegative()
.SetDisplay("Trailing Stop Points", "Trailing distance in price steps.", "Risk")
;
_trailingStepPoints = Param(nameof(TrailingStepPoints), 5m)
.SetNotNegative()
.SetDisplay("Trailing Step Points", "Minimum profit before the trailing stop moves.", "Risk")
;
_dojiBodyPoints = Param(nameof(DojiBodyPoints), 1m)
.SetNotNegative()
.SetDisplay("Doji Body Points", "Maximum difference between open and close to treat the candle as a doji.", "Pattern")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Time frame used for signal generation.", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_hasPreviousCandle = false;
_prevOpen = 0m;
_prevClose = 0m;
_prevHigh = 0m;
_prevLow = 0m;
ResetProtection();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
ManageActivePosition(candle);
if (!_hasPreviousCandle)
{
CachePreviousCandle(candle);
return;
}
var step = Security?.PriceStep ?? 1m;
var tolerance = DojiBodyPoints <= 0m ? step : DojiBodyPoints * step;
var bodySize = Math.Abs(_prevOpen - _prevClose);
var isDoji = bodySize <= tolerance;
var breakoutUp = isDoji && candle.ClosePrice > _prevHigh;
var breakoutDown = isDoji && candle.ClosePrice < _prevLow;
if (breakoutUp && Position == 0)
{
BuyMarket();
}
else if (breakoutDown && Position == 0)
{
SellMarket();
}
CachePreviousCandle(candle);
}
private void ManageActivePosition(ICandleMessage candle)
{
if (Position == 0)
return;
var step = Security?.PriceStep ?? 1m;
var trailingDistance = TrailingStopPoints > 0m ? TrailingStopPoints * step : 0m;
var trailingStep = TrailingStepPoints > 0m ? TrailingStepPoints * step : 0m;
if (Position > 0)
{
if (trailingDistance > 0m && _entryPrice.HasValue)
{
var gain = candle.ClosePrice - _entryPrice.Value;
if (gain > trailingDistance + trailingStep)
{
var newStop = candle.ClosePrice - trailingDistance;
if (!_stopPrice.HasValue || newStop > _stopPrice.Value)
_stopPrice = newStop;
}
}
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
SellMarket(Math.Abs(Position));
ResetProtection();
return;
}
if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
SellMarket(Math.Abs(Position));
ResetProtection();
return;
}
}
else if (Position < 0)
{
if (trailingDistance > 0m && _entryPrice.HasValue)
{
var gain = _entryPrice.Value - candle.ClosePrice;
if (gain > trailingDistance + trailingStep)
{
var newStop = candle.ClosePrice + trailingDistance;
if (!_stopPrice.HasValue || newStop < _stopPrice.Value)
_stopPrice = newStop;
}
}
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetProtection();
return;
}
if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetProtection();
return;
}
}
}
private void InitializeProtection(decimal price, bool isLong, decimal step)
{
_entryPrice = price;
if (StopLossPoints > 0m)
{
var offset = StopLossPoints * step;
_stopPrice = isLong ? price - offset : price + offset;
}
else
{
_stopPrice = null;
}
if (TakeProfitPoints > 0m)
{
var offset = TakeProfitPoints * step;
_takePrice = isLong ? price + offset : price - offset;
}
else
{
_takePrice = null;
}
}
private void ResetProtection()
{
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
}
private void CachePreviousCandle(ICandleMessage candle)
{
_prevOpen = candle.OpenPrice;
_prevClose = candle.ClosePrice;
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_hasPreviousCandle = true;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math
class doji_arrows_strategy(Strategy):
def __init__(self):
super(doji_arrows_strategy, self).__init__()
self._stop_loss_points = self.Param("StopLossPoints", 30.0)
self._take_profit_points = self.Param("TakeProfitPoints", 90.0)
self._trailing_stop_points = self.Param("TrailingStopPoints", 15.0)
self._trailing_step_points = self.Param("TrailingStepPoints", 5.0)
self._doji_body_points = self.Param("DojiBodyPoints", 1.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._has_previous_candle = False
self._prev_open = 0.0
self._prev_close = 0.0
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = None
self._stop_price = None
self._take_price = None
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(doji_arrows_strategy, self).OnStarted2(time)
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._manage_active_position(candle)
if not self._has_previous_candle:
self._cache_previous_candle(candle)
return
step = self._get_price_step()
tolerance = step if self._doji_body_points.Value <= 0 else self._doji_body_points.Value * step
body_size = abs(self._prev_open - self._prev_close)
is_doji = body_size <= tolerance
breakout_up = is_doji and float(candle.ClosePrice) > self._prev_high
breakout_down = is_doji and float(candle.ClosePrice) < self._prev_low
if breakout_up and self.Position == 0:
self.BuyMarket()
self._initialize_protection(float(candle.ClosePrice), True, step)
elif breakout_down and self.Position == 0:
self.SellMarket()
self._initialize_protection(float(candle.ClosePrice), False, step)
self._cache_previous_candle(candle)
def _manage_active_position(self, candle):
if self.Position == 0:
return
step = self._get_price_step()
trailing_distance = self._trailing_stop_points.Value * step if self._trailing_stop_points.Value > 0 else 0.0
trailing_step = self._trailing_step_points.Value * step if self._trailing_step_points.Value > 0 else 0.0
if self.Position > 0:
if trailing_distance > 0 and self._entry_price is not None:
gain = float(candle.ClosePrice) - self._entry_price
if gain > trailing_distance + trailing_step:
new_stop = float(candle.ClosePrice) - trailing_distance
if self._stop_price is None or new_stop > self._stop_price:
self._stop_price = new_stop
if self._stop_price is not None and float(candle.LowPrice) <= self._stop_price:
self.SellMarket(abs(self.Position))
self._reset_protection()
return
if self._take_price is not None and float(candle.HighPrice) >= self._take_price:
self.SellMarket(abs(self.Position))
self._reset_protection()
return
elif self.Position < 0:
if trailing_distance > 0 and self._entry_price is not None:
gain = self._entry_price - float(candle.ClosePrice)
if gain > trailing_distance + trailing_step:
new_stop = float(candle.ClosePrice) + trailing_distance
if self._stop_price is None or new_stop < self._stop_price:
self._stop_price = new_stop
if self._stop_price is not None and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket(abs(self.Position))
self._reset_protection()
return
if self._take_price is not None and float(candle.LowPrice) <= self._take_price:
self.BuyMarket(abs(self.Position))
self._reset_protection()
return
def _initialize_protection(self, price, is_long, step):
self._entry_price = price
if self._stop_loss_points.Value > 0:
offset = self._stop_loss_points.Value * step
self._stop_price = price - offset if is_long else price + offset
else:
self._stop_price = None
if self._take_profit_points.Value > 0:
offset = self._take_profit_points.Value * step
self._take_price = price + offset if is_long else price - offset
else:
self._take_price = None
def _reset_protection(self):
self._entry_price = None
self._stop_price = None
self._take_price = None
def _cache_previous_candle(self, candle):
self._prev_open = float(candle.OpenPrice)
self._prev_close = float(candle.ClosePrice)
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
self._has_previous_candle = True
def _get_price_step(self):
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
return step if step > 0 else 1.0
def OnReseted(self):
super(doji_arrows_strategy, self).OnReseted()
self._has_previous_candle = False
self._prev_open = 0.0
self._prev_close = 0.0
self._prev_high = 0.0
self._prev_low = 0.0
self._reset_protection()
def CreateClone(self):
return doji_arrows_strategy()