Открыть на GitHub

Стратегия News Pending Orders

Стратегия размещает пару отложенных стоп-заявок вокруг текущей цены и управляет ими по мере изменения рынка. Предназначена для торговли на новостях, когда ожидаются резкие движения.

Как работает

  • При отсутствии позиции стратегия выставляет:
    • Buy Stop по цене Ask + Step.
    • Sell Stop по цене Bid - Step.
  • Отложенные заявки переоцениваются каждые TimeModify секунд, если рынок сдвинулся минимум на StepTrail.
  • После исполнения одной заявки противоположная отменяется.
  • На основе цены входа создаются защитный стоп-лосс и опциональный тейк-профит.
  • Стоп-лосс может быть перенесён в безубыток при достижении заданной прибыли и далее сопровождается трейлингом.

Стратегия работает на данных Level1 и не использует индикаторы.

Параметры

Параметр По умолчанию Описание
Step 10 Расстояние в тиках для размещения отложенных стоп-заявок.
StopLoss 10 Начальный стоп-лосс в тиках.
TakeProfit 50 Тейк-профит в тиках (0 отключает).
TrailingStop 10 Дистанция трейлинг-стопа в тиках.
TrailingStart 0 Прибыль в тиках, после которой активируется трейлинг.
StepTrail 2 Минимальное изменение цены стопа (в тиках) для его переноса.
BreakEven false Перенос стопа в точку входа после достижения MinProfitBreakEven.
MinProfitBreakEven 0 Прибыль в тиках для переноса стопа в безубыток.
TimeModify 30 Интервал в секундах между попытками переоценки отложенных заявок.

Примечания

  • Управление заявками реализовано через высокоуровневый API StockSharp.
  • При закрытии позиции защитные заявки отменяются.
  • В репозитории представлена только версия на C#, реализация на 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;
	}
}