This strategy places a pair of pending stop orders around the current price and manages them as the market evolves. It is intended for trading during news releases where sharp moves are expected.
How it works
When flat, the strategy places:
A buy stop order at Ask + Step.
A sell stop order at Bid - Step.
Pending orders are repriced every TimeModify seconds if the market moved by at least StepTrail.
When an order is executed, the opposite pending order is cancelled.
A protective stop-loss and optional take-profit are created based on entry price.
The stop-loss can be moved to break-even after a defined profit and then trailed as price moves further.
The strategy operates on Level1 data and does not rely on any indicators.
Parameters
Parameter
Default
Description
Step
10
Distance in ticks for placing pending stop orders.
StopLoss
10
Initial stop-loss in ticks.
TakeProfit
50
Take-profit in ticks (0 disables).
TrailingStop
10
Trailing stop distance in ticks.
TrailingStart
0
Profit in ticks before trailing is activated.
StepTrail
2
Minimum change in stop price (in ticks) to send a new stop order.
BreakEven
false
Move stop to entry after reaching MinProfitBreakEven.
MinProfitBreakEven
0
Profit in ticks required to move stop to break-even.
TimeModify
30
Seconds between pending order repricing attempts.
Notes
Orders are managed using the high-level StockSharp API.
The strategy cancels protective orders when the position is closed.
Only the C# version is provided; no Python implementation is included.
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()