Divergenzhändler (klassische Konvertierung)
Diese Strategie reproduziert das Verhalten des MetaTrader 4 Expertenberaters Divergenzhändler innerhalb des StockSharp hohen Niveaus API. Für den ausgewählten Kerzenpreis (standardmäßig offen) werden zwei einfache gleitende Durchschnitte berechnet. Das System überwacht, wie sich der Abstand zwischen den schnellen und langsamen Durchschnittswerten von einem Balken zum nächsten ändert:
- Wenn sich der Spread nach oben ausdehnt und der Divergenzwert zwischen der Kaufschwelle und der Stay-Out-Schwelle bleibt, wird eine Long-Position eröffnet oder eine bestehende Short-Position gedeckt.
- Wenn sich der Spread innerhalb der gespiegelten Schwellenwerte nach unten ausweitet, wird eine Short-Position eingegangen oder ein bestehender Long-Trade geschlossen.
Es werden nur fertige Kerzen verwendet, entsprechend der Bar-für-Bar-Verarbeitung des Original-Expertenberaters. Alle Verwaltungsregeln werden mit ereignisgesteuerten High-Level-Aufrufen (BuyMarket / SellMarket) implementiert.
Handelsregeln
- Abonnieren Sie den konfigurierten Kerzentyp und berechnen Sie zwei SMAs mit den Perioden Fast SMA und Slow SMA.
- Berechnen Sie den aktuellen Spread (
fast - slow) und vergleichen Sie ihn mit dem vorherigen Spread, um den Divergenzwert zu erhalten. - Geben Sie „long“ ein, wenn die Divergenz positiv ist, größer oder gleich dem Kaufschwellenwert und kleiner oder gleich dem Stay-Out-Schwellenwert.
- Geben Sie kurz ein, wenn die Divergenz negativ, kleiner oder gleich
-Buy Thresholdund größer oder gleich-Stay Out Thresholdist. - Kehren Sie eine bestehende Position um, wenn ein entgegengesetztes Signal auftritt.
- Beschränken Sie neue Einträge auf das lokale Zeitfenster zwischen Start Hour und Stop Hour (Umbruch über Mitternacht wird unterstützt).
Risikomanagement
- Optionale feste Werte für Take Profit (Pips) und Stop Loss (Pips) werden auf Kerzenhochs/-tiefs überwacht.
- Der Break-Even-Trigger (Pips) verschiebt den Stop auf
entry ± Break-Even Buffer, sobald die Position die angegebene Anzahl an Pips erreicht. - Der Trailing Stop (Pips) folgt dem günstigsten Preis, sobald der Handel profitabel ist. Durch die Einstellung 9999 wird der Trailing Stop deaktiviert und der ursprüngliche EA-Standardwert widergespiegelt.
- Das Korbmanagement schließt alle offenen Positionen, wenn die nicht realisierten Gewinne und Verluste den Korbgewinn erreichen oder unter
-Basket Lossin der Kontowährung fallen.
Parameter
| Parameter | Beschreibung |
|---|---|
Order Volume |
Volumen, das beim Eröffnen einer neuen Position verwendet wird. |
Fast SMA / Slow SMA |
Perioden für die beiden einfachen gleitenden Durchschnitte. |
Applied Price |
Kerzenkomponente wird in beide gleitenden Durchschnitte weitergeleitet. |
Buy Threshold |
Untere Divergenzgrenze, die Long-Trades ermöglicht. |
Stay Out Threshold |
Obere Divergenzgrenze, oberhalb derer keine neuen Geschäfte getätigt werden. |
Take Profit (pips) / Stop Loss (pips) |
Optionale Hard-Exits, gemessen in Pips. |
Trailing Stop (pips) |
Nachlaufdistanz, die angewendet wird, nachdem der Handel profitabel wird. |
Break-Even Trigger (pips) |
Erforderlicher Gewinn in Pips, bevor der Stopp auf die Gewinnschwelle verschoben wird. |
Break-Even Buffer (pips) |
Zusätzlicher Puffer zum Break-Even-Stopp hinzugefügt. |
Basket Profit / Basket Loss |
Globale Eigenkapitalgrenzen in Kontowährung. |
Start Hour / Stop Hour |
Lokales Handelssitzungsfenster. |
Candle Type |
Zeitrahmen für Kerzenabonnements und Berechnungen. |
Nutzungshinweise
- Hängen Sie die Strategie an ein Wertpapier an und legen Sie den Kerzentyp fest, der dem ursprünglichen Zeitrahmen des Diagramms entspricht.
- Stellen Sie sicher, dass die
PriceStep/StepPrice-Eigenschaften des Instruments so konfiguriert sind, dass Pip-basierte Steuerungen ordnungsgemäß funktionieren. - Um Funktionen wie Trailing Stop oder Break-Even-Shift zu deaktivieren, belassen Sie deren Parameter auf dem alten Sentinel-Wert (9999) oder Null.
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>
/// Classic divergence trading strategy converted from the MetaTrader 4 "Divergence Trader" expert.
/// The strategy compares a fast and a slow simple moving average and monitors how the spread between
/// them changes from bar to bar. A widening spread to the upside triggers long trades while a widening
/// spread to the downside triggers short trades. Risk management mimics the original MQL behaviour with
/// optional profit targets, stop-loss, trailing stop, break-even shift and basket level exits.
/// </summary>
public class DivergenceTraderClassicStrategy : Strategy
{
public enum CandlePrices
{
Open,
Close,
High,
Low,
Median,
Typical,
Weighted
}
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<CandlePrices> _appliedPrice;
private readonly StrategyParam<decimal> _buyThreshold;
private readonly StrategyParam<decimal> _stayOutThreshold;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<decimal> _breakEvenPips;
private readonly StrategyParam<decimal> _breakEvenBufferPips;
private readonly StrategyParam<decimal> _basketProfitCurrency;
private readonly StrategyParam<decimal> _basketLossCurrency;
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _stopHour;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _fastSma;
private SimpleMovingAverage _slowSma;
private decimal? _previousSpread;
private decimal _pipSize;
private decimal _maxBasketPnL;
private decimal _minBasketPnL;
private decimal? _breakEvenPrice;
private decimal? _trailingStopPrice;
private decimal _highestPrice;
private decimal _lowestPrice;
private decimal _entryPrice;
/// <summary>
/// Initializes a new instance of <see cref="DivergenceTraderClassicStrategy"/>.
/// </summary>
public DivergenceTraderClassicStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume used when opening a new position.", "Trading")
;
_fastPeriod = Param(nameof(FastPeriod), 7)
.SetGreaterThanZero()
.SetDisplay("Fast SMA", "Period for the fast simple moving average.", "Indicators")
;
_slowPeriod = Param(nameof(SlowPeriod), 88)
.SetGreaterThanZero()
.SetDisplay("Slow SMA", "Period for the slow simple moving average.", "Indicators")
;
_appliedPrice = Param(nameof(AppliedPrice), CandlePrices.Open)
.SetDisplay("Applied Price", "Price component forwarded into the moving averages.", "Indicators");
_buyThreshold = Param(nameof(BuyThreshold), 10m)
.SetDisplay("Buy Threshold", "Minimal divergence needed to allow long entries.", "Signals")
;
_stayOutThreshold = Param(nameof(StayOutThreshold), 1000m)
.SetDisplay("Stay Out Threshold", "Upper divergence bound disabling new entries.", "Signals")
;
_takeProfitPips = Param(nameof(TakeProfitPips), 0m)
.SetDisplay("Take Profit (pips)", "Distance in pips used to exit winners.", "Risk");
_stopLossPips = Param(nameof(StopLossPips), 0m)
.SetDisplay("Stop Loss (pips)", "Maximum adverse excursion tolerated.", "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 9999m)
.SetDisplay("Trailing Stop (pips)", "Trailing distance; 9999 disables trailing just like the EA.", "Risk");
_breakEvenPips = Param(nameof(BreakEvenPips), 9999m)
.SetDisplay("Break-Even Trigger (pips)", "Profit in pips required before moving the stop to break-even.", "Risk");
_breakEvenBufferPips = Param(nameof(BreakEvenBufferPips), 2m)
.SetDisplay("Break-Even Buffer (pips)", "Buffer in pips added to the break-even stop.", "Risk");
_basketProfitCurrency = Param(nameof(BasketProfitCurrency), 75m)
.SetDisplay("Basket Profit", "Floating profit that forces closing all positions.", "Basket");
_basketLossCurrency = Param(nameof(BasketLossCurrency), 9999m)
.SetDisplay("Basket Loss", "Floating loss that forces closing all positions.", "Basket");
_startHour = Param(nameof(StartHour), 0)
.SetDisplay("Start Hour", "Hour when trading becomes active (0-23).", "Schedule");
_stopHour = Param(nameof(StopHour), 24)
.SetDisplay("Stop Hour", "Hour when trading stops accepting new entries (1-24).", "Schedule");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used to calculate signals.", "General");
}
/// <summary>
/// Base volume for new positions.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Period for the fast moving average.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Period for the slow moving average.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Price component forwarded into both moving averages.
/// </summary>
public CandlePrices AppliedPrice
{
get => _appliedPrice.Value;
set => _appliedPrice.Value = value;
}
/// <summary>
/// Divergence value required before long trades can be opened.
/// </summary>
public decimal BuyThreshold
{
get => _buyThreshold.Value;
set => _buyThreshold.Value = value;
}
/// <summary>
/// Maximum divergence that still allows trades. Above this value trading is skipped.
/// </summary>
public decimal StayOutThreshold
{
get => _stayOutThreshold.Value;
set => _stayOutThreshold.Value = value;
}
/// <summary>
/// Take-profit distance in pips. Zero keeps the trade open until an opposite signal.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Stop-loss distance in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips. Use a very large value to disable the trail.
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Profit trigger for moving the stop to break-even.
/// </summary>
public decimal BreakEvenPips
{
get => _breakEvenPips.Value;
set => _breakEvenPips.Value = value;
}
/// <summary>
/// Additional buffer applied when shifting the stop to break-even.
/// </summary>
public decimal BreakEvenBufferPips
{
get => _breakEvenBufferPips.Value;
set => _breakEvenBufferPips.Value = value;
}
/// <summary>
/// Basket profit threshold in account currency.
/// </summary>
public decimal BasketProfitCurrency
{
get => _basketProfitCurrency.Value;
set => _basketProfitCurrency.Value = value;
}
/// <summary>
/// Basket loss threshold in account currency.
/// </summary>
public decimal BasketLossCurrency
{
get => _basketLossCurrency.Value;
set => _basketLossCurrency.Value = value;
}
/// <summary>
/// Hour of the day when new trades are allowed.
/// </summary>
public int StartHour
{
get => _startHour.Value;
set => _startHour.Value = value;
}
/// <summary>
/// Hour of the day when new trades are blocked.
/// </summary>
public int StopHour
{
get => _stopHour.Value;
set => _stopHour.Value = value;
}
/// <summary>
/// Candle type (timeframe) used by the strategy.
/// </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();
_fastSma = null;
_slowSma = null;
_previousSpread = null;
_pipSize = 0m;
_maxBasketPnL = 0m;
_minBasketPnL = 0m;
_breakEvenPrice = null;
_trailingStopPrice = null;
_highestPrice = 0m;
_lowestPrice = 0m;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (Position != 0 && _entryPrice == 0m)
_entryPrice = trade.Trade.Price;
if (Position == 0m)
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipSize = CalculatePipSize();
_fastSma = new SMA { Length = FastPeriod };
_slowSma = new SMA { Length = SlowPeriod };
_previousSpread = null;
_breakEvenPrice = null;
_trailingStopPrice = null;
_highestPrice = 0m;
_lowestPrice = 0m;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_fastSma, _slowSma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastSma);
DrawIndicator(area, _slowSma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
// Work only with fully formed candles.
if (candle.State != CandleStates.Finished)
return;
// Update trailing logic for existing positions before acting on new signals.
ManageOpenPosition(candle);
// Respect basket limits from the legacy EA.
if (EvaluateBasketPnL(candle.ClosePrice))
{
_previousSpread = fastValue - slowValue;
return;
}
if (_fastSma == null || _slowSma == null)
return;
if (!_fastSma.IsFormed || !_slowSma.IsFormed)
{
_previousSpread = fastValue - slowValue;
return;
}
var currentSpread = fastValue - slowValue;
var divergence = _previousSpread.HasValue ? currentSpread - _previousSpread.Value : 0m;
_previousSpread = currentSpread;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!IsWithinTradingHours(candle.CloseTime))
return;
if (OrderVolume <= 0m)
return;
// Avoid over-hedging: only reverse when the signal changes direction.
if (divergence >= BuyThreshold && divergence <= StayOutThreshold)
{
if (Position < 0m)
{
BuyMarket(Math.Abs(Position));
}
if (Position <= 0m)
{
ResetPositionTracking();
BuyMarket(OrderVolume);
}
}
else if (divergence <= -BuyThreshold && divergence >= -StayOutThreshold)
{
if (Position > 0m)
{
SellMarket(Position);
}
if (Position >= 0m)
{
ResetPositionTracking();
SellMarket(OrderVolume);
}
}
}
private void ManageOpenPosition(ICandleMessage candle)
{
if (Position == 0m)
{
ResetPositionTracking();
return;
}
var entryPrice = _entryPrice;
if (entryPrice == 0m)
return;
var pipSize = EnsurePipSize();
var takeProfitDistance = TakeProfitPips > 0m ? TakeProfitPips * pipSize : 0m;
var stopLossDistance = StopLossPips > 0m ? StopLossPips * pipSize : 0m;
var breakEvenDistance = BreakEvenPips > 0m && BreakEvenPips < 9000m ? BreakEvenPips * pipSize : 0m;
var breakEvenBuffer = BreakEvenBufferPips > 0m ? BreakEvenBufferPips * pipSize : 0m;
var trailingDistance = TrailingStopPips > 0m && TrailingStopPips < 9000m ? TrailingStopPips * pipSize : 0m;
var absPosition = Math.Abs(Position);
if (Position > 0m)
{
_highestPrice = Math.Max(_highestPrice == 0m ? entryPrice : _highestPrice, candle.HighPrice);
var profitDistance = candle.ClosePrice - entryPrice;
if (breakEvenDistance > 0m && profitDistance >= breakEvenDistance && _breakEvenPrice == null)
_breakEvenPrice = entryPrice + breakEvenBuffer;
if (_breakEvenPrice is decimal bePrice && candle.LowPrice <= bePrice)
{
SellMarket(absPosition);
ResetPositionTracking();
return;
}
if (trailingDistance > 0m && profitDistance >= trailingDistance)
{
var candidate = _highestPrice - trailingDistance;
if (_trailingStopPrice == null || candidate > _trailingStopPrice)
_trailingStopPrice = candidate;
if (_trailingStopPrice is decimal trailing && candle.LowPrice <= trailing)
{
SellMarket(absPosition);
ResetPositionTracking();
return;
}
}
if (takeProfitDistance > 0m && profitDistance >= takeProfitDistance)
{
SellMarket(absPosition);
ResetPositionTracking();
return;
}
if (stopLossDistance > 0m && candle.LowPrice <= entryPrice - stopLossDistance)
{
SellMarket(absPosition);
ResetPositionTracking();
}
}
else if (Position < 0m)
{
_lowestPrice = Math.Min(_lowestPrice == 0m ? entryPrice : _lowestPrice, candle.LowPrice);
var profitDistance = entryPrice - candle.ClosePrice;
if (breakEvenDistance > 0m && profitDistance >= breakEvenDistance && _breakEvenPrice == null)
_breakEvenPrice = entryPrice - breakEvenBuffer;
if (_breakEvenPrice is decimal bePrice && candle.HighPrice >= bePrice)
{
BuyMarket(absPosition);
ResetPositionTracking();
return;
}
if (trailingDistance > 0m && profitDistance >= trailingDistance)
{
var candidate = _lowestPrice + trailingDistance;
if (_trailingStopPrice == null || candidate < _trailingStopPrice)
_trailingStopPrice = candidate;
if (_trailingStopPrice is decimal trailing && candle.HighPrice >= trailing)
{
BuyMarket(absPosition);
ResetPositionTracking();
return;
}
}
if (takeProfitDistance > 0m && profitDistance >= takeProfitDistance)
{
BuyMarket(absPosition);
ResetPositionTracking();
return;
}
if (stopLossDistance > 0m && candle.HighPrice >= entryPrice + stopLossDistance)
{
BuyMarket(absPosition);
ResetPositionTracking();
}
}
}
private bool EvaluateBasketPnL(decimal lastPrice)
{
if (BasketProfitCurrency <= 0m && BasketLossCurrency <= 0m)
return false;
if (Position == 0m)
return false;
var entryPrice = _entryPrice;
if (entryPrice == 0m)
return false;
var step = EnsurePipSize();
var stepValue = step;
var priceMove = Position > 0m ? lastPrice - entryPrice : entryPrice - lastPrice;
var pipMove = step > 0m ? priceMove / step : priceMove;
var currencyPnL = pipMove * stepValue * Math.Abs(Position);
_maxBasketPnL = Math.Max(_maxBasketPnL, currencyPnL);
_minBasketPnL = Math.Min(_minBasketPnL, currencyPnL);
var shouldCloseForProfit = BasketProfitCurrency > 0m && currencyPnL >= BasketProfitCurrency;
var shouldCloseForLoss = BasketLossCurrency > 0m && currencyPnL <= -BasketLossCurrency;
if (shouldCloseForProfit || shouldCloseForLoss)
{
CloseAllPositions();
return true;
}
return false;
}
private void CloseAllPositions()
{
if (Position > 0m)
{
SellMarket(Position);
}
else if (Position < 0m)
{
BuyMarket(Math.Abs(Position));
}
ResetPositionTracking();
}
private void ResetPositionTracking()
{
_breakEvenPrice = null;
_trailingStopPrice = null;
_highestPrice = 0m;
_lowestPrice = 0m;
}
private bool IsWithinTradingHours(DateTimeOffset time)
{
var hour = time.Hour;
if (StartHour == StopHour)
return true;
if (StartHour < StopHour)
return hour >= StartHour && hour < StopHour;
// Overnight window that crosses midnight.
return hour >= StartHour || hour < StopHour;
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 0m;
return step > 0m ? step : 0.0001m;
}
private decimal EnsurePipSize()
{
if (_pipSize <= 0m)
_pipSize = CalculatePipSize();
return _pipSize;
}
}
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
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage
class divergence_trader_classic_strategy(Strategy):
def __init__(self):
super(divergence_trader_classic_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 0.1) \
.SetDisplay("Order Volume", "Volume used when opening a new position", "Trading")
self._fast_period = self.Param("FastPeriod", 7) \
.SetDisplay("Fast SMA", "Period for the fast simple moving average", "Indicators")
self._slow_period = self.Param("SlowPeriod", 88) \
.SetDisplay("Slow SMA", "Period for the slow simple moving average", "Indicators")
self._buy_threshold = self.Param("BuyThreshold", 10.0) \
.SetDisplay("Buy Threshold", "Minimal divergence needed to allow long entries", "Signals")
self._stay_out_threshold = self.Param("StayOutThreshold", 1000.0) \
.SetDisplay("Stay Out Threshold", "Upper divergence bound disabling new entries", "Signals")
self._take_profit_pips = self.Param("TakeProfitPips", 0.0) \
.SetDisplay("Take Profit (pips)", "Distance in pips used to exit winners", "Risk")
self._stop_loss_pips = self.Param("StopLossPips", 0.0) \
.SetDisplay("Stop Loss (pips)", "Maximum adverse excursion tolerated", "Risk")
self._trailing_stop_pips = self.Param("TrailingStopPips", 9999.0) \
.SetDisplay("Trailing Stop (pips)", "Trailing distance; 9999 disables trailing", "Risk")
self._break_even_pips = self.Param("BreakEvenPips", 9999.0) \
.SetDisplay("Break-Even Trigger (pips)", "Profit in pips before moving stop to break-even", "Risk")
self._break_even_buffer_pips = self.Param("BreakEvenBufferPips", 2.0) \
.SetDisplay("Break-Even Buffer (pips)", "Buffer in pips added to the break-even stop", "Risk")
self._basket_profit_currency = self.Param("BasketProfitCurrency", 75.0) \
.SetDisplay("Basket Profit", "Floating profit that forces closing all positions", "Basket")
self._basket_loss_currency = self.Param("BasketLossCurrency", 9999.0) \
.SetDisplay("Basket Loss", "Floating loss that forces closing all positions", "Basket")
self._start_hour = self.Param("StartHour", 0) \
.SetDisplay("Start Hour", "Hour when trading becomes active (0-23)", "Schedule")
self._stop_hour = self.Param("StopHour", 24) \
.SetDisplay("Stop Hour", "Hour when trading stops accepting new entries (1-24)", "Schedule")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe used to calculate signals", "General")
self._previous_spread = None
self._pip_size = 0.0
self._max_basket_pnl = 0.0
self._min_basket_pnl = 0.0
self._break_even_price = None
self._trailing_stop_price = None
self._highest_price = 0.0
self._lowest_price = 0.0
self._entry_price = 0.0
@property
def OrderVolume(self):
return self._order_volume.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
@property
def BuyThreshold(self):
return self._buy_threshold.Value
@property
def StayOutThreshold(self):
return self._stay_out_threshold.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TrailingStopPips(self):
return self._trailing_stop_pips.Value
@property
def BreakEvenPips(self):
return self._break_even_pips.Value
@property
def BreakEvenBufferPips(self):
return self._break_even_buffer_pips.Value
@property
def BasketProfitCurrency(self):
return self._basket_profit_currency.Value
@property
def BasketLossCurrency(self):
return self._basket_loss_currency.Value
@property
def StartHour(self):
return self._start_hour.Value
@property
def StopHour(self):
return self._stop_hour.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(divergence_trader_classic_strategy, self).OnStarted2(time)
self._pip_size = self._calculate_pip_size()
self._fast_sma = SimpleMovingAverage()
self._fast_sma.Length = self.FastPeriod
self._slow_sma = SimpleMovingAverage()
self._slow_sma.Length = self.SlowPeriod
self._previous_spread = None
self._break_even_price = None
self._trailing_stop_price = None
self._highest_price = 0.0
self._lowest_price = 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_sma, self._slow_sma, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_value = float(fast_value)
slow_value = float(slow_value)
self._manage_open_position(candle)
if self._evaluate_basket_pnl(float(candle.ClosePrice)):
self._previous_spread = fast_value - slow_value
return
if not self._fast_sma.IsFormed or not self._slow_sma.IsFormed:
self._previous_spread = fast_value - slow_value
return
current_spread = fast_value - slow_value
divergence = current_spread - self._previous_spread if self._previous_spread is not None else 0.0
self._previous_spread = current_spread
if not self._is_within_trading_hours(candle.CloseTime):
return
ov = float(self.OrderVolume)
if ov <= 0:
return
buy_thr = float(self.BuyThreshold)
stay_out = float(self.StayOutThreshold)
if divergence >= buy_thr and divergence <= stay_out:
if self.Position < 0:
self.BuyMarket(abs(self.Position))
if self.Position <= 0:
self._reset_position_tracking()
self.BuyMarket(ov)
elif divergence <= -buy_thr and divergence >= -stay_out:
if self.Position > 0:
self.SellMarket(self.Position)
if self.Position >= 0:
self._reset_position_tracking()
self.SellMarket(ov)
def _manage_open_position(self, candle):
if self.Position == 0:
self._reset_position_tracking()
return
entry_price = self._entry_price
if entry_price == 0:
return
pip_size = self._ensure_pip_size()
tp_pips = float(self.TakeProfitPips)
sl_pips = float(self.StopLossPips)
be_pips = float(self.BreakEvenPips)
be_buffer = float(self.BreakEvenBufferPips)
trail_pips = float(self.TrailingStopPips)
take_profit_distance = tp_pips * pip_size if tp_pips > 0 else 0.0
stop_loss_distance = sl_pips * pip_size if sl_pips > 0 else 0.0
break_even_distance = be_pips * pip_size if be_pips > 0 and be_pips < 9000 else 0.0
break_even_buffer = be_buffer * pip_size if be_buffer > 0 else 0.0
trailing_distance = trail_pips * pip_size if trail_pips > 0 and trail_pips < 9000 else 0.0
abs_position = abs(self.Position)
high_price = float(candle.HighPrice)
low_price = float(candle.LowPrice)
close_price = float(candle.ClosePrice)
if self.Position > 0:
if self._highest_price == 0:
self._highest_price = entry_price
self._highest_price = max(self._highest_price, high_price)
profit_distance = close_price - entry_price
if break_even_distance > 0 and profit_distance >= break_even_distance and self._break_even_price is None:
self._break_even_price = entry_price + break_even_buffer
if self._break_even_price is not None and low_price <= self._break_even_price:
self.SellMarket(abs_position)
self._reset_position_tracking()
return
if trailing_distance > 0 and profit_distance >= trailing_distance:
candidate = self._highest_price - trailing_distance
if self._trailing_stop_price is None or candidate > self._trailing_stop_price:
self._trailing_stop_price = candidate
if self._trailing_stop_price is not None and low_price <= self._trailing_stop_price:
self.SellMarket(abs_position)
self._reset_position_tracking()
return
if take_profit_distance > 0 and profit_distance >= take_profit_distance:
self.SellMarket(abs_position)
self._reset_position_tracking()
return
if stop_loss_distance > 0 and low_price <= entry_price - stop_loss_distance:
self.SellMarket(abs_position)
self._reset_position_tracking()
elif self.Position < 0:
if self._lowest_price == 0:
self._lowest_price = entry_price
self._lowest_price = min(self._lowest_price, low_price)
profit_distance = entry_price - close_price
if break_even_distance > 0 and profit_distance >= break_even_distance and self._break_even_price is None:
self._break_even_price = entry_price - break_even_buffer
if self._break_even_price is not None and high_price >= self._break_even_price:
self.BuyMarket(abs_position)
self._reset_position_tracking()
return
if trailing_distance > 0 and profit_distance >= trailing_distance:
candidate = self._lowest_price + trailing_distance
if self._trailing_stop_price is None or candidate < self._trailing_stop_price:
self._trailing_stop_price = candidate
if self._trailing_stop_price is not None and high_price >= self._trailing_stop_price:
self.BuyMarket(abs_position)
self._reset_position_tracking()
return
if take_profit_distance > 0 and profit_distance >= take_profit_distance:
self.BuyMarket(abs_position)
self._reset_position_tracking()
return
if stop_loss_distance > 0 and high_price >= entry_price + stop_loss_distance:
self.BuyMarket(abs_position)
self._reset_position_tracking()
def _evaluate_basket_pnl(self, last_price):
bp = float(self.BasketProfitCurrency)
bl = float(self.BasketLossCurrency)
if bp <= 0 and bl <= 0:
return False
if self.Position == 0:
return False
entry_price = self._entry_price
if entry_price == 0:
return False
step = self._ensure_pip_size()
price_move = last_price - entry_price if self.Position > 0 else entry_price - last_price
pip_move = price_move / step if step > 0 else price_move
currency_pnl = pip_move * step * abs(self.Position)
self._max_basket_pnl = max(self._max_basket_pnl, currency_pnl)
self._min_basket_pnl = min(self._min_basket_pnl, currency_pnl)
should_close_profit = bp > 0 and currency_pnl >= bp
should_close_loss = bl > 0 and currency_pnl <= -bl
if should_close_profit or should_close_loss:
self._close_all_positions()
return True
return False
def _close_all_positions(self):
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(abs(self.Position))
self._reset_position_tracking()
def _reset_position_tracking(self):
self._break_even_price = None
self._trailing_stop_price = None
self._highest_price = 0.0
self._lowest_price = 0.0
def _is_within_trading_hours(self, time):
hour = time.Hour
start = self.StartHour
stop = self.StopHour
if start == stop:
return True
if start < stop:
return hour >= start and hour < stop
return hour >= start or hour < stop
def _calculate_pip_size(self):
ps = self.Security.PriceStep if self.Security is not None else None
step = float(ps) if ps is not None else 0.0
return step if step > 0 else 0.0001
def _ensure_pip_size(self):
if self._pip_size <= 0:
self._pip_size = self._calculate_pip_size()
return self._pip_size
def OnOwnTradeReceived(self, trade):
super(divergence_trader_classic_strategy, self).OnOwnTradeReceived(trade)
if self.Position != 0 and self._entry_price == 0:
self._entry_price = float(trade.Trade.Price)
if self.Position == 0:
self._entry_price = 0.0
def OnReseted(self):
super(divergence_trader_classic_strategy, self).OnReseted()
self._previous_spread = None
self._pip_size = 0.0
self._max_basket_pnl = 0.0
self._min_basket_pnl = 0.0
self._break_even_price = None
self._trailing_stop_price = None
self._highest_price = 0.0
self._lowest_price = 0.0
self._entry_price = 0.0
def CreateClone(self):
return divergence_trader_classic_strategy()