Открыть на GitHub

Стратегия Gann Line

Эта стратегия переносит логику советника MetaTrader 4 «Gann Line» (исходник 24877) на высокоуровневый API StockSharp. Сохранены те же фильтры тренда, импульса и долгосрочного MACD, а все блоки управления рисками переведены в шаги цены, что делает расчёты независимыми от брокера.

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

  1. Тренд (основной таймфрейм)
    • К типичной цене свечи (high + low + close) / 3 применяются две линейно-взвешенные скользящие средние.
    • Для покупок быстрая LWMA должна закрываться выше медленной, для продаж — ниже.
  2. Импульс (старший таймфрейм)
    • Осциллятор Momentum настраиваемого таймфрейма измеряет отклонение от уровня 100.
    • Минимум одно из трёх последних завершённых значений должно превысить заданный порог, иначе вход запрещён.
  3. Медленный MACD (очень старший таймфрейм)
    • MACD с длинными параметрами (по умолчанию месячные свечи) подтверждает направление: основная линия выше сигнальной для покупок и ниже для продаж.
  4. Управление позицией
    • Фиксированные стоп и тейк переводятся из шагов цены в абсолютные уровни при открытии сделки.
    • Опциональный перевод в безубыток переносит стоп к цене входа плюс заданный отступ после прохождения нужного профита.
    • Опциональный трейлинг подтягивает стоп за экстремумом цены, когда прибыль достигла заданного числа шагов.

Управление рисками

  • Все расстояния вводятся в шагах цены и автоматически переводятся в котировки через PriceStep инструмента.
  • Используется свойство Volume базового класса. Если оно равно нулю, выставляется 1 лот/контракт.
  • Одновременно управляется только одна суммарная позиция: обратный сигнал закрывает текущую сделку и затем открывает новую.

Отличия от версии MQL4

  • В оригинале использовалась вручную нарисованная линия Ганна. StockSharp не предоставляет событий по графическим объектам, поэтому её роль выполняет фильтр на основе пересечения LWMA.
  • Денежный трейлинг, частичные закрытия и учёт просадки по счёту упрощены до детерминированных расчётов в шагах цены.
  • Уведомления (алерты, почта, push) не реализованы, так как стратегии StockSharp пишут в журнал.

Параметры

Имя Описание
Fast LWMA Период быстрой LWMA для фильтра тренда.
Slow LWMA Период медленной LWMA.
Momentum Period Длина расчёта осциллятора Momentum на старшем таймфрейме.
Momentum Threshold Минимальное отклонение от 100, необходимое для разрешения входа.
MACD Fast / Slow / Signal Периоды EMA в фильтре MACD.
Take Profit (steps) Расстояние до тейк-профита в шагах цены.
Stop Loss (steps) Расстояние до стоп-лосса в шагах цены.
Use Trailing, Trail Activation, Trail Distance Флаги и параметры трейлинга.
Use BreakEven, BreakEven Activation, BreakEven Offset Флаги и параметры перевода в безубыток.
Primary Timeframe Таймфрейм для расчёта LWMA.
Momentum Timeframe Таймфрейм для осциллятора Momentum.
MACD Timeframe Таймфрейм для фильтра MACD.

Рекомендации по запуску

  1. Выберите инструмент и задайте Primary Timeframe. Остальные таймфреймы по умолчанию — 1 час (Momentum) и 30 дней (MACD), при необходимости их можно изменить.
  2. Настройте Volume и шаговые параметры риска под спецификации контракта.
  3. Запускайте стратегию в Designer или из кода и контролируйте журнал: в нём видны срабатывания фильтров, перенос стопа в безубыток и трейлинг.
  4. Оптимизируйте пороги Momentum и MACD для адаптации к другим рынкам и таймфреймам.

Возможные улучшения

  • Добавить глобальный стоп по equity, аналогичный оригинальному советнику.
  • Реализовать работу с пользовательской линией тренда, когда StockSharp предоставит события по объектам графика.
  • Ввести частичное закрытие позиции, чтобы полностью повторить поведение MQL4.
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;

public class GannLineStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public GannLineStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}