Стратегия переносит логику советника Exp_JFatlCandle_MMRec.mq5 в инфраструктуру StockSharp.
Она отслеживает смену цветов свечей, построенных с помощью фильтра JFatl, и дополняет их блоком управления
рисками MMRec, который уменьшает объём позиции после серии убыточных сделок.
Торговая идея
Формируются синтетические свечи: исходные OHLC значения сглаживаются ядром Fast Adaptive Trend Line (FATL).
Используются оригинальные 39 коэффициентов фильтра; затем применяется экспоненциальное сглаживание,
приближающее Jurik Moving Average из версии MT5.
Цвета свечей трактуются по величине разницы между фильтрованными open и close:
значение 2 — бычья свеча (close выше open);
значение 0 — медвежья свеча (close ниже open);
значение 1 — нейтральное тело.
Бычий цвет на свече SignalBar + 1 бара назад приводит к закрытию коротких позиций и подготовке к новому
входу в лонг, когда свеча SignalBar баров назад перестает быть бычьей.
Аналогично, медвежий цвет закрывает лонги и разрешает открытие короткой позиции, если более свежая свеча уже не медвежья.
Объём сделок вычисляется через MMRecounter: если из последних TotalTrigger сделок нужного направления не менее
LossTrigger завершились убытком, используется уменьшенный лот, иначе — нормальный.
Параметры
Параметр
Описание
CandleType
Таймфрейм свечей, поступающих на вход фильтра FATL (по умолчанию 12 часов).
Длина экспоненциального сглаживания после ядра FATL.
NormalVolume
Базовый объём позиции при стабильных результатах.
ReducedVolume
Уменьшенный объём, активируется при серии убыточных сделок.
BuyTotalTrigger / SellTotalTrigger
Количество последних сделок каждого направления, которые анализирует MMRecounter.
BuyLossTrigger / SellLossTrigger
Минимум убыточных сделок в окне анализа, после которого включается ReducedVolume.
EnableBuyEntries / EnableSellEntries
Разрешение на открытие длинных/коротких позиций.
EnableBuyExits / EnableSellExits
Разрешение на закрытие позиций при появлении противоположного сигнала.
StopLossPoints
Необязательный стоп-лосс в шагах цены инструмента. 0 отключает защиту.
TakeProfitPoints
Тейк-профит в шагах цены. 0 отключает цель.
Правила торговли
После накопления достаточной истории (не менее 39 свечей) рассчитываются фильтрованные OHLC и цвет свечи.
Пусть C1 — цвет свечи SignalBar + 1 баров назад, C0 — цвет свечи SignalBar баров назад
(при SignalBar = 0 используется текущая свеча как C0, предыдущая — как C1).
Если C1 == 2 (бычий цвет):
закрыть все короткие позиции, если EnableSellExits включён;
открыть длинную позицию рассчитанным объёмом, если EnableBuyEntries включён и C0 != 2.
Если C1 == 0 (медвежий цвет):
закрыть все длинные позиции, если активирован EnableBuyExits;
открыть короткую позицию, если EnableSellEntries включён и C0 != 0.
Позиции также закрываются при достижении уровней стоп-лосса или тейк-профита в пределах диапазона свечи.
Деньги-менеджмент
После каждой закрытой позиции стратегия сохраняет результат отдельно для лонгов и шортов.
Перед входом она анализирует до TotalTrigger прошлых сделок выбранного направления.
Если количество убыточных результатов достигает LossTrigger, новая позиция открывается уменьшенным объёмом,
в противном случае — стандартным.
Примечания
Расчёт стоп-лосса и тейк-профита ведётся в шагах цены (Security.PriceStep). При отсутствии данных используется шаг 1.
Пока фильтр FATL не накопит 39 значений, сигналы и сделки не формируются.
История сделок для MMRecounter ограничена 100 записями — старые результаты автоматически удаляются.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class JfatlCandleMmRecStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal? _prevFast, _prevSlow;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public JfatlCandleMmRecStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 7).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 21).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevSlow = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = null; _prevSlow = null;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fast); DrawIndicator(area, slow); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) { _prevFast = fast; _prevSlow = slow; return; }
if (_prevFast == null || _prevSlow == null) { _prevFast = fast; _prevSlow = slow; return; }
var prevAbove = _prevFast.Value > _prevSlow.Value;
var currAbove = fast > slow;
_prevFast = fast; _prevSlow = slow;
if (!prevAbove && currAbove && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
else if (prevAbove && !currAbove && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
}
}
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 jfatl_candle_mm_rec_strategy(Strategy):
def __init__(self):
super(jfatl_candle_mm_rec_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 7) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 21) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
def OnReseted(self):
super(jfatl_candle_mm_rec_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(jfatl_candle_mm_rec_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast = ExponentialMovingAverage()
fast.Length = self.FastPeriod
slow = ExponentialMovingAverage()
slow.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, slow, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fv
self._prev_slow = sv
return
prev_above = self._prev_fast > self._prev_slow
curr_above = fv > sv
self._prev_fast = fv
self._prev_slow = sv
if not prev_above and curr_above and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif prev_above and not curr_above and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return jfatl_candle_mm_rec_strategy()