La estrategia reproduce el comportamiento del MetaTrader asesor experto Stat_Euclidean_Metric.mq4. Supervisa las reversiones de MACD en un solo instrumento y período de tiempo. Cuando la línea MACD forma un punto de inflexión local, la estrategia abre una posición inmediatamente (modo de entrenamiento) o valida la configuración con un clasificador de k vecinos más cercanos (k-NN) que compara la estructura actual del mercado con vectores de características históricas almacenados en archivos binarios.
Lógica comercial
Suscríbase al tipo de vela configurado y calcule el indicador MACD sobre el precio típico ((Máximo + Mínimo + Cierre) / 3).
Detecte una reversión bajista cuando los últimos tres valores completos de MACD cumplan con MACD[2] <= MACD[1] y MACD[1] > MACD[0].
Detectar una reversión alcista cuando MACD[2] >= MACD[1] y MACD[1] < MACD[0].
Dependiendo del modo seleccionado:
Modo de entrenamiento (TrainingMode = true): abra una orden de mercado en la dirección de la reversión después de cerrar opcionalmente la posición actual. Esto imita el comportamiento original de EA cuando recopila nuevas muestras.
Modo clasificador (TrainingMode = false): calcula cinco proporciones de promedios móviles simples del precio típico y evalúa la probabilidad de éxito con un modelo k-NN. Realice pedidos solo si la probabilidad cruza los umbrales configurados.
Aplique el módulo integrado StartProtection para adjuntar niveles de stop-loss y take-profit en pasos del instrumento.
Vector de características para clasificación
El modelo k-NN utiliza los siguientes ratios calculados sobre la vela recién cerrada:
SMA(89) / SMA(144)
SMA(144) / SMA(233)
SMA(21) / SMA(89)
SMA(55) / SMA(89)
SMA(2) / SMA(55)
Cada muestra almacenada en los archivos del conjunto de datos contiene seis valores double: las cinco proporciones anteriores y una etiqueta (0 para un resultado desfavorable, 1 para una operación exitosa). Durante la evaluación, la estrategia selecciona las muestras NeighborCount más cercanas, promedia sus etiquetas e interpreta el resultado como la probabilidad de éxito.
Archivos de conjunto de datos
BuyDatasetPath: ruta al archivo binario con vectores recopilados después de operaciones alcistas.
SellDatasetPath: ruta al archivo binario con vectores recopilados después de operaciones bajistas.
Si una ruta es relativa, se resolverá con Environment.CurrentDirectory. Los archivos faltantes se informan en el registro y se tratan como un conjunto de datos vacío. Esta implementación lee conjuntos de datos pero no actualiza ni agrega nuevas muestras automáticamente; La exportación de nuevos vectores debe manejarse externamente cuando se ejecuta en modo de entrenamiento.
Parámetros
TrainingMode: cambie entre el comercio MACD puro y el comercio asistido por clasificador.
BuyThreshold / SellThreshold: probabilidad mínima devuelta por el clasificador para abrir operaciones en la dirección principal.
AllowInverseEntries: permite operaciones contrarias cuando la probabilidad es extremadamente baja.
InverseBuyThreshold / InverseSellThreshold: probabilidad máxima que aún activa una operación en dirección opuesta.
FastLength / SlowLength / SignalLength – MACD EMA longitudes.
TakeProfitPoints / StopLossPoints: niveles de protección expresados en pasos del instrumento.
ClosePositionsOnSignal: cierra la posición neta actual antes de enviar una nueva orden.
BuyDatasetPath / SellDatasetPath: archivos binarios que almacenan vectores históricos.
NeighborCount: número de vecinos utilizados en la votación k-NN.
CandleType – serie de velas utilizadas para todos los indicadores.
Recomendaciones de uso
Proporcione rutas absolutas o relativas al directorio de trabajo a los archivos del conjunto de datos antes de habilitar el modo clasificador.
Recopile muestras de alta calidad ejecutando la estrategia en modo de entrenamiento con datos históricos y exportando vectores manualmente.
Optimice los umbrales y el recuento de vecinos para adaptar el clasificador a nuevos mercados o instrumentos.
Mantenga el parámetro Volume del instrumento alineado con el modelo de riesgo porque la estrategia siempre abre Volume + |Position| lotes para revertir la posición neta cuando sea necesario.
Diferencias con la versión MQL4
Los conjuntos de datos del clasificador solo se leen; el EA original escribe nuevas muestras durante la desinicialización. Aquí el usuario debe actualizar los archivos manualmente después de analizar el historial comercial.
Todas las órdenes de protección se adjuntan mediante parámetros StockSharp StartProtection en lugar de parámetros manuales OrderSend.
El cierre de órdenes en el modo clasificador siempre sale de la posición completa cuando ClosePositionsOnSignal está habilitado, mientras que el script MQL4 cierra solo las órdenes rentables antes de recibir nuevas señales.
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()