Открыть на GitHub

Стратегия Franks 4 Hour Limit Orders

Обзор

Franks 4 Hour Limit Orders Strategy переносит советник MetaTrader 4 из файла MQL/7684/Franks_4hour_limit_orders.mq4 на высокоуровневый API StockSharp. Оригинальный робот развивает идеи «Тройного экрана» Александра Элдера: на четырёхчасовом графике анализируется гистограмма MACD (OsMA) и Force Index, после чего выставляются встречные лимитные заявки возле экстремумов предыдущей свечи. Реализация для StockSharp сохраняет эту логику, строго соблюдает требования репозитория (табуляция, высокоуровневый API, отсутствие самописных коллекций) и снабжена подробными комментариями на английском языке прямо в коде.

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

  1. Источник данных. Стратегия подписывается на свечи выбранного таймфрейма (по умолчанию H4) и обрабатывает только завершённые бары, чтобы полностью повторить поведение MT4-советника.
  2. Индикаторы. Используются два готовых индикатора:
    • MovingAverageConvergenceDivergenceSignal(12, 26, 9) предоставляет линию MACD и сигнальную линию; их разница даёт значение гистограммы OsMA.
    • ForceIndex(24) рассчитывает силу предыдущей свечи. В расчётах участвуют только финальные значения.
  3. Контекст из истории. Для определения направления требуется минимум две завершённые свечи. Внутренние переменные хранят предыдущие значения OsMA, Force Index и экстремумы свечи High[1]/Low[1].
  4. Условия для продажи. Если гистограмма OsMA растёт (OsMA[1] > OsMA[2]), а Force Index предыдущей свечи отрицателен, формируется встречный sell limit:
    • Базовая цена — максимум предыдущей свечи плюс один «пункт».
    • Дополнительно контролируется минимальный отступ 16 пунктов (настраивается) от текущего бид-курса: фактическая цена равна максимуму между базовой ценой и Bid + buffer.
    • Стоп-лосс и тейк-профит рассчитываются в пунктах (по умолчанию 35 и 150) и выравниваются по шагу цены инструмента.
  5. Условия для покупки. Если гистограмма OsMA снижается (OsMA[1] < OsMA[2]) и Force Index положителен, выставляется buy limit ниже рынка:
    • Базовая цена — минимум предыдущей свечи минус один пункт.
    • Выполняется то же требование по отступу: выбирается минимум между базовой ценой и Ask - buffer.
  6. Сопровождение отложенных заявок. При смене направления OsMA неисполнённая заявка снимается. Когда одна сторона срабатывает, противоположная заявка удаляется, чтобы исключить двойную экспозицию.
  7. Управление позицией. После исполнения сохраняется цена входа, активируются заранее посчитанные уровни стоп-лосса и тейк-профита. Дополнительно включён трейлинг-стоп на основе пунктов (по умолчанию 30 пунктов), который сдвигает защитный стоп только в сторону прибыли при достаточном движении цены.
  8. Выходы. На каждом завершённом баре проверяется, достигла ли цена уровней стопа или профита: для лонга позиция закрывается при касании стопа минимумом свечи либо при достижении цели максимумом. Для шорта условия зеркальные.

Параметры

Параметр Значение по умолчанию Описание
OrderVolume 1 Постоянный объём лимитных заявок.
StopLossPips 35 Расстояние от точки входа до стоп-лосса в пунктах.
TakeProfitPips 150 Расстояние от точки входа до тейк-профита в пунктах.
TrailingStopPips 30 Размер трейлинг-стопа в пунктах.
EntryBufferPips 16 Минимальный зазор между рынком и лимитной заявкой.
PipSize 0.0001 Размер одного пункта (можно менять под экзотические инструменты).
CandleType H4 Таймфрейм свечей, используемый в расчётах.

Состав папки

  • CS/Franks4HourLimitOrdersStrategy.cs — реализация стратегии с подробными комментариями на английском языке.
  • README.md — описание стратегии на английском языке.
  • README_ru.md — эта русскоязычная документация.
  • README_zh.md — китайская версия описания.

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

  • Применяется исключительно высокоуровневый API StockSharp: подписка на свечи, привязка индикаторов и готовые методы отправки заявок.
  • Все ценовые уровни округляются к шагу цены инструмента, чтобы исключить ошибки площадки.
  • Для хранения состояния используются только необходимые переменные, что соответствует запрету на самодельные коллекции.
  • Управление стопами, тейк-профитом и трейлингом выполняется внутри обработчика свечей и повторяет механику оригинального MT4-советника.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Franks 4H strategy - EMA crossover with momentum filter.
/// Buys on bullish EMA crossover with positive momentum.
/// Sells on bearish EMA crossover with negative momentum.
/// </summary>
public class Franks4HourLimitOrdersStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public Franks4HourLimitOrdersStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12)
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 26)
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");

		_momentumPeriod = Param(nameof(MomentumPeriod), 10)
			.SetDisplay("Momentum", "Momentum period", "Indicators");

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

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); _prevFast = 0m; _prevSlow = 0m; _hasPrev = false; }

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

		_hasPrev = false;

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var momentum = new Momentum { Length = MomentumPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, momentum, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal momentum)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_hasPrev)
		{
			_prevFast = fast;
			_prevSlow = slow;
			_hasPrev = true;
			return;
		}

		var crossUp = _prevFast <= _prevSlow && fast > slow;
		var crossDown = _prevFast >= _prevSlow && fast < slow;

		if (crossUp && momentum > 0 && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		else if (crossDown && momentum < 0 && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevFast = fast;
		_prevSlow = slow;
	}
}