Стратегия News Hour Trade
News Hour Trade размещает отложенные заявки на покупку и продажу перед выходом важной новости. Заявки выставляются на фиксированном расстоянии от текущей цены и сопровождаются стоп‑лоссом, тейк‑профитом и при необходимости трейлинг‑стопом.
Идея
- В заданный час и минуту стратегия готовится к выходу новости.
- Выставляются buy stop и sell stop ордера на
PriceGapшагов выше и ниже текущей цены. - После срабатывания одной заявки противоположная отменяется.
- Открытая позиция защищается фиксированными уровнями стоп‑лосса и тейк‑профита. При включённом
TrailStopстоп двигается вслед за ценой. - За день выполняется только одна торговая попытка.
Параметры
- StartHour / StartMinute – время начала работы.
- DelaySeconds – пауза перед размещением ордеров (информационный параметр).
- Volume – объём заявки в лотах.
- StopLoss – расстояние до стоп‑лосса в шагах цены.
- TakeProfit – расстояние до тейк‑профита.
- PriceGap – отступ от текущей цены для отложенных заявок.
- Expiration – время жизни ордеров в секундах (0 – без ограничения).
- TrailStop – включить трейлинг‑стоп.
- TrailingStop – расстояние для трейлинг‑стопа.
- TrailingGap – минимальный зазор перед переносом стопа.
- BuyTrade / SellTrade – разрешение на покупки или продажи.
- CandleType – таймфрейм, используемый для отслеживания времени.
Примечания
Стратегия рассчитана на таймфрейм M5 и подходит для инструментов с узким спредом. Используйте осторожно во время публикации важных новостей.
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()