Открыть на GitHub

Стратегия Fly System Scalp

Общее описание

Fly System Scalp — это адаптация советника FlySystemEA на платформу StockSharp. Стратегия постоянно отслеживает лучшие цены Bid/Ask и выставляет симметричные стоп-ордера Buy Stop и Sell Stop на фиксированном расстоянии от рынка. Цель — быстро реагировать на импульсные движения, сохраняя строгий контроль над спредом, комиссиями и торговым расписанием.

Логика работы

  1. Подписка на Level-1. Стратегия получает обновления лучшего предложения и спроса по инструменту.
  2. Проверки перед действиями. На каждом тике выполняются проверки:
    • стратегия подключена и разрешено выставление заявок;
    • текущий момент входит в торговый интервал (если фильтр включён);
    • спред плюс значение CommissionInPips не превышает параметр MaxSpread.
  3. Размещение стоп-ордеров. При выполнении условий и отсутствии открытых позиций выставляются два ордера:
    • Buy Stop на уровне Ask + PendingDistance * pip с защитным стоп-лоссом и опциональным тейк-профитом;
    • Sell Stop на Bid - PendingDistance * pip с зеркальными параметрами. Если актуальная цена отличается от требуемой более чем на ModifyThreshold пунктов, ордера перерегистрируются.
  4. Сопровождение. При исполнении одного ордера противоположный немедленно отменяется. Если спред или время выходят за допустимые границы, все отложенные заявки удаляются и цикл ожидания начинается заново.
  5. Управление объёмом. При включённом AutoLotSize объём рассчитывается как доля (RiskFactor%) от капитала, делённая на возможный убыток по стоп-лоссу. При отсутствии данных брокера используется фиксированный ManualVolume.
  6. Защитные механизмы. Вызов StartProtection() подключает встроенную защиту StockSharp для контроля позиции.

Параметры

Название Описание Значение по умолчанию
PendingDistance Расстояние до рынка в пунктах для обоих стоп-ордеров. 4
StopLossDistance Размер стоп-лосса в пунктах. 0.4
TakeProfitDistance Размер тейк-профита в пунктах. 10
UseTakeProfit Включение/отключение тейк-профита. false
MaxSpread Максимально допустимый спред (0 — без ограничения). 1
CommissionInPips Комиссия в пунктах, добавляемая к спреду при проверке. 0
AutoLotSize Автоматический расчёт объёма. false
RiskFactor Процент капитала для расчёта риска. 10
ManualVolume Фиксированный объём без автолота. 0.1
UseTimeFilter Использовать ли торговый интервал. false
TradeStartTime Начало торгового интервала (включительно). 00:00:00
TradeStopTime Конец торгового интервала (исключая). 00:00:00
ModifyThreshold Минимальное отклонение в пунктах для перерегистрации ордера. 1

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

  • Для автоматического расчёта объёма требуются корректные свойства инструмента: Step, PriceStep, StepPrice, LotStep, MinVolume, MaxVolume. При отсутствии данных стратегия использует ManualVolume.
  • Размер пункта определяется по шагу цены и количеству знаков, что соответствует логике исходного советника (учёт трёх- и пятизнаковых котировок).
  • Если TradeStartTime и TradeStopTime совпадают при включённом фильтре, торговля разрешена круглосуточно. При TradeStartTime > TradeStopTime диапазон считается переходящим через полночь.
  • Проверка спреда суммирует фактический спред и CommissionInPips, что эквивалентно оригинальной реализации.
  • Визуальные объекты не создаются — отображение можно организовать отдельными инструментами.

Отличия от оригинала

  • Удалены таймер и графические элементы, присутствовавшие в MQL-версии; управление построено на событиях StockSharp.
  • Логика модификации ордеров упрощена: перерегистрация выполняется при превышении порога ModifyThreshold.
  • Автоопределение комиссий заменено на ручной параметр, но контроль спреда по-прежнему учитывает комиссию.
  • Вместо ручного контроля стоп-уровней используется стандартный метод StartProtection().

Советы по тестированию

Для корректного бэктеста необходимы исторические данные уровня 1 (bid/ask) либо реконструированный поток котировок из тиков. Только свечные данные не позволят корректно смоделировать исполнение стоп-ордеров.

using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Fly System Scalp strategy: EMA crossover + RSI confirmation.
/// Buys when close crosses above EMA and RSI confirms.
/// Sells when close crosses below EMA and RSI confirms.
/// </summary>
public class FlySystemScalpStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _rsiPeriod;

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

	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public FlySystemScalpStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");

		_emaPeriod = Param(nameof(EmaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA period", "Indicators");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period", "Indicators");
	}

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

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		decimal? prevClose = null;
		decimal? prevEma = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ema, rsi, (candle, emaVal, rsiVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				var close = candle.ClosePrice;

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

					if (crossUp && rsiVal < 55m && Position <= 0)
						BuyMarket();
					else if (crossDown && rsiVal > 45m && Position >= 0)
						SellMarket();
				}

				prevClose = close;
				prevEma = emaVal;
			})
			.Start();

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