E Skoch-Strategie für schwebende Orders
Übersicht
Die E Skoch-Strategie für schwebende Orders recreiert den ursprünglichen MetaTrader-Expertenberater, der auf eine neue Bar wartet, die zwei neuesten Hochs und Tiefs sowohl im Trading-Zeitrahmen als auch im Tages-Zeitrahmen analysiert und schwebende Breakout-Orders platziert. Das Ziel ist es, Momentum zu erfassen, wenn der Markt nach einem kurzfristigen Rücksetzer, der durch den Tagestrend bestätigt wird, durch die vorherige Bar bricht.
Die StockSharp-Implementierung behält die ursprünglichen Ideen bei, verwendet jedoch High-Level-API-Funktionen wie Kerzenabonnements, automatische Schutzorders und Strategieparameter. Die C#-Version ist im Ordner CS/ gespeichert und es wird noch kein Python-Port bereitgestellt.
Handelslogik
- Bei jeder abgeschlossenen Kerze ruft die Strategie die Hochs und Tiefs der vorherigen zwei Kerzen im Arbeitszeitrahmen und der vorherigen zwei Tageskerzen ab.
- Wenn das letzte Tageshoch niedriger ist als das vor zwei Tagen und das vorherige Intradayhoch niedriger ist als das davor, platziert die Strategie einen Buy Stop über dem letzten Intradayhoch plus einem konfigurierbaren Puffer.
- Wenn das letzte Tagestief höher ist als das vor zwei Tagen und das vorherige Intradaytief höher ist als das davor, platziert die Strategie einen Sell Stop unter dem letzten Intradaytief minus einem konfigurierbaren Puffer.
- Jede schwebende Order setzt individuelle Stop-Loss- und Take-Profit-Level. Wenn ein Einstieg ausgelöst wird, sendet die Strategie sofort Schutz-Stop- und Limit-Orders für die offene Position.
- Wenn keine Positionen oder Orders aktiv sind, zeichnet die Strategie das aktuelle Eigenkapital als Basislinie auf. Wenn das Kontoeigenkapital um den konfigurierten Prozentsatz relativ zu dieser Basislinie wächst, werden alle Positionen geschlossen und Schutzorders storniert.
- Optionale Blockierung (
CheckExistingTrade) verhindert neue Einstiege, während eine Position offen ist, und spiegelt den ursprünglichen Eingabeparameter "CheckTrade" wider.
Parameter
| Parameter |
Beschreibung |
CandleType |
Primärer Zeitrahmen für Signale. Standard: 1-Stunden-Kerzen. |
TakeProfitBuyPips / StopLossBuyPips |
Long-seitige Gewinn- und Verlustversätze in Pips. |
TakeProfitSellPips / StopLossSellPips |
Short-seitige Gewinn- und Verlustversätze in Pips. |
IndentHighPips / IndentLowPips |
Abstand in Pips vom letzten Hoch oder Tief zur Platzierung von Stop-Orders. |
CheckExistingTrade |
Wenn true, werden neue Orders übersprungen, während eine Position offen ist. |
PercentEquity |
Prozentualer Eigenkapitalgewinn, der für den Ausstieg aus allen Positionen erforderlich ist. |
Volume |
Ordergröße (Standard 0,01 Lot, um dem ursprünglichen Expertenberater zu entsprechen). |
Risikomanagement
- Buy-Stop-Orders platzieren einen Stop-Loss unter dem Einstiegspreis und einen Take-Profit darüber.
- Sell-Stop-Orders platzieren einen Stop-Loss über dem Einstiegspreis und einen Take-Profit darunter.
- Schutzorders werden automatisch storniert, wenn die Position schließt oder wenn ein neuer Schutz-Set erstellt wird.
- Die Eigenkapital-Wachstumsprüfung fungiert als globaler "Sicherungsautomat", um Gewinne zu sichern, bevor der Handel fortgesetzt wird.
Hinweise
- Die Strategie erfordert sowohl den Handelszeitrahmen als auch Tageskerzen, stellen Sie also sicher, dass Daten für beide Abonnements in Designer oder während Backtests verfügbar sind.
- Die Pip-Konvertierung passt sich automatisch an Symbole an, die Bruch-Pip-Preise verwenden (3 oder 5 Dezimalstellen), indem der Preisschritt mit 10 multipliziert wird.
- Die Logik geht von einer einzigen aggregierten Position aus; simultane Long- und Short-Exposition wird absichtlich vermieden, wenn
CheckExistingTrade aktiviert ist.
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>
/// Pending breakout strategy based on the e-Skoch pending orders idea.
/// Detects falling highs or rising lows across two timeframes to enter on breakouts.
/// </summary>
public class ESkochPendingOrdersStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _takeProfitBuyPips;
private readonly StrategyParam<decimal> _stopLossBuyPips;
private readonly StrategyParam<decimal> _takeProfitSellPips;
private readonly StrategyParam<decimal> _stopLossSellPips;
private readonly StrategyParam<decimal> _indentHighPips;
private readonly StrategyParam<decimal> _indentLowPips;
private readonly StrategyParam<bool> _checkExistingTrade;
private decimal? _prevHigh1;
private decimal? _prevHigh2;
private decimal? _prevLow1;
private decimal? _prevLow2;
private decimal? _pendingBuyPrice;
private decimal? _pendingSellPrice;
private decimal _entryPrice;
private decimal _longStop;
private decimal _longTake;
private decimal _shortStop;
private decimal _shortTake;
private decimal _pipValue;
/// <summary>
/// Main candle type for signal evaluation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal TakeProfitBuyPips
{
get => _takeProfitBuyPips.Value;
set => _takeProfitBuyPips.Value = value;
}
public decimal StopLossBuyPips
{
get => _stopLossBuyPips.Value;
set => _stopLossBuyPips.Value = value;
}
public decimal TakeProfitSellPips
{
get => _takeProfitSellPips.Value;
set => _takeProfitSellPips.Value = value;
}
public decimal StopLossSellPips
{
get => _stopLossSellPips.Value;
set => _stopLossSellPips.Value = value;
}
public decimal IndentHighPips
{
get => _indentHighPips.Value;
set => _indentHighPips.Value = value;
}
public decimal IndentLowPips
{
get => _indentLowPips.Value;
set => _indentLowPips.Value = value;
}
public bool CheckExistingTrade
{
get => _checkExistingTrade.Value;
set => _checkExistingTrade.Value = value;
}
public ESkochPendingOrdersStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe", "General");
_takeProfitBuyPips = Param(nameof(TakeProfitBuyPips), 2000m)
.SetGreaterThanZero()
.SetDisplay("Buy TP (pips)", "Long take profit distance", "Trading");
_stopLossBuyPips = Param(nameof(StopLossBuyPips), 500m)
.SetGreaterThanZero()
.SetDisplay("Buy SL (pips)", "Long stop loss distance", "Trading");
_takeProfitSellPips = Param(nameof(TakeProfitSellPips), 2000m)
.SetGreaterThanZero()
.SetDisplay("Sell TP (pips)", "Short take profit distance", "Trading");
_stopLossSellPips = Param(nameof(StopLossSellPips), 500m)
.SetGreaterThanZero()
.SetDisplay("Sell SL (pips)", "Short stop loss distance", "Trading");
_indentHighPips = Param(nameof(IndentHighPips), 500m)
.SetGreaterThanZero()
.SetDisplay("High Indent", "Buy stop offset", "Trading");
_indentLowPips = Param(nameof(IndentLowPips), 500m)
.SetGreaterThanZero()
.SetDisplay("Low Indent", "Sell stop offset", "Trading");
_checkExistingTrade = Param(nameof(CheckExistingTrade), true)
.SetDisplay("Block During Position", "Skip signals when a position exists", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHigh1 = null;
_prevHigh2 = null;
_prevLow1 = null;
_prevLow2 = null;
_pendingBuyPrice = null;
_pendingSellPrice = null;
_entryPrice = 0m;
_longStop = 0m;
_longTake = 0m;
_shortStop = 0m;
_shortTake = 0m;
_pipValue = 1m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var priceStep = Security?.PriceStep ?? 0m;
_pipValue = priceStep <= 0m ? 1m : priceStep;
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;
// Check pending entries against current candle.
CheckPendingEntries(candle);
// Manage SL/TP for open positions.
ManagePosition(candle);
// Need at least 2 previous bars.
if (_prevHigh1 is null)
{
_prevHigh1 = candle.HighPrice;
_prevLow1 = candle.LowPrice;
return;
}
if (_prevHigh2 is null)
{
_prevHigh2 = _prevHigh1;
_prevLow2 = _prevLow1;
_prevHigh1 = candle.HighPrice;
_prevLow1 = candle.LowPrice;
return;
}
var hasPosition = Position != 0;
// Falling highs => place buy stop above recent high.
if (_prevHigh2 > _prevHigh1 && !hasPosition)
{
if (!CheckExistingTrade || Position == 0)
{
var buyPrice = _prevHigh1.Value + _pipValue * IndentHighPips;
_pendingBuyPrice = buyPrice;
_longStop = buyPrice - _pipValue * StopLossBuyPips;
_longTake = buyPrice + _pipValue * TakeProfitBuyPips;
}
}
// Rising lows => place sell stop below recent low.
if (_prevLow2 < _prevLow1 && !hasPosition)
{
if (!CheckExistingTrade || Position == 0)
{
var sellPrice = _prevLow1.Value - _pipValue * IndentLowPips;
_pendingSellPrice = sellPrice;
_shortStop = sellPrice + _pipValue * StopLossSellPips;
_shortTake = sellPrice - _pipValue * TakeProfitSellPips;
}
}
// Shift history.
_prevHigh2 = _prevHigh1;
_prevLow2 = _prevLow1;
_prevHigh1 = candle.HighPrice;
_prevLow1 = candle.LowPrice;
}
private void CheckPendingEntries(ICandleMessage candle)
{
if (Position != 0)
return;
if (_pendingBuyPrice is decimal buyPrice && candle.HighPrice >= buyPrice)
{
BuyMarket();
_entryPrice = buyPrice;
_pendingBuyPrice = null;
_pendingSellPrice = null;
return;
}
if (_pendingSellPrice is decimal sellPrice && candle.LowPrice <= sellPrice)
{
SellMarket();
_entryPrice = sellPrice;
_pendingBuyPrice = null;
_pendingSellPrice = null;
}
}
private void ManagePosition(ICandleMessage candle)
{
if (Position > 0)
{
if (_longStop > 0m && candle.LowPrice <= _longStop)
{
SellMarket();
ResetPositionState();
return;
}
if (_longTake > 0m && candle.HighPrice >= _longTake)
{
SellMarket();
ResetPositionState();
}
}
else if (Position < 0)
{
if (_shortStop > 0m && candle.HighPrice >= _shortStop)
{
BuyMarket();
ResetPositionState();
return;
}
if (_shortTake > 0m && candle.LowPrice <= _shortTake)
{
BuyMarket();
ResetPositionState();
}
}
}
private void ResetPositionState()
{
_entryPrice = 0m;
_longStop = 0m;
_longTake = 0m;
_shortStop = 0m;
_shortTake = 0m;
_pendingBuyPrice = null;
_pendingSellPrice = null;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class e_skoch_pending_orders_strategy(Strategy):
"""Pending breakout: detects falling highs or rising lows to enter on breakouts with SL/TP."""
def __init__(self):
super(e_skoch_pending_orders_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary timeframe", "General")
self._take_profit_buy_pips = self.Param("TakeProfitBuyPips", 2000.0) \
.SetGreaterThanZero() \
.SetDisplay("Buy TP (pips)", "Long take profit distance", "Trading")
self._stop_loss_buy_pips = self.Param("StopLossBuyPips", 500.0) \
.SetGreaterThanZero() \
.SetDisplay("Buy SL (pips)", "Long stop loss distance", "Trading")
self._take_profit_sell_pips = self.Param("TakeProfitSellPips", 2000.0) \
.SetGreaterThanZero() \
.SetDisplay("Sell TP (pips)", "Short take profit distance", "Trading")
self._stop_loss_sell_pips = self.Param("StopLossSellPips", 500.0) \
.SetGreaterThanZero() \
.SetDisplay("Sell SL (pips)", "Short stop loss distance", "Trading")
self._indent_high_pips = self.Param("IndentHighPips", 500.0) \
.SetGreaterThanZero() \
.SetDisplay("High Indent", "Buy stop offset", "Trading")
self._indent_low_pips = self.Param("IndentLowPips", 500.0) \
.SetGreaterThanZero() \
.SetDisplay("Low Indent", "Sell stop offset", "Trading")
self._check_existing_trade = self.Param("CheckExistingTrade", True) \
.SetDisplay("Block During Position", "Skip signals when a position exists", "Risk")
self._prev_high1 = None
self._prev_high2 = None
self._prev_low1 = None
self._prev_low2 = None
self._pending_buy_price = None
self._pending_sell_price = None
self._entry_price = 0.0
self._long_stop = 0.0
self._long_take = 0.0
self._short_stop = 0.0
self._short_take = 0.0
self._pip_value = 1.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def TakeProfitBuyPips(self):
return float(self._take_profit_buy_pips.Value)
@property
def StopLossBuyPips(self):
return float(self._stop_loss_buy_pips.Value)
@property
def TakeProfitSellPips(self):
return float(self._take_profit_sell_pips.Value)
@property
def StopLossSellPips(self):
return float(self._stop_loss_sell_pips.Value)
@property
def IndentHighPips(self):
return float(self._indent_high_pips.Value)
@property
def IndentLowPips(self):
return float(self._indent_low_pips.Value)
@property
def CheckExistingTrade(self):
return self._check_existing_trade.Value
def OnStarted2(self, time):
super(e_skoch_pending_orders_strategy, self).OnStarted2(time)
self._prev_high1 = None
self._prev_high2 = None
self._prev_low1 = None
self._prev_low2 = None
self._pending_buy_price = None
self._pending_sell_price = None
self._entry_price = 0.0
self._long_stop = 0.0
self._long_take = 0.0
self._short_stop = 0.0
self._short_take = 0.0
sec = self.Security
price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 0.0
self._pip_value = price_step if price_step > 0 else 1.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
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
# Check pending entries
self._check_pending_entries(h, lo)
# Manage SL/TP
self._manage_position(h, lo)
# Need at least 2 previous bars
if self._prev_high1 is None:
self._prev_high1 = h
self._prev_low1 = lo
return
if self._prev_high2 is None:
self._prev_high2 = self._prev_high1
self._prev_low2 = self._prev_low1
self._prev_high1 = h
self._prev_low1 = lo
return
has_position = self.Position != 0
# Falling highs -> place buy stop above recent high
if self._prev_high2 > self._prev_high1 and not has_position:
if not self.CheckExistingTrade or self.Position == 0:
buy_price = self._prev_high1 + self._pip_value * self.IndentHighPips
self._pending_buy_price = buy_price
self._long_stop = buy_price - self._pip_value * self.StopLossBuyPips
self._long_take = buy_price + self._pip_value * self.TakeProfitBuyPips
# Rising lows -> place sell stop below recent low
if self._prev_low2 < self._prev_low1 and not has_position:
if not self.CheckExistingTrade or self.Position == 0:
sell_price = self._prev_low1 - self._pip_value * self.IndentLowPips
self._pending_sell_price = sell_price
self._short_stop = sell_price + self._pip_value * self.StopLossSellPips
self._short_take = sell_price - self._pip_value * self.TakeProfitSellPips
# Shift history
self._prev_high2 = self._prev_high1
self._prev_low2 = self._prev_low1
self._prev_high1 = h
self._prev_low1 = lo
def _check_pending_entries(self, h, lo):
if self.Position != 0:
return
if self._pending_buy_price is not None and h >= self._pending_buy_price:
self.BuyMarket()
self._entry_price = self._pending_buy_price
self._pending_buy_price = None
self._pending_sell_price = None
return
if self._pending_sell_price is not None and lo <= self._pending_sell_price:
self.SellMarket()
self._entry_price = self._pending_sell_price
self._pending_buy_price = None
self._pending_sell_price = None
def _manage_position(self, h, lo):
if self.Position > 0:
if self._long_stop > 0 and lo <= self._long_stop:
self.SellMarket()
self._reset_position_state()
return
if self._long_take > 0 and h >= self._long_take:
self.SellMarket()
self._reset_position_state()
elif self.Position < 0:
if self._short_stop > 0 and h >= self._short_stop:
self.BuyMarket()
self._reset_position_state()
return
if self._short_take > 0 and lo <= self._short_take:
self.BuyMarket()
self._reset_position_state()
def _reset_position_state(self):
self._entry_price = 0.0
self._long_stop = 0.0
self._long_take = 0.0
self._short_stop = 0.0
self._short_take = 0.0
self._pending_buy_price = None
self._pending_sell_price = None
def OnReseted(self):
super(e_skoch_pending_orders_strategy, self).OnReseted()
self._prev_high1 = None
self._prev_high2 = None
self._prev_low1 = None
self._prev_low2 = None
self._pending_buy_price = None
self._pending_sell_price = None
self._entry_price = 0.0
self._long_stop = 0.0
self._long_take = 0.0
self._short_stop = 0.0
self._short_take = 0.0
self._pip_value = 1.0
def CreateClone(self):
return e_skoch_pending_orders_strategy()