Esta estrategia coloca un par de órdenes stop pendientes alrededor del precio actual y las gestiona a medida que el mercado evoluciona. Está destinada a operar durante publicaciones de noticias donde se esperan movimientos bruscos.
Cómo funciona
Cuando está sin posición, la estrategia coloca:
Una orden de buy stop en Ask + Step.
Una orden de sell stop en Bid - Step.
Las órdenes pendientes se reprician cada TimeModify segundos si el mercado se ha movido al menos StepTrail.
Cuando se ejecuta una orden, la orden pendiente opuesta se cancela.
Se crean un stop loss protector y un take profit opcional basados en el precio de entrada.
El stop loss puede moverse al punto de equilibrio tras una ganancia definida y luego seguir el precio a medida que avanza.
La estrategia opera con datos de Nivel1 y no depende de ningún indicador.
Parámetros
Parámetro
Predeterminado
Descripción
Step
10
Distancia en ticks para colocar las órdenes stop pendientes.
StopLoss
10
Stop loss inicial en ticks.
TakeProfit
50
Take profit en ticks (0 lo desactiva).
TrailingStop
10
Distancia del trailing stop en ticks.
TrailingStart
0
Ganancia en ticks antes de activar el trailing.
StepTrail
2
Cambio mínimo en el precio del stop (en ticks) para enviar una nueva orden stop.
BreakEven
false
Mover el stop a la entrada al alcanzar MinProfitBreakEven.
MinProfitBreakEven
0
Ganancia en ticks requerida para mover el stop al punto de equilibrio.
TimeModify
30
Segundos entre intentos de repricio de órdenes pendientes.
Notas
Las órdenes se gestionan mediante la API de alto nivel de StockSharp.
La estrategia cancela las órdenes protectoras cuando la posición se cierra.
Solo se proporciona la versión en C#; no se incluye implementación en Python.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// News-style volatility breakout strategy.
/// Enters on ATR expansion with momentum confirmation via EMA.
/// </summary>
public class NewsPendingOrdersStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMult;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevAtr;
private decimal _entryPrice;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public decimal AtrMult { get => _atrMult.Value; set => _atrMult.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public NewsPendingOrdersStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA trend period", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period", "Indicators");
_atrMult = Param(nameof(AtrMult), 1.5m)
.SetDisplay("ATR Mult", "ATR expansion multiplier", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_prevAtr = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var atr = new StandardDeviation { Length = AtrPeriod };
SubscribeCandles(CandleType).Bind(ema, atr, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal ema, decimal atr)
{
if (candle.State != CandleStates.Finished) return;
if (_prevAtr <= 0) { _prevAtr = atr; return; }
var close = candle.ClosePrice;
var bodySize = Math.Abs(candle.ClosePrice - candle.OpenPrice);
// Volatility expansion: big body candle relative to stddev
var expansion = bodySize > atr * 0.5m;
if (expansion && close > ema && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
_entryPrice = close;
}
else if (expansion && close < ema && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
_entryPrice = close;
}
// Exit long
else if (Position > 0)
{
if (close < ema || (_entryPrice > 0 && close <= _entryPrice - atr * 2))
{
SellMarket();
_entryPrice = 0;
}
}
// Exit short
else if (Position < 0)
{
if (close > ema || (_entryPrice > 0 && close >= _entryPrice + atr * 2))
{
BuyMarket();
_entryPrice = 0;
}
}
_prevAtr = atr;
}
}
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.Indicators import ExponentialMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
class news_pending_orders_strategy(Strategy):
def __init__(self):
super(news_pending_orders_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 10) \
.SetDisplay("EMA Period", "EMA trend period", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period", "Indicators")
self._atr_mult = self.Param("AtrMult", 1.5) \
.SetDisplay("ATR Mult", "ATR expansion multiplier", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_atr = 0.0
self._entry_price = 0.0
@property
def ema_period(self):
return self._ema_period.Value
@property
def atr_period(self):
return self._atr_period.Value
@property
def atr_mult(self):
return self._atr_mult.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(news_pending_orders_strategy, self).OnReseted()
self._prev_atr = 0.0
self._entry_price = 0.0
def OnStarted2(self, time):
super(news_pending_orders_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
atr = StandardDeviation()
atr.Length = self.atr_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, atr, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle, ema, atr):
if candle.State != CandleStates.Finished:
return
if self._prev_atr <= 0:
self._prev_atr = atr
return
close = candle.ClosePrice
body_size = abs(float(candle.ClosePrice) - float(candle.OpenPrice))
# Volatility expansion: big body candle relative to stddev
expansion = body_size > atr * 0.5
if expansion and close > ema and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
elif expansion and close < ema and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
# Exit long
elif self.Position > 0:
if close < ema or (self._entry_price > 0 and close <= self._entry_price - atr * 2):
self.SellMarket()
self._entry_price = 0
# Exit short
elif self.Position < 0:
if close > ema or (self._entry_price > 0 and close >= self._entry_price + atr * 2):
self.BuyMarket()
self._entry_price = 0
self._prev_atr = atr
def CreateClone(self):
return news_pending_orders_strategy()