Эта стратегия переносит логику советника MetaTrader 4 «Gann Line» (исходник 24877) на высокоуровневый API StockSharp. Сохранены те же фильтры тренда, импульса и долгосрочного MACD, а все блоки управления рисками переведены в шаги цены, что делает расчёты независимыми от брокера.
Логика торговли
Тренд (основной таймфрейм)
К типичной цене свечи (high + low + close) / 3 применяются две линейно-взвешенные скользящие средние.
Для покупок быстрая LWMA должна закрываться выше медленной, для продаж — ниже.
Импульс (старший таймфрейм)
Осциллятор Momentum настраиваемого таймфрейма измеряет отклонение от уровня 100.
Минимум одно из трёх последних завершённых значений должно превысить заданный порог, иначе вход запрещён.
Медленный MACD (очень старший таймфрейм)
MACD с длинными параметрами (по умолчанию месячные свечи) подтверждает направление: основная линия выше сигнальной для покупок и ниже для продаж.
Управление позицией
Фиксированные стоп и тейк переводятся из шагов цены в абсолютные уровни при открытии сделки.
Опциональный перевод в безубыток переносит стоп к цене входа плюс заданный отступ после прохождения нужного профита.
Опциональный трейлинг подтягивает стоп за экстремумом цены, когда прибыль достигла заданного числа шагов.
Управление рисками
Все расстояния вводятся в шагах цены и автоматически переводятся в котировки через 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.
Рекомендации по запуску
Выберите инструмент и задайте Primary Timeframe. Остальные таймфреймы по умолчанию — 1 час (Momentum) и 30 дней (MACD), при необходимости их можно изменить.
Настройте Volume и шаговые параметры риска под спецификации контракта.
Запускайте стратегию в Designer или из кода и контролируйте журнал: в нём видны срабатывания фильтров, перенос стопа в безубыток и трейлинг.
Оптимизируйте пороги 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class gann_line_strategy(Strategy):
def __init__(self):
super(gann_line_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(gann_line_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(gann_line_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return gann_line_strategy()