Открыть на GitHub

Стратегия SurefireThing

Обзор

SurefireThing — это реализация на StockSharp высокоуровневого API экспертной системы MetaTrader 4 Surefirething. Стратегия работает только с закрытыми свечами, вычисляет уровни отложенных заявок по диапазону предыдущей сессии и обнуляет позицию при смене торгового дня. Основная идея заключается в постановке симметричных лимитных заявок вокруг цены закрытия предыдущего дня, чтобы воспользоваться возможным возвратом к среднему.

Логика торговли

  • При обнаружении нового торгового дня стратегия пытается закрыть открытую позицию и снимает активные отложенные заявки.
  • Для последней завершённой свечи предыдущего дня рассчитывается диапазон (High - Low) и умножается на RangeMultiplier (по умолчанию 1.1 — как в оригинальном советнике).
  • Половина скорректированного диапазона прибавляется к цене закрытия для определения уровня sell limit. Та же величина вычитается из цены закрытия для постановки buy limit.
  • Параметры StopLoss и TakeProfit задаются в шагах цены. Если у инструмента определён Security.Step, значения автоматически переводятся в абсолютные расстояния и обрабатываются через StartProtection, чтобы при исполнении позиций автоматически выставлялись защитные ордера.
  • Заявки выставляются один раз в сутки. При исполнении выход из позиции происходит за счёт защитных ордеров, иначе лимитные заявки остаются активными до следующей дневной перезагрузки.

Параметры

Параметр Описание Значение по умолчанию
OrderVolume Объём каждой отложенной заявки. 0.1
TakeProfitPoints Дистанция тейк-профита в шагах цены. При наличии Security.Step переводится в абсолютное значение. 10
StopLossPoints Дистанция стоп-лосса в шагах цены. Переводится аналогично тейк-профиту. 15
RangeMultiplier Коэффициент, которым масштабируется диапазон предыдущей свечи. 1.1
CandleType Основной таймфрейм, используемый стратегией. По умолчанию — минутные свечи, но может быть изменён под исходный график. TimeSpan.FromMinutes(1)

Особенности реализации

  • Высокоуровневый API: подписка на свечи осуществляется через SubscribeCandles(CandleType), обработчик ProcessCandle работает только с завершёнными свечами.
  • Суточный сброс: функция CloseForNewDay следит за сменой календарного дня по времени открытия свечи, закрывает позицию и отменяет заявки.
  • Защитные ордера: ConfigureProtection преобразует точечные параметры риска в объекты Unit и активирует StartProtection, чтобы стопы и тейки автоматически переустанавливались после сделок.
  • Управление жизненным циклом ордеров: ссылки на лимитные заявки хранятся в _buyLimitOrder и _sellLimitOrder, очищаются через CancelPendingOrder и OnOrderChanged при завершении или отмене.
  • Нормализация цен: перед отправкой ордеров цены округляются до шага инструмента функцией Security.ShrinkPrice.

Рекомендации по использованию

  • Подберите CandleType под таймфрейм, который использовался в оригинальном советнике, чтобы расчёты выполнялись по тем же свечам.
  • При изменении волатильности инструмента корректируйте RangeMultiplier, чтобы расстояние до лимитных заявок оставалось адекватным.
  • Если брокер ограничивает минимальные расстояния до стопов, убедитесь, что TakeProfitPoints и StopLossPoints после пересчёта соответствуют правилам площадки.
  • Стратегия предполагает непрерывные внутридневные данные. При гэпах (выходные, праздники) следующая доступная свеча всё равно инициирует сброс и постановку новых ордеров на основе последнего наблюдаемого бара.
using System;
using System.Collections.Generic;

using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Daily range breakout strategy.
/// Calculates buy/sell levels from previous day's range.
/// Buys when price drops below the lower level, sells when above the upper level.
/// Closes position at end of day.
/// </summary>
public class SurefireThingStrategy : Strategy
{
	private readonly StrategyParam<decimal> _rangeMultiplier;
	private readonly StrategyParam<DataType> _candleType;

	private DateTime? _currentDay;
	private decimal _buyLevel;
	private decimal _sellLevel;
	private bool _levelsReady;
	private decimal _prevDayClose;
	private decimal _prevDayHigh;
	private decimal _prevDayLow;
	private decimal _dayHigh;
	private decimal _dayLow;
	private decimal _dayClose;
	private bool _hasPrevDay;
	private bool _tradedToday;

	public decimal RangeMultiplier
	{
		get => _rangeMultiplier.Value;
		set => _rangeMultiplier.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public SurefireThingStrategy()
	{
		_rangeMultiplier = Param(nameof(RangeMultiplier), 0.5m)
			.SetDisplay("Range Mult", "Multiplier for range-based levels", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle series", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_currentDay = null;
		_buyLevel = 0m;
		_sellLevel = 0m;
		_levelsReady = false;
		_prevDayClose = 0m;
		_prevDayHigh = 0m;
		_prevDayLow = 0m;
		_dayHigh = 0m;
		_dayLow = 0m;
		_dayClose = 0m;
		_hasPrevDay = false;
		_tradedToday = false;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_currentDay = null;
		_levelsReady = false;
		_hasPrevDay = false;
		_tradedToday = false;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var day = candle.OpenTime.Date;

		// New day detected
		if (_currentDay == null || day > _currentDay.Value)
		{
			// Close position at end of previous day
			if (Position > 0)
				SellMarket();
			else if (Position < 0)
				BuyMarket();

			// Save previous day stats
			if (_currentDay != null)
			{
				_prevDayClose = _dayClose;
				_prevDayHigh = _dayHigh;
				_prevDayLow = _dayLow;
				_hasPrevDay = true;
			}

			// Calculate new levels
			if (_hasPrevDay)
			{
				var range = _prevDayHigh - _prevDayLow;
				if (range > 0)
				{
					var halfRange = range * RangeMultiplier;
					_buyLevel = _prevDayClose - halfRange;
					_sellLevel = _prevDayClose + halfRange;
					_levelsReady = true;
				}
			}

			_currentDay = day;
			_dayHigh = candle.HighPrice;
			_dayLow = candle.LowPrice;
			_dayClose = candle.ClosePrice;
			_tradedToday = false;
		}
		else
		{
			if (candle.HighPrice > _dayHigh) _dayHigh = candle.HighPrice;
			if (candle.LowPrice < _dayLow) _dayLow = candle.LowPrice;
			_dayClose = candle.ClosePrice;
		}

		if (!_levelsReady)
			return;

		var price = candle.ClosePrice;

		// Only one trade per day per direction
		if (!_tradedToday && Position == 0)
		{
			if (price <= _buyLevel)
			{
				BuyMarket();
				_tradedToday = true;
			}
			else if (price >= _sellLevel)
			{
				SellMarket();
				_tradedToday = true;
			}
		}
	}
}