e-Skoch Pending Orders Strategy
Overview
The e-Skoch Pending Orders Strategy recreates the original MetaTrader expert advisor that waits for a new bar, analyses the two most recent highs and lows on both the trading timeframe and the daily timeframe, and places pending breakout orders. The goal is to catch momentum when the market breaks through the previous bar after a short-term pullback confirmed by the daily trend.
The StockSharp implementation keeps the original ideas but uses high-level API features such as candle subscriptions, automatic protection orders, and strategy parameters. The C# version is stored inside the CS/ folder and no Python port is provided yet.
Trading Logic
- On every finished candle the strategy retrieves the highs and lows of the previous two candles on the working timeframe and the previous two daily candles.
- If the last daily high is lower than the high from two days ago and the previous intraday high is lower than the one before it, the strategy places a buy stop above the latest intraday high plus a configurable buffer.
- If the last daily low is higher than the low from two days ago and the previous intraday low is higher than the one before it, the strategy places a sell stop below the latest intraday low minus a configurable buffer.
- Each pending order sets individual stop-loss and take-profit levels. When an entry is triggered the strategy immediately submits protective stop and limit orders for the open position.
- When no positions or orders are active the strategy records the current equity as a baseline. If the account equity grows by the configured percentage relative to that baseline, all positions are closed and protective orders are cancelled.
- Optional blocking (
CheckExistingTrade) prevents new entries while any position is open, mirroring the original “CheckTrade” input parameter.
Parameters
| Parameter |
Description |
CandleType |
Primary timeframe used for signals. Default: 1-hour candles. |
TakeProfitBuyPips / StopLossBuyPips |
Long-side profit and loss offsets measured in pips. |
TakeProfitSellPips / StopLossSellPips |
Short-side profit and loss offsets measured in pips. |
IndentHighPips / IndentLowPips |
Distance in pips from the latest high or low used to place stop orders. |
CheckExistingTrade |
When true, new orders are skipped while any position is open. |
PercentEquity |
Percentage gain on equity required to exit all positions. |
Volume |
Order size (default 0.01 lot to match the original expert advisor). |
Risk Management
- Buy stop orders place a stop-loss below the entry price and a take-profit above it.
- Sell stop orders place a stop-loss above the entry price and a take-profit below it.
- Protective orders are automatically cancelled when the position closes or when a new protection set is created.
- The equity growth check acts as a global “circuit breaker” to lock in profits before trading resumes.
Notes
- The strategy requires both the trading timeframe and daily candles, so make sure data for both subscriptions is available in Designer or during backtests.
- Pip conversion automatically adjusts for symbols that use fractional pip pricing (3 or 5 decimal digits) by multiplying the price step by 10.
- The logic assumes a single aggregated position; simultaneous long and short exposure is intentionally avoided when
CheckExistingTrade is enabled.
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()