Estrategia de Órdenes Pendientes E Skoch
Descripción General
La Estrategia de Órdenes Pendientes E Skoch recrea el asesor experto original de MetaTrader que espera una nueva barra, analiza los dos máximos y mínimos más recientes tanto en el marco temporal de trading como en el diario, y coloca órdenes de ruptura pendientes. El objetivo es capturar momentum cuando el mercado rompe a través de la barra anterior después de un retroceso a corto plazo confirmado por la tendencia diaria.
La implementación de StockSharp mantiene las ideas originales pero usa características de la API de alto nivel como suscripciones a velas, órdenes de protección automáticas y parámetros de estrategia. La versión en C# se almacena en la carpeta CS/ y aún no se proporciona un puerto en Python.
Lógica de Trading
- En cada vela terminada, la estrategia recupera los máximos y mínimos de las dos velas anteriores en el marco temporal de trabajo y las dos velas diarias anteriores.
- Si el último máximo diario es menor que el de hace dos días y el máximo intradía anterior es menor que el anterior, la estrategia coloca un buy stop por encima del último máximo intradía más un búfer configurable.
- Si el último mínimo diario es mayor que el de hace dos días y el mínimo intradía anterior es mayor que el anterior, la estrategia coloca un sell stop por debajo del último mínimo intradía menos un búfer configurable.
- Cada orden pendiente establece niveles individuales de stop-loss y take-profit. Cuando se activa una entrada, la estrategia envía inmediatamente órdenes de stop y límite de protección para la posición abierta.
- Cuando no hay posiciones ni órdenes activas, la estrategia registra el capital actual como línea de base. Si el capital de la cuenta crece en el porcentaje configurado relativo a esa línea de base, todas las posiciones se cierran y las órdenes de protección se cancelan.
- El bloqueo opcional (
CheckExistingTrade) evita nuevas entradas mientras cualquier posición esté abierta, imitando el parámetro de entrada original "CheckTrade".
Parámetros
| Parámetro |
Descripción |
CandleType |
Marco temporal principal utilizado para señales. Predeterminado: velas de 1 hora. |
TakeProfitBuyPips / StopLossBuyPips |
Compensaciones de ganancia y pérdida del lado largo medidas en pips. |
TakeProfitSellPips / StopLossSellPips |
Compensaciones de ganancia y pérdida del lado corto medidas en pips. |
IndentHighPips / IndentLowPips |
Distancia en pips desde el último máximo o mínimo usada para colocar órdenes de stop. |
CheckExistingTrade |
Cuando es verdadero, se omiten nuevas órdenes mientras cualquier posición esté abierta. |
PercentEquity |
Porcentaje de ganancia en capital requerido para salir de todas las posiciones. |
Volume |
Tamaño de orden (predeterminado 0.01 lote para coincidir con el asesor experto original). |
Gestión de Riesgos
- Las órdenes buy stop colocan un stop-loss por debajo del precio de entrada y un take-profit por encima.
- Las órdenes sell stop colocan un stop-loss por encima del precio de entrada y un take-profit por debajo.
- Las órdenes de protección se cancelan automáticamente cuando la posición se cierra o cuando se crea un nuevo conjunto de protección.
- La verificación de crecimiento del capital actúa como un "disyuntor" global para asegurar ganancias antes de que se reanude el trading.
Notas
- La estrategia requiere tanto el marco temporal de trading como velas diarias, así que asegúrese de que los datos para ambas suscripciones estén disponibles en Designer o durante las pruebas retrospectivas.
- La conversión de pips ajusta automáticamente los símbolos que usan precios de pip fraccionarios (3 o 5 dígitos decimales) multiplicando el paso de precio por 10.
- La lógica asume una única posición agregada; la exposición simultánea larga y corta se evita intencionalmente cuando
CheckExistingTrade está habilitado.
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()