News Hour Trade Strategy
The News Hour Trade strategy places pending buy and sell stop orders around scheduled high impact news events. Orders are offset from the current price by a fixed number of steps and include stop-loss, take-profit and optional trailing stop management.
Idea
- At the configured start hour and minute the strategy prepares for an upcoming news release.
- A buy stop and a sell stop order are placed
PriceGapsteps above and below the current price. - When one order triggers, the opposite pending order is cancelled automatically.
- The open position is protected with fixed stop-loss and take-profit levels. If
TrailStopis enabled the stop level follows the price when it moves in favor of the position. - Only one trade per day is allowed.
Parameters
- StartHour / StartMinute – time to start trading.
- DelaySeconds – pause before orders are placed (currently informational).
- Volume – order size in lots.
- StopLoss – distance to stop-loss in price steps.
- TakeProfit – distance to take-profit in steps.
- PriceGap – offset from current price for pending orders.
- Expiration – pending order lifetime in seconds (0 means no expiration).
- TrailStop – enable trailing stop.
- TrailingStop – distance from current price for trailing stop.
- TrailingGap – minimum gap before updating trailing stop.
- BuyTrade / SellTrade – enable buy or sell side orders.
- CandleType – timeframe used for time tracking.
Notes
The strategy is intended for the M5 timeframe but can be applied to any instrument with low spreads. Use with caution around major news events.
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>
/// NewsHourTrade strategy places breakout trades around scheduled news events.
/// At the configured hour/minute, it tracks price and enters on breakout above/below with SL/TP.
/// </summary>
public class NewsHourTradeStrategy : Strategy
{
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _startMinute;
private readonly StrategyParam<int> _stopLoss;
private readonly StrategyParam<int> _takeProfit;
private readonly StrategyParam<int> _priceGap;
private readonly StrategyParam<int> _tradeIntervalDays;
private readonly StrategyParam<bool> _buyTrade;
private readonly StrategyParam<bool> _sellTrade;
private readonly StrategyParam<DataType> _candleType;
private DateTime _lastTradeDay;
private decimal _tickSize;
private bool _setupConsumed;
private bool _exitSubmitted;
private decimal _entryPrice;
private decimal _stopPrice;
private decimal _takePrice;
private static readonly object _sync = new();
public int StartHour { get => _startHour.Value; set => _startHour.Value = value; }
public int StartMinute { get => _startMinute.Value; set => _startMinute.Value = value; }
public int StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public int TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public int PriceGap { get => _priceGap.Value; set => _priceGap.Value = value; }
public int TradeIntervalDays { get => _tradeIntervalDays.Value; set => _tradeIntervalDays.Value = value; }
public bool BuyTrade { get => _buyTrade.Value; set => _buyTrade.Value = value; }
public bool SellTrade { get => _sellTrade.Value; set => _sellTrade.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public NewsHourTradeStrategy()
{
_startHour = Param(nameof(StartHour), 0).SetDisplay("Start Hour", "Hour to start", "Parameters");
_startMinute = Param(nameof(StartMinute), 0).SetDisplay("Start Minute", "Minute to start", "Parameters");
_stopLoss = Param(nameof(StopLoss), 500).SetDisplay("Stop Loss", "Stop distance in steps", "Risk");
_takeProfit = Param(nameof(TakeProfit), 1000).SetDisplay("Take Profit", "Take profit distance in steps", "Risk");
_priceGap = Param(nameof(PriceGap), 10).SetDisplay("Price Gap", "Price offset in steps", "Parameters");
_tradeIntervalDays = Param(nameof(TradeIntervalDays), 365).SetGreaterThanZero().SetDisplay("Trade Interval Days", "Minimum number of calendar days between setups", "Parameters");
_buyTrade = Param(nameof(BuyTrade), true).SetDisplay("Buy Trade", "Enable buys", "Parameters");
_sellTrade = Param(nameof(SellTrade), true).SetDisplay("Sell Trade", "Enable sells", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Working timeframe", "Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lastTradeDay = default;
_tickSize = 0m;
_setupConsumed = false;
_exitSubmitted = false;
_entryPrice = 0m;
_stopPrice = 0m;
_takePrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_tickSize = Security.PriceStep ?? 1m;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
lock (_sync)
{
if (candle.State != CandleStates.Finished)
return;
var date = candle.OpenTime.Date;
// At news time, record reference price and start watching for breakout.
var enoughDaysPassed = _lastTradeDay == default || (date - _lastTradeDay).TotalDays >= TradeIntervalDays;
if (!_setupConsumed && enoughDaysPassed && candle.OpenTime.Hour == StartHour && candle.OpenTime.Minute >= StartMinute && Position == 0)
{
_lastTradeDay = date;
_setupConsumed = true;
_exitSubmitted = false;
var longBias = candle.ClosePrice >= candle.OpenPrice;
var openLong = BuyTrade && (!SellTrade || longBias);
var openShort = SellTrade && (!BuyTrade || !longBias);
if (openLong)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice - StopLoss * _tickSize;
_takePrice = _entryPrice + TakeProfit * _tickSize;
}
else if (openShort)
{
SellMarket();
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice + StopLoss * _tickSize;
_takePrice = _entryPrice - TakeProfit * _tickSize;
}
return;
}
// Manage the single open position with one market exit order.
if (Position > 0)
{
if (!_exitSubmitted && (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takePrice))
{
SellMarket(Position);
_exitSubmitted = true;
}
}
else if (Position < 0)
{
if (!_exitSubmitted && (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takePrice))
{
BuyMarket(-Position);
_exitSubmitted = true;
}
}
}
}
/// <inheritdoc />
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
lock (_sync)
{
if (Position == 0m)
{
_exitSubmitted = false;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import DateTime, TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class news_hour_trade_strategy(Strategy):
def __init__(self):
super(news_hour_trade_strategy, self).__init__()
self._start_hour = self.Param("StartHour", 0) \
.SetDisplay("Start Hour", "Hour to start", "Parameters")
self._start_minute = self.Param("StartMinute", 0) \
.SetDisplay("Start Minute", "Minute to start", "Parameters")
self._stop_loss = self.Param("StopLoss", 500) \
.SetDisplay("Stop Loss", "Stop distance in steps", "Risk")
self._take_profit = self.Param("TakeProfit", 1000) \
.SetDisplay("Take Profit", "Take profit distance in steps", "Risk")
self._price_gap = self.Param("PriceGap", 10) \
.SetDisplay("Price Gap", "Price offset in steps", "Parameters")
self._trade_interval_days = self.Param("TradeIntervalDays", 365) \
.SetDisplay("Trade Interval Days", "Minimum number of calendar days between setups", "Parameters")
self._buy_trade = self.Param("BuyTrade", True) \
.SetDisplay("Buy Trade", "Enable buys", "Parameters")
self._sell_trade = self.Param("SellTrade", True) \
.SetDisplay("Sell Trade", "Enable sells", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Working timeframe", "Parameters")
self._last_trade_day = DateTime.MinValue
self._tick_size = 0.0
self._setup_consumed = False
self._exit_submitted = False
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
@property
def StartHour(self):
return self._start_hour.Value
@StartHour.setter
def StartHour(self, value):
self._start_hour.Value = value
@property
def StartMinute(self):
return self._start_minute.Value
@StartMinute.setter
def StartMinute(self, value):
self._start_minute.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def PriceGap(self):
return self._price_gap.Value
@PriceGap.setter
def PriceGap(self, value):
self._price_gap.Value = value
@property
def TradeIntervalDays(self):
return self._trade_interval_days.Value
@TradeIntervalDays.setter
def TradeIntervalDays(self, value):
self._trade_interval_days.Value = value
@property
def BuyTrade(self):
return self._buy_trade.Value
@BuyTrade.setter
def BuyTrade(self, value):
self._buy_trade.Value = value
@property
def SellTrade(self):
return self._sell_trade.Value
@SellTrade.setter
def SellTrade(self, value):
self._sell_trade.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(news_hour_trade_strategy, self).OnStarted2(time)
ps = self.Security.PriceStep
self._tick_size = float(ps) if ps is not None else 1.0
self.SubscribeCandles(self.CandleType) \
.Bind(self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
date = candle.OpenTime.Date
sl = self.StopLoss
tp = self.TakeProfit
ts = self._tick_size
enough_days = self._last_trade_day == DateTime.MinValue or (date - self._last_trade_day).TotalDays >= self.TradeIntervalDays
if not self._setup_consumed and enough_days and candle.OpenTime.Hour == self.StartHour and candle.OpenTime.Minute >= self.StartMinute and self.Position == 0:
self._last_trade_day = date
self._setup_consumed = True
self._exit_submitted = False
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
long_bias = close >= open_p
open_long = self.BuyTrade and (not self.SellTrade or long_bias)
open_short = self.SellTrade and (not self.BuyTrade or not long_bias)
if open_long:
self.BuyMarket()
self._entry_price = close
self._stop_price = self._entry_price - sl * ts
self._take_price = self._entry_price + tp * ts
elif open_short:
self.SellMarket()
self._entry_price = close
self._stop_price = self._entry_price + sl * ts
self._take_price = self._entry_price - tp * ts
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0:
if not self._exit_submitted and (low <= self._stop_price or high >= self._take_price):
self.SellMarket()
self._exit_submitted = True
elif self.Position < 0:
if not self._exit_submitted and (high >= self._stop_price or low <= self._take_price):
self.BuyMarket()
self._exit_submitted = True
def OnReseted(self):
super(news_hour_trade_strategy, self).OnReseted()
self._last_trade_day = DateTime.MinValue
self._tick_size = 0.0
self._setup_consumed = False
self._exit_submitted = False
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
def CreateClone(self):
return news_hour_trade_strategy()