E-Skoch-Open-Strategie (StockSharp-Port)
Überblick
Die E-Skoch-Open-Strategie repliziert den ursprünglichen MetaTrader 5 Expert Advisor, der ein einfaches Drei-Kerzen-Muster handelt. Die StockSharp-Implementierung verarbeitet abgeschlossene Kerzen, wertet Momentum-Umkehrungen in den jüngsten Schlusskursen aus und öffnet eine neue Position, wenn die erforderliche Konfiguration erscheint. Das Risiko wird durch Stop-Loss/Take-Profit-Abstände gesteuert, die in angepassten Punkten (Pips) gemessen werden, sowie durch ein Eigenkapitalwachstumsziel, das alle offenen Positionen flachlegen kann. Die Positionsgrößenbestimmung folgt einem Martingale-Schema: Nach einem Verlusttrade wird die nächste Ordergröße mit 1.6 multipliziert, während profitable Trades das Volumen auf den Anfangswert zurücksetzen.
Handelslogik
- Arbeitet mit dem durch den Parameter
CandleTypedefinierten Zeitrahmen (Standard: 1 Stunde). - Wartet, bis mindestens drei abgeschlossene Kerzen verfügbar sind.
- Kauf-Setup: wenn
Close[n-3] > Close[n-2]undClose[n-1] < Close[n-2], und Long-Trades aktiviert sind. - Verkauf-Setup: wenn
Close[n-3] > Close[n-2]undClose[n-2] < Close[n-1], und Short-Trades aktiviert sind. - Wenn
CloseOnOppositeSignalaktiviert ist, schließt ein entgegengesetztes Signal die bestehende Position sofort und überspringt neue Einträge für die aktuelle Bar. - Für jede neue Position fügt die Strategie statische Stop-Loss- und Take-Profit-Level hinzu, die vom aktuellen Schlusskurs und dem konfigurierten Abstand in angepassten Punkten berechnet werden. Wenn das Hoch/Tief einer abgeschlossenen Kerze eines dieser Niveaus erreicht, wird die Position geschlossen.
- Die Strategie prüft kontinuierlich das Kontokapital. Wenn das Kapitalwachstum relativ zum letzten Flat-Moment
TargetProfitPercentübersteigt, werden alle Positionen geschlossen. - Nachdem ein Trade mit Verlust schließt, wird das nächste Ordervolumen mit 1.6 multipliziert. Nach einem profitablen Trade kehrt das Volumen zur anfänglichen Größe zurück. Volumen werden unter Verwendung der Instrumentbeschränkungen normalisiert (
VolumeStep,VolumeMin,VolumeMax).
Parameter
| Parameter | Beschreibung |
|---|---|
CandleType |
Für die Mustererkennung verwendeter Zeitrahmen. Funktioniert mit allen von StockSharp unterstützten Kerzen. |
InitialOrderVolume |
Basis-Losgröße für den ersten Trade in einer Sequenz (Standard: 0.01). |
StopLossPoints |
Stop-Loss-Abstand ausgedrückt in angepassten Punkten. Bei 5-stelligen oder 3-stelligen Instrumenten beträgt der Punktwert PriceStep * 10, sonst PriceStep. |
TakeProfitPoints |
Take-Profit-Abstand unter Verwendung derselben angepassten Punktkonvention. |
EnableBuySignals / EnableSellSignals |
Long- oder Short-Einträge umschalten. |
MaxBuyTrades / MaxSellTrades |
Maximale Anzahl aufeinanderfolgender Trades pro Richtung (-1 entfernt das Limit). Der Port hält standardmäßig höchstens eine Position pro Richtung. |
TargetProfitPercent |
Kapitalgewinn-Prozentsatz, der das Schließen aller Positionen auslöst (Standard: 1.2%). |
CloseOnOppositeSignal |
Wenn aktiviert, zwingt ein Signal in die entgegengesetzte Richtung zu einer Flat-Position, bevor neue Trades in Betracht gezogen werden. |
Risikohinweise
- Stop-Loss- und Take-Profit-Level werden aus Kerzenhochs/-tiefs simuliert. Im Live-Handel kann die Intrabar-Ausführung von MetaTrader abweichen, wo Schutzorders auf dem Server registriert sind.
- Der Martingale-Multiplikator (1.6) kann Volumen während Drawdowns schnell anwachsen lassen. Sicherstellen, dass die Instrumentgrenzen (
VolumeMax) und das Portfoliokapital die größte erwartete Position unterstützen können. - Gewinnsperre auf Kapitalbasis funktioniert nur, wenn Portfolio-Informationen über
Portfolio.CurrentValueverfügbar sind.
Verwendungstipps
CandleTypeanpassen, um dem im ursprünglichen Expert Advisor verwendeten Zeitrahmen zu entsprechen.StopLossPoints/TakeProfitPointsan die Instrumentvolatilität anpassen; sie sind pip-basiert dank der angepassten Punktberechnung.- Eine Richtung deaktivieren, wenn Hedging vom Broker oder der Risikorichtlinie nicht erlaubt ist.
- Bei langen Tests die Kapitalzielsetzung und Martingale-Einstellungen im Auge behalten, um unerwartete Liquidationen zu vermeiden.
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>
/// Port of the "E-Skoch-Open" MetaTrader strategy using StockSharp high level API.
/// The strategy reacts to a three-candle closing price pattern and applies
/// martingale position sizing together with equity based stops.
/// </summary>
public class ESkochOpenStrategy : Strategy
{
private readonly StrategyParam<decimal> _martingaleMultiplier;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<bool> _enableBuySignals;
private readonly StrategyParam<bool> _enableSellSignals;
private readonly StrategyParam<decimal> _targetProfitPercent;
private readonly StrategyParam<bool> _closeOnOppositeSignal;
private readonly StrategyParam<int> _maxBuyTrades;
private readonly StrategyParam<int> _maxSellTrades;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _initialOrderVolume;
private decimal _pointValue;
private decimal _currentVolume;
private decimal _entryEquity;
private decimal _baselineEquity;
private bool _positionTracked;
private decimal? _closeMinus1;
private decimal? _closeMinus2;
private decimal? _closeMinus3;
private decimal? _longStop;
private decimal? _longTake;
private decimal? _shortStop;
private decimal? _shortTake;
private int _activeLongEntries;
private int _activeShortEntries;
private int _previousPatternSignal;
/// <summary>
/// Stop loss distance expressed in adjusted points (default: 130).
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in adjusted points (default: 200).
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Enables long entries created by the pattern.
/// </summary>
public bool EnableBuySignals
{
get => _enableBuySignals.Value;
set => _enableBuySignals.Value = value;
}
/// <summary>
/// Enables short entries created by the pattern.
/// </summary>
public bool EnableSellSignals
{
get => _enableSellSignals.Value;
set => _enableSellSignals.Value = value;
}
/// <summary>
/// Equity percentage gain that triggers closing every open position.
/// </summary>
public decimal TargetProfitPercent
{
get => _targetProfitPercent.Value;
set => _targetProfitPercent.Value = value;
}
/// <summary>
/// When true, opposite trades immediately flatten the existing position.
/// </summary>
public bool CloseOnOppositeSignal
{
get => _closeOnOppositeSignal.Value;
set => _closeOnOppositeSignal.Value = value;
}
/// <summary>
/// Maximum number of consecutive long entries (-1 disables the limit).
/// </summary>
public int MaxBuyTrades
{
get => _maxBuyTrades.Value;
set => _maxBuyTrades.Value = value;
}
/// <summary>
/// Maximum number of consecutive short entries (-1 disables the limit).
/// </summary>
public int MaxSellTrades
{
get => _maxSellTrades.Value;
set => _maxSellTrades.Value = value;
}
/// <summary>
/// Candle type used for pattern detection.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Base order volume used for the first trade in a sequence.
/// </summary>
public decimal InitialOrderVolume
{
get => _initialOrderVolume.Value;
set => _initialOrderVolume.Value = value;
}
/// <summary>
/// Volume multiplier after losses (martingale).
/// </summary>
public decimal MartingaleMultiplier
{
get => _martingaleMultiplier.Value;
set => _martingaleMultiplier.Value = value;
}
/// <summary>
/// Creates the strategy parameters with defaults similar to the MQL version.
/// </summary>
public ESkochOpenStrategy()
{
_martingaleMultiplier = Param(nameof(MartingaleMultiplier), 1.6m)
.SetGreaterThanZero()
.SetDisplay("Martingale Mult", "Volume multiplier after losses", "Risk");
_stopLossPoints = Param(nameof(StopLossPoints), 130m)
.SetDisplay("Stop Loss Points", "Loss distance measured in adjusted points", "Risk")
.SetNotNegative();
_takeProfitPoints = Param(nameof(TakeProfitPoints), 200m)
.SetDisplay("Take Profit Points", "Profit distance measured in adjusted points", "Risk")
.SetNotNegative();
_enableBuySignals = Param(nameof(EnableBuySignals), true)
.SetDisplay("Enable Buy", "Allow opening long positions", "Trading");
_enableSellSignals = Param(nameof(EnableSellSignals), true)
.SetDisplay("Enable Sell", "Allow opening short positions", "Trading");
_targetProfitPercent = Param(nameof(TargetProfitPercent), 1.2m)
.SetDisplay("Target Profit %", "Close all positions after reaching this equity growth", "Risk")
.SetNotNegative();
_closeOnOppositeSignal = Param(nameof(CloseOnOppositeSignal), false)
.SetDisplay("Close On Opposite", "Close open positions when an opposite signal appears", "Trading");
_maxBuyTrades = Param(nameof(MaxBuyTrades), 1)
.SetDisplay("Max Long Trades", "Maximum concurrent long trades", "Risk");
_maxSellTrades = Param(nameof(MaxSellTrades), 1)
.SetDisplay("Max Short Trades", "Maximum concurrent short trades", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for pattern recognition", "Data");
_initialOrderVolume = Param(nameof(InitialOrderVolume), 0.01m)
.SetDisplay("Initial Volume", "Volume of the first trade", "Trading")
.SetGreaterThanZero();
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_closeMinus1 = null;
_closeMinus2 = null;
_closeMinus3 = null;
_longStop = null;
_longTake = null;
_shortStop = null;
_shortTake = null;
_activeLongEntries = 0;
_activeShortEntries = 0;
_positionTracked = false;
_pointValue = 0m;
_currentVolume = 0m;
_entryEquity = 0m;
_baselineEquity = 0m;
_previousPatternSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = InitialOrderVolume;
_pointValue = CalculatePointValue();
_currentVolume = NormalizeVolume(InitialOrderVolume);
var equity = Portfolio?.CurrentValue ?? 0m;
_baselineEquity = equity;
_entryEquity = equity;
_positionTracked = Position != 0;
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;
}
CheckEquityTarget();
if (CheckProtection(candle))
{
// Skip new entries if a protection exit already triggered on this bar.
UpdateCloses(candle.ClosePrice);
return;
}
// removed IsFormedAndOnlineAndAllowTrading check for backtesting
if (_closeMinus1.HasValue && _closeMinus2.HasValue && _closeMinus3.HasValue)
{
var close1 = _closeMinus1.Value;
var close2 = _closeMinus2.Value;
var close3 = _closeMinus3.Value;
var buySignal = close3 > close2 && close1 < close2;
var sellSignal = close3 > close2 && close2 < close1;
var patternSignal = buySignal ? 1 : sellSignal ? -1 : 0;
if (buySignal && patternSignal != _previousPatternSignal)
{
HandleBuySignal(candle);
}
if (sellSignal && patternSignal != _previousPatternSignal)
{
HandleSellSignal(candle);
}
_previousPatternSignal = patternSignal;
}
else
{
_previousPatternSignal = 0;
}
UpdateCloses(candle.ClosePrice);
}
private void HandleBuySignal(ICandleMessage candle)
{
if (!EnableBuySignals)
{
return;
}
if (CloseOnOppositeSignal && Position < 0)
{
BuyMarket(Math.Abs(Position));
return;
}
if (Position > 0)
{
return;
}
if (MaxBuyTrades != -1 && _activeLongEntries >= MaxBuyTrades)
{
return;
}
var volume = NormalizeVolume(_currentVolume);
if (volume <= 0m)
{
return;
}
BuyMarket(volume);
_activeLongEntries++;
_positionTracked = true;
_entryEquity = Portfolio?.CurrentValue ?? _entryEquity;
SetupProtection(true, candle.ClosePrice);
}
private void HandleSellSignal(ICandleMessage candle)
{
if (!EnableSellSignals)
{
return;
}
if (CloseOnOppositeSignal && Position > 0)
{
SellMarket(Math.Abs(Position));
return;
}
if (Position < 0)
{
return;
}
if (MaxSellTrades != -1 && _activeShortEntries >= MaxSellTrades)
{
return;
}
var volume = NormalizeVolume(_currentVolume);
if (volume <= 0m)
{
return;
}
SellMarket(volume);
_activeShortEntries++;
_positionTracked = true;
_entryEquity = Portfolio?.CurrentValue ?? _entryEquity;
SetupProtection(false, candle.ClosePrice);
}
private bool CheckProtection(ICandleMessage candle)
{
if (Position > 0)
{
if (_longStop.HasValue && candle.LowPrice <= _longStop.Value)
{
SellMarket(Math.Abs(Position));
ResetProtection();
return true;
}
if (_longTake.HasValue && candle.HighPrice >= _longTake.Value)
{
SellMarket(Math.Abs(Position));
ResetProtection();
return true;
}
}
else if (Position < 0)
{
if (_shortStop.HasValue && candle.HighPrice >= _shortStop.Value)
{
BuyMarket(Math.Abs(Position));
ResetProtection();
return true;
}
if (_shortTake.HasValue && candle.LowPrice <= _shortTake.Value)
{
BuyMarket(Math.Abs(Position));
ResetProtection();
return true;
}
}
return false;
}
private void SetupProtection(bool isLong, decimal referencePrice)
{
var point = _pointValue;
if (point <= 0m)
{
point = Security?.PriceStep ?? 0m;
}
if (isLong)
{
_longStop = StopLossPoints > 0m ? referencePrice - StopLossPoints * point : null;
_longTake = TakeProfitPoints > 0m ? referencePrice + TakeProfitPoints * point : null;
_shortStop = null;
_shortTake = null;
}
else
{
_shortStop = StopLossPoints > 0m ? referencePrice + StopLossPoints * point : null;
_shortTake = TakeProfitPoints > 0m ? referencePrice - TakeProfitPoints * point : null;
_longStop = null;
_longTake = null;
}
}
private void ResetProtection()
{
_longStop = null;
_longTake = null;
_shortStop = null;
_shortTake = null;
}
private void UpdateCloses(decimal close)
{
_closeMinus3 = _closeMinus2;
_closeMinus2 = _closeMinus1;
_closeMinus1 = close;
}
private void CheckEquityTarget()
{
if (TargetProfitPercent <= 0m)
{
return;
}
if (_baselineEquity <= 0m)
{
return;
}
var equity = Portfolio?.CurrentValue ?? 0m;
var growthPercent = (equity - _baselineEquity) / _baselineEquity * 100m;
if (growthPercent >= TargetProfitPercent)
{
CloseAllPositions();
}
}
private void CloseAllPositions()
{
if (Position > 0)
{
SellMarket(Math.Abs(Position));
}
else if (Position < 0)
{
BuyMarket(Math.Abs(Position));
}
}
/// <inheritdoc />
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
if (Position == 0)
{
if (_positionTracked)
{
var equity = Portfolio?.CurrentValue ?? _baselineEquity;
if (equity >= _entryEquity)
{
_currentVolume = NormalizeVolume(InitialOrderVolume);
}
else
{
_currentVolume = NormalizeVolume(_currentVolume * MartingaleMultiplier);
}
_baselineEquity = equity;
_positionTracked = false;
ResetProtection();
_activeLongEntries = 0;
_activeShortEntries = 0;
}
else
{
_baselineEquity = Portfolio?.CurrentValue ?? _baselineEquity;
}
}
else
{
_positionTracked = true;
_entryEquity = Portfolio?.CurrentValue ?? _entryEquity;
}
}
private decimal NormalizeVolume(decimal volume)
{
if (volume <= 0m)
{
return 0m;
}
var sec = Security;
if (sec != null)
{
var step = sec.VolumeStep ?? 0m;
if (step > 0m)
{
volume = Math.Floor(volume / step) * step;
}
var min = sec.MinVolume ?? 0m;
if (min > 0m && volume < min)
{
volume = min;
}
var max = sec.MaxVolume ?? 0m;
if (max > 0m && volume > max)
{
volume = max;
}
}
return volume;
}
private decimal CalculatePointValue()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
{
return 0m;
}
var decimals = CountDecimals(step);
return decimals == 3 || decimals == 5 ? step * 10m : step;
}
private static int CountDecimals(decimal value)
{
value = Math.Abs(value);
var decimals = 0;
while (value != Math.Truncate(value) && decimals < 10)
{
value *= 10m;
decimals++;
}
return decimals;
}
}
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
class e_skoch_open_strategy(Strategy):
"""
Port of the E-Skoch-Open MetaTrader strategy.
Reacts to a three-candle closing price pattern and applies
martingale position sizing together with equity-based stops.
"""
def __init__(self):
super(e_skoch_open_strategy, self).__init__()
self._martingale_multiplier = self.Param("MartingaleMultiplier", 1.6) \
.SetDisplay("Martingale Mult", "Volume multiplier after losses", "Risk")
self._stop_loss_points = self.Param("StopLossPoints", 130.0) \
.SetDisplay("Stop Loss Points", "Loss distance in adjusted points", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 200.0) \
.SetDisplay("Take Profit Points", "Profit distance in adjusted points", "Risk")
self._enable_buy = self.Param("EnableBuySignals", True) \
.SetDisplay("Enable Buy", "Allow opening long positions", "Trading")
self._enable_sell = self.Param("EnableSellSignals", True) \
.SetDisplay("Enable Sell", "Allow opening short positions", "Trading")
self._target_profit_pct = self.Param("TargetProfitPercent", 1.2) \
.SetDisplay("Target Profit %", "Close all after equity growth", "Risk")
self._close_on_opposite = self.Param("CloseOnOppositeSignal", False) \
.SetDisplay("Close On Opposite", "Close positions on opposite signal", "Trading")
self._max_buy_trades = self.Param("MaxBuyTrades", 1) \
.SetDisplay("Max Long Trades", "Maximum concurrent long trades", "Risk")
self._max_sell_trades = self.Param("MaxSellTrades", 1) \
.SetDisplay("Max Short Trades", "Maximum concurrent short trades", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe for pattern recognition", "Data")
self._initial_volume = self.Param("InitialOrderVolume", 0.01) \
.SetDisplay("Initial Volume", "Volume of the first trade", "Trading")
self._point_value = 0.0
self._current_volume = 0.0
self._entry_equity = 0.0
self._baseline_equity = 0.0
self._position_tracked = False
self._close_m1 = None
self._close_m2 = None
self._close_m3 = None
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
self._active_long_entries = 0
self._active_short_entries = 0
self._prev_pattern_signal = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(e_skoch_open_strategy, self).OnReseted()
self._close_m1 = None
self._close_m2 = None
self._close_m3 = None
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
self._active_long_entries = 0
self._active_short_entries = 0
self._position_tracked = False
self._point_value = 0.0
self._current_volume = 0.0
self._entry_equity = 0.0
self._baseline_equity = 0.0
self._prev_pattern_signal = 0
def OnStarted2(self, time):
super(e_skoch_open_strategy, self).OnStarted2(time)
self.Volume = self._initial_volume.Value
self._point_value = self._calculate_point_value()
self._current_volume = self._initial_volume.Value
equity = 0.0
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
equity = float(self.Portfolio.CurrentValue)
self._baseline_equity = equity
self._entry_equity = equity
self._position_tracked = self.Position != 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._check_equity_target()
if self._check_protection(candle):
self._update_closes(float(candle.ClosePrice))
return
if self._close_m1 is not None and self._close_m2 is not None and self._close_m3 is not None:
c1 = self._close_m1
c2 = self._close_m2
c3 = self._close_m3
buy_signal = c3 > c2 and c1 < c2
sell_signal = c3 > c2 and c2 < c1
pattern_signal = 1 if buy_signal else (-1 if sell_signal else 0)
if buy_signal and pattern_signal != self._prev_pattern_signal:
self._handle_buy_signal(candle)
if sell_signal and pattern_signal != self._prev_pattern_signal:
self._handle_sell_signal(candle)
self._prev_pattern_signal = pattern_signal
else:
self._prev_pattern_signal = 0
self._update_closes(float(candle.ClosePrice))
def _handle_buy_signal(self, candle):
if not self._enable_buy.Value:
return
if self._close_on_opposite.Value and self.Position < 0:
self.BuyMarket(abs(float(self.Position)))
return
if self.Position > 0:
return
max_buy = self._max_buy_trades.Value
if max_buy != -1 and self._active_long_entries >= max_buy:
return
volume = self._current_volume
if volume <= 0:
return
self.BuyMarket(volume)
self._active_long_entries += 1
self._position_tracked = True
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
self._entry_equity = float(self.Portfolio.CurrentValue)
self._setup_protection(True, float(candle.ClosePrice))
def _handle_sell_signal(self, candle):
if not self._enable_sell.Value:
return
if self._close_on_opposite.Value and self.Position > 0:
self.SellMarket(abs(float(self.Position)))
return
if self.Position < 0:
return
max_sell = self._max_sell_trades.Value
if max_sell != -1 and self._active_short_entries >= max_sell:
return
volume = self._current_volume
if volume <= 0:
return
self.SellMarket(volume)
self._active_short_entries += 1
self._position_tracked = True
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
self._entry_equity = float(self.Portfolio.CurrentValue)
self._setup_protection(False, float(candle.ClosePrice))
def _check_protection(self, candle):
if self.Position > 0:
if self._long_stop is not None and float(candle.LowPrice) <= self._long_stop:
self.SellMarket(abs(float(self.Position)))
self._reset_protection()
return True
if self._long_take is not None and float(candle.HighPrice) >= self._long_take:
self.SellMarket(abs(float(self.Position)))
self._reset_protection()
return True
elif self.Position < 0:
if self._short_stop is not None and float(candle.HighPrice) >= self._short_stop:
self.BuyMarket(abs(float(self.Position)))
self._reset_protection()
return True
if self._short_take is not None and float(candle.LowPrice) <= self._short_take:
self.BuyMarket(abs(float(self.Position)))
self._reset_protection()
return True
return False
def _setup_protection(self, is_long, ref_price):
point = self._point_value
if point <= 0.0:
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
point = step if step > 0 else 1.0
sl = self._stop_loss_points.Value
tp = self._take_profit_points.Value
if is_long:
self._long_stop = ref_price - sl * point if sl > 0 else None
self._long_take = ref_price + tp * point if tp > 0 else None
self._short_stop = None
self._short_take = None
else:
self._short_stop = ref_price + sl * point if sl > 0 else None
self._short_take = ref_price - tp * point if tp > 0 else None
self._long_stop = None
self._long_take = None
def _reset_protection(self):
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
def _update_closes(self, close):
self._close_m3 = self._close_m2
self._close_m2 = self._close_m1
self._close_m1 = close
def _check_equity_target(self):
if self._target_profit_pct.Value <= 0.0:
return
if self._baseline_equity <= 0.0:
return
equity = 0.0
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
equity = float(self.Portfolio.CurrentValue)
growth = (equity - self._baseline_equity) / self._baseline_equity * 100.0
if growth >= self._target_profit_pct.Value:
if self.Position > 0:
self.SellMarket(abs(float(self.Position)))
elif self.Position < 0:
self.BuyMarket(abs(float(self.Position)))
def OnPositionReceived(self, position):
super(e_skoch_open_strategy, self).OnPositionReceived(position)
if self.Position == 0:
if self._position_tracked:
equity = self._baseline_equity
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
equity = float(self.Portfolio.CurrentValue)
if equity >= self._entry_equity:
self._current_volume = self._initial_volume.Value
else:
self._current_volume = self._current_volume * self._martingale_multiplier.Value
self._baseline_equity = equity
self._position_tracked = False
self._reset_protection()
self._active_long_entries = 0
self._active_short_entries = 0
else:
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
self._baseline_equity = float(self.Portfolio.CurrentValue)
else:
self._position_tracked = True
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
self._entry_equity = float(self.Portfolio.CurrentValue)
def _calculate_point_value(self):
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0.0:
return 0.0
decimals = self._count_decimals(step)
return step * 10.0 if decimals == 3 or decimals == 5 else step
@staticmethod
def _count_decimals(value):
value = abs(value)
decimals = 0
while value != int(value) and decimals < 10:
value *= 10.0
decimals += 1
return decimals
def CreateClone(self):
return e_skoch_open_strategy()