Открыть на GitHub

Стратегия Sail System EA

Обзор

Sail System EA — это хеджирующий скальпер, который поддерживает симметричную длинную и короткую позицию, контролируя при этом требования брокера по максимальному спреду, минимальному стоп-уровню и торговым сессиям. Перенос на StockSharp полностью выполнен на высокоуровневом API Strategy: стратегия подписывается на котировки первого уровня, переоткрывает обе стороны хеджа и управляет виртуальными стопами/тейк-профитами без обращения к низкоуровневому коннектору.

Внутри поддерживаются два объекта PositionState (для лонга и шорта). Для каждого из них хранятся цена входа, оставшийся объём, виртуальные уровни защиты и сведения о отложенных ордерах — аналогично тому, как советник в MQL ввёл отдельные тикеты для рыночных и отложенных сделок.

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

  1. Сессионный фильтр. Торговля может ограничиваться заданным временным окном. При выходе из сессии стратегия, в зависимости от параметра ManageExistingOrders, либо оставляет позиции, либо снимает отложенные ордера, либо полностью закрывает позиции.
  2. Контроль спреда. Через SubscribeLevel1() поступают обновления bid/ask. Можно проверять текущий спред или его скользящую среднюю (до 100 значений) и сравнивать с MaxSpread с учётом комиссии. При превышении лимита система по желанию закрывает позиции и может увеличить дистанцию входа (MultiplierIncrease), ожидая нормализации рынка.
  3. Механизм входа. При разрешённой торговле стратегия либо сразу открывает лонг и шорт по рынку, либо поддерживает пару лимитных заявок (опция UsePendingOrders). Цена лимитного ордера вычисляется от текущих bid/ask плюс DistancePending (в пунктах) и дополнительный коэффициент безопасности.
  4. Виртуальная защита. После исполнения ордера выставляются виртуальные стоп-лосс и при необходимости тейк-профит на основе OrdersStopLoss / OrdersTakeProfit. Обновление происходит каждые DelayModifyOrders тиков, но только если улучшение превышает StepModifyOrders. Так реализуется постепенное подтягивание стопа, как в оригинальном MQL-скрипте.
  5. Выходы. Когда bid (для лонга) или ask (для шорта) достигает виртуального стопа или цели, стратегия отправляет противоположный рыночный ордер. В журнале фиксируется причина закрытия (стоп, тейк, окончание сессии или превышение спреда), чтобы отчёты совпадали с MQL-версией.
  6. Переоткрытие. Если отложенные заявки уехали от рынка дальше, чем PipsReplaceOrders * SafeMultiplier, они отменяются и создаются заново по актуальным ценам. Это заменяет таймерную перенастройку уровней в исходном коде.
  7. Расчёт объёма. Можно использовать фиксированный ManualLotSize или автоматически рассчитать объём по капиталу и параметру RiskFactor, как делал советник.

Параметры

Параметр Описание
OrderVolume / ManualLotSize Базовый объём сделки при отключённом авто-лоте.
AutoLotSize, RiskFactor Включают расчёт объёма от капитала.
UseVirtualLevels Управляет виртуальными стопами и тейками на стороне стратегии.
OrdersStopLoss, OrdersTakeProfit, PutTakeProfit Защитные дистанции в пунктах.
DelayModifyOrders, StepModifyOrders Частота и минимальный шаг подтягивания уровней.
PipsReplaceOrders, SafeMultiplier Повторно выставляет хедж при сильном отклонении цены.
UsePendingOrders, DistancePending Переключение между лимитными и рыночными входами.
UseTimeFilter, TimeStartTrade, TimeStopTrade, ManageExistingOrders Настройки торговой сессии.
MaxSpread, TypeOfSpreadUse, HighSpreadAction, MultiplierIncrease, CloseOnHighSpread Контроль спреда и реакция.
CommissionInPip, CountAvgSpread, TimesForAverage Управление учётом комиссии и усреднением спреда.
AcceptStopLevel, Slippage, OrdersId Минимальный стоп, допустимое проскальзывание и эквивалент magic number.

Все параметры оформлены через StrategyParam<T>, поэтому доступны в Designer и поддерживают оптимизацию.

Отличия от MQL-версии

  • В StockSharp используется неттинговый учёт позиции, поэтому после исполнения одной стороны противоположный отложенный ордер отменяется, чтобы не обнулить позицию. Поведение хеджа при этом сохраняется.
  • Флаг UseVirtualLevels оставляет управление стопами внутри стратегии. В MQL использовались графические объекты, здесь же обновления фиксируются в логе.
  • Усреднение спреда реализовано через инкрементную среднюю, без массивов, но с тем же ограничением по количеству значений.

Использование высокоуровневого API

  • Подписка SubscribeLevel1().Bind(ProcessLevel1) запускает всю логику на основе лучших bid/ask.
  • Ордера создаются методом RegisterOrder и рыночными помощниками (BuyMarket, SellMarket), что соответствует методическим рекомендациям.
  • StartProtection() вызывается один раз в OnStarted, активируя защитную инфраструктуру фреймворка.
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Sail System EA strategy: Momentum + SMA crossover trend.
/// Buys when close crosses above SMA and momentum confirms.
/// Sells when close crosses below SMA and momentum confirms.
/// </summary>
public class SailSystemEaStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _smaPeriod;
	private readonly StrategyParam<int> _momPeriod;

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

	public int SmaPeriod
	{
		get => _smaPeriod.Value;
		set => _smaPeriod.Value = value;
	}

	public int MomPeriod
	{
		get => _momPeriod.Value;
		set => _momPeriod.Value = value;
	}

	public SailSystemEaStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");

		_smaPeriod = Param(nameof(SmaPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("SMA Period", "SMA period", "Indicators");

		_momPeriod = Param(nameof(MomPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Momentum", "Momentum period", "Indicators");
	}

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

		var sma = new SimpleMovingAverage { Length = SmaPeriod };
		var mom = new Momentum { Length = MomPeriod };

		decimal? prevClose = null;
		decimal? prevSma = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(sma, mom, (candle, smaVal, momVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				var close = candle.ClosePrice;

				if (prevClose.HasValue && prevSma.HasValue)
				{
					var crossUp = prevClose.Value <= prevSma.Value && close > smaVal;
					var crossDown = prevClose.Value >= prevSma.Value && close < smaVal;

					if (crossUp && momVal > 100m && Position <= 0)
						BuyMarket();
					else if (crossDown && momVal < 100m && Position >= 0)
						SellMarket();
				}

				prevClose = close;
				prevSma = smaVal;
			})
			.Start();

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