Стратегия переносит эксперта GANN_FAN с платформы MetaTrader на StockSharp и использует высокоуровневый API. Тренд определяется парой линейных взвешенных скользящих средних, подтверждается классическим осциллятором Momentum и фильтруется MACD. Направление открытия сделок дополнительно проверяется по реконструированным линиям гановского веера, которые строятся на основе подтверждённых фракталов. Управление позицией повторяет оригинал: ступенчатое наращивание объёма, фиксированные стопы, трейлинг и перенос в безубыток.
Логика торговли
Трендовый фильтр – две LWMA по типичной цене (High+Low+Close)/3. Для покупок быстрая LWMA должна находиться выше медленной, для продаж – ниже.
Подтверждение моментума – рассчитывается показатель 100 * Close / Close(n). Величина отклонения от 100 анализируется на трёх последних закрытых свечах; если хотя бы одно значение превышает порог, импульс считается достаточным.
Фильтр MACD – настраиваемый MACD (fast/slow/signal) должен поддерживать текущую тенденцию. Для входа в лонг требуется MACD > Signal, для шорта – MACD < Signal.
Ориентация веера Gann – последние подтверждённые фракталы формируют восходящую и нисходящую линии веера. Для лонга наклон линии, построенной по двум последним down-фракталам, должен быть положительным; для шорта наклон линии по up-фракталам должен быть отрицательным.
Наращивание позиции – при повторном сигнале стратегия добавляет объём в сторону текущей позиции, пока не достигнут лимит сделок. Каждый новый вход умножает базовый объём на показатель Lot Exponent, что имитирует мартингейл оригинального советника.
Управление риском
Стоп-лосс и тейк-профит – задаются в шагах цены инструмента; стратегия автоматически переводит их в реальные значения, используя Security.PriceStep.
Перенос в безубыток – после достижения заданной прибыли стоп переносится к цене входа с указанным отступом.
Трейлинг-стоп – активируется после срабатывания порога. Может использовать фиксированную дистанцию от закрытия или минимум/максимум последних свечей с дополнительной подушкой.
Принудительное закрытие – параметр Force Exit закрывает любые позиции на следующей завершённой свече.
Параметры
Параметр
Описание
Volume
Базовый объём первой сделки.
Fast LWMA / Slow LWMA
Периоды быстрых и медленных LWMA для фильтра тренда.
Momentum Period / Threshold
Длина расчёта Momentum и минимальное отклонение от 100.
MACD Fast / Slow / Signal
Периоды EMA в фильтре MACD.
Fractal History
Количество хранимых подтверждённых фракталов для построения веера.
Max Trades
Максимальное число добавлений в одну сторону.
Lot Exponent
Множитель, увеличивающий объём каждой последующей сделки.
Stop Loss / Take Profit
Дистанции защитных стопов в шагах цены.
Enable Trailing
Включение трейлинг-стопа.
Trail Trigger / Distance / Padding
Порог активации, расстояние трейлинга и дополнительная подушка (в шагах цены).
Use Candle Trail
Использовать ли экстремумы свечей для трейлинга.
Trailing Candles
Количество последних свечей для расчёта свечного трейлинга.
Enable Break-even
Активирует перенос стопа в безубыток.
Break-even Trigger / Offset
Порог прибыли и сдвиг стопа при переносе.
Use Gann Filter
Требовать подтверждения направлением веера.
Force Exit
Принудительно закрыть позицию на следующем баре.
Candle Type
Тип свечей, по которым ведутся расчёты и генерируются сигналы.
Особенности реализации
Все расчёты выполняются только на закрытых свечах, полученных через SubscribeCandles, что соответствует рекомендациям по использованию высокоуровневого API StockSharp.
Если коннектор не предоставляет PriceStep, защитные механизмы (стопы, трейлинг, безубыток) остаются неактивными до появления корректного значения шага цены.
Для лонгов и шортов ведутся отдельные состояния: цена входа, уровни трейлинга и безубытка автоматически сбрасываются при смене направления позиции.
Графические объекты и оповещения из оригинального MQL-советника заменены логикой фрактального построения веера внутри StockSharp, что делает стратегию полностью автономной.
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 GannFanStrategy : 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 GannFanStrategy()
{
_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_fan_strategy(Strategy):
def __init__(self):
super(gann_fan_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_fan_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_fan_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_fan_strategy()