Стратегия повторяет логику советника MetaTrader Stat_Euclidean_Metric.mq4. Она отслеживает развороты линии MACD на выбранном инструменте и таймфрейме. В базовом режиме стратегия сразу открывает позицию по сигналу, а в основном режиме сравнивает текущие рыночные условия с историческими векторами признаков из бинарных файлов и принимает решение на основе алгоритма k ближайших соседей.
Торговая логика
Подписка на свечи заданного типа и расчёт MACD по типичной цене ((High + Low + Close) / 3).
Фиксация медвежьего разворота, когда для трёх последних завершённых свечей выполняется MACD[2] <= MACD[1] и MACD[1] > MACD[0].
Фиксация бычьего разворота, когда MACD[2] >= MACD[1] и MACD[1] < MACD[0].
Реакция на сигнал зависит от режима:
TrainingMode = true — открытие рыночной позиции по направлению разворота (при необходимости сначала закрывается текущая позиция).
TrainingMode = false — расчёт пяти отношений простых скользящих средних типичной цены, оценка вероятности успеха по k-NN и открытие сделок только при выполнении порогов.
Подключение встроенной защиты StartProtection, которая автоматически добавляет стоп-лосс и тейк-профит в шагах цены.
Признаки для классификатора
Вектор признаков формируется на только что закрытой свече и включает:
SMA(89) / SMA(144)
SMA(144) / SMA(233)
SMA(21) / SMA(89)
SMA(55) / SMA(89)
SMA(2) / SMA(55)
Каждая запись в файлах содержит шесть значений типа double: пять отношений и метку (0 — неудачная сделка, 1 — успешная). При вычислении стратегии выбирается NeighborCount ближайших записей, после чего среднее по меткам трактуется как вероятность успеха.
Файлы данных
BuyDatasetPath — путь к бинарному файлу с векторами после покупок.
SellDatasetPath — путь к бинарному файлу с векторами после продаж.
Относительные пути приводятся к Environment.CurrentDirectory. Отсутствие файлов фиксируется в журнале и трактуется как пустой набор. Текущая реализация только читает файлы и не добавляет новые записи автоматически; при работе в учебном режиме экспорт векторов необходимо выполнять вручную.
Параметры
TrainingMode — выбор между чистой логикой MACD и режимом с классификатором.
BuyThreshold / SellThreshold — минимальная вероятность для открытия позиции по сигналу.
AllowInverseEntries — разрешение на контртрендовые сделки при очень низкой вероятности.
InverseBuyThreshold / InverseSellThreshold — максимальная вероятность, при которой открывается противоположная сделка.
FastLength / SlowLength / SignalLength — периоды EMA в MACD.
TakeProfitPoints / StopLossPoints — расстояние до тейк-профита и стоп-лосса в шагах цены.
ClosePositionsOnSignal — закрывать ли текущую позицию перед новой заявкой.
BuyDatasetPath / SellDatasetPath — пути к бинарным файлам с обучающими данными.
NeighborCount — количество соседей в голосовании k-NN.
CandleType — тип свечей для всех расчётов.
Рекомендации по использованию
Перед запуском режима с классификатором укажите корректные пути к файлам данных (абсолютные или относительные).
Для пополнения базы данных запустите стратегию в учебном режиме на исторических данных и сохраните признаки вручную.
Оптимизируйте пороги и количество соседей, чтобы адаптировать модель к различным инструментам.
Контролируйте параметр Volume: при смене направления стратегия выставляет заявку объёмом Volume + |Position|, чтобы мгновенно перевернуться.
Отличия от версии MQL4
Данные классификатора только читаются. В оригинальном советнике новые записи добавлялись при завершении работы, здесь файлы нужно обновлять самостоятельно.
Защита позиции реализована через StartProtection, а не через параметры OrderSend.
В классификационном режиме при включённом ClosePositionsOnSignal стратегия полностью закрывает позицию перед новым сигналом. В MQL4-скрипте закрывались только прибыльные ордера.
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>
/// MACD reversal strategy with moving average ratio filter.
/// Enters on MACD histogram reversals filtered by MA trend alignment.
/// </summary>
public class StatEuclideanMetricStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _trendMaLength;
private readonly List<decimal> _macdHistory = new();
public StatEuclideanMetricStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis.", "General");
_fastLength = Param(nameof(FastLength), 12)
.SetDisplay("Fast Length", "Fast EMA period for MACD.", "Indicators");
_slowLength = Param(nameof(SlowLength), 26)
.SetDisplay("Slow Length", "Slow EMA period for MACD.", "Indicators");
_trendMaLength = Param(nameof(TrendMaLength), 50)
.SetDisplay("Trend MA Length", "Period for trend filter MA.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
public int TrendMaLength
{
get => _trendMaLength.Value;
set => _trendMaLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_macdHistory.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastEma = new ExponentialMovingAverage { Length = FastLength };
var slowEma = new ExponentialMovingAverage { Length = SlowLength };
var trendMa = new SimpleMovingAverage { Length = TrendMaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, trendMa, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, trendMa);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal trendValue)
{
if (candle.State != CandleStates.Finished)
return;
var macdLine = fastValue - slowValue;
_macdHistory.Add(macdLine);
if (_macdHistory.Count > 5)
_macdHistory.RemoveAt(0);
if (_macdHistory.Count < 3)
return;
var close = candle.ClosePrice;
var macd1 = _macdHistory[^1];
var macd2 = _macdHistory[^2];
var macd3 = _macdHistory[^3];
// MACD reversal patterns
var buyReversal = macd3 >= macd2 && macd2 < macd1; // V-shape bottom
var sellReversal = macd3 <= macd2 && macd2 > macd1; // inverted V top
// Exit conditions
if (Position > 0 && sellReversal)
{
SellMarket();
}
else if (Position < 0 && buyReversal)
{
BuyMarket();
}
// Entry conditions with trend filter
if (Position == 0)
{
if (buyReversal && close > trendValue)
{
BuyMarket();
}
else if (sellReversal && close < trendValue)
{
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, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class stat_euclidean_metric_strategy(Strategy):
"""MACD reversal with trend MA filter."""
def __init__(self):
super(stat_euclidean_metric_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 12).SetDisplay("Fast Length", "Fast EMA for MACD", "Indicators")
self._slow_length = self.Param("SlowLength", 26).SetDisplay("Slow Length", "Slow EMA for MACD", "Indicators")
self._trend_ma_length = self.Param("TrendMaLength", 50).SetDisplay("Trend MA Length", "Period for trend filter", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(stat_euclidean_metric_strategy, self).OnReseted()
self._macd_history = []
def OnStarted2(self, time):
super(stat_euclidean_metric_strategy, self).OnStarted2(time)
self._macd_history = []
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._fast_length.Value
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self._slow_length.Value
trend_ma = SimpleMovingAverage()
trend_ma.Length = self._trend_ma_length.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(fast_ema, slow_ema, trend_ma, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, trend_ma)
self.DrawOwnTrades(area)
def OnProcess(self, candle, fast_val, slow_val, trend_val):
if candle.State != CandleStates.Finished:
return
macd_line = fast_val - slow_val
self._macd_history.append(macd_line)
if len(self._macd_history) > 5:
self._macd_history.pop(0)
if len(self._macd_history) < 3:
return
close = float(candle.ClosePrice)
m1 = self._macd_history[-1]
m2 = self._macd_history[-2]
m3 = self._macd_history[-3]
buy_reversal = m3 >= m2 and m2 < m1
sell_reversal = m3 <= m2 and m2 > m1
# Exits
if self.Position > 0 and sell_reversal:
self.SellMarket()
elif self.Position < 0 and buy_reversal:
self.BuyMarket()
# Entries
if self.Position == 0:
if buy_reversal and close > trend_val:
self.BuyMarket()
elif sell_reversal and close < trend_val:
self.SellMarket()
def CreateClone(self):
return stat_euclidean_metric_strategy()