Auf GitHub ansehen

Mnist-Musterklassifizierungsstrategie

Herkunft

Die Strategie ist ein StockSharp-Port des MetaTrader 5-Experten TestMnistOnnx.mq5 (MQL ID 47225). Das ursprüngliche Skript stellt eine interaktive Funktion bereit Canvas, auf dem der Benutzer Ziffern zeichnet, die durch ein gebündeltes MNIST ONNX-Modell klassifiziert werden. Die StockSharp-Version behält den Geist von Mustererkennung, sondern ersetzt die handgezeichnete Leinwand durch eine rollende Matrix aus fertigen Kerzen.

Konzept

  1. Ein rollierendes Fenster von LookbackPeriod abgeschlossenen Kerzen (Standard 28) wird als 28×28-Raster behandelt, ähnlich einem MNIST-Bild.
  2. Mehrere statistische Merkmale – Bereichskomprimierung, Trendstärke, Momentum, RSI-Abweichung und ATR-Normalisierung – werden kombiniert in einen synthetischen „Konfidenz“-Score um, der die vom MQL-Experten erstellte neuronale Netzwerkwahrscheinlichkeit nachahmt.
  3. Die resultierenden Features werden einer von zehn Musterklassen (09) zugeordnet. Jede Klasse repräsentiert ein Marktregime (Flat, Trend, Ausbruch, Pullback, Umkehr usw.).
  4. Wenn die erkannte Klasse mit dem vom Benutzer ausgewählten TargetClass übereinstimmt und die synthetische Konfidenz über ConfidenceThreshold liegt, Die Strategie eröffnet oder kehrt eine Position in die angegebene Richtung um. Positionen werden abgeflacht, wenn sich die Klasse ändert oder Das Vertrauen fällt unter die Schwelle.

Parameter

Parameter Standard Beschreibung
LookbackPeriod 28 Anzahl der fertigen Kerzen, die in das MNIST-ähnliche Raster umgewandelt werden.
TargetClass 1 Klassenindex (0–9), der Handelsaktionen auslösen soll.
ConfidenceThreshold 0,6 Minimale synthetische Wahrscheinlichkeit, die die Auftragserteilung ermöglicht.
Volume 1 Auftragsvolumen für neue Positionen.
CandleType Zeitrahmen von 5 Minuten Für Kerzenaktualisierungen abonnierter Datentyp.

Musterklassen

Klasse Bedeutung
0 Flache Konsolidierung oder Konsolidierung mit geringer Volatilität.
1 Anhaltender Aufwärtstrend.
2 Anhaltender Abwärtstrend.
3 Ausbruch nach oben mit starkem Follow-Through.
4 Ausbruch nach unten mit starkem Follow-Through.
5 Große Volatilitätsspanne ohne klare Tendenz.
6 Bullischer Pullback innerhalb eines Aufwärtstrends.
7 Abwärtstrend innerhalb eines Abwärtstrends.
8 Bullische Umkehr nach einem längeren Rückgang.
9 Abwärtstrend nach einem längeren Anstieg.

Handelsregeln

  • Handelt nur mit fertigen Kerzen, um mit dem ursprünglichen Experten synchron zu bleiben, der auf die fertigen Zeichnungen reagiert hat.
  • Verwendet Marktaufträge (BuyMarket, SellMarket) und flacht sie ab, bevor sie sich umkehrt, um das Einzelpositionsverhalten des zu imitieren Originalskript.
  • Die Konfidenzskalierung ist auf [0, 1] beschränkt. Durch Erhöhen von ConfidenceThreshold werden schwächere Signale herausgefiltert.
  • Die Strategie verwaltet keine Schutzstopps; Das Risikomanagement wird voraussichtlich extern in StockSharp konfiguriert.

Nutzungstipps

  • Wählen Sie einen Kerzentyp, der den Marktrhythmus widerspiegelt, den Sie analysieren möchten. Kürzere Zeitrahmen reagieren schneller, sind aber lauter.
  • Optimieren Sie TargetClass und ConfidenceThreshold gemeinsam – einige Klassen sind von Natur aus seltener und erfordern möglicherweise niedrigere Schwellenwerte.
  • Der synthetische Musterklassifikator ist deterministisch; Es besteht keine Abhängigkeit von externen ONNX-Laufzeitbibliotheken.
  • Kombinieren Sie es mit den in StockSharp verfügbaren integrierten Risikoschutztools (z. B. StartProtection), um die Gefährdung zu kontrollieren.

Unterschiede zum Original

  • Interaktives Zeichnen und ONNX-Inferenz werden durch eine vollautomatische Kerzenanalyse ersetzt.
  • Das „Konfidenz“ ist eine deterministische Mischung von Indikatoren und keine Wahrscheinlichkeit eines neuronalen Netzwerks.
  • Es wird eine Handelslogik hinzugefügt, um die Mustererkennung in umsetzbare Aufträge umzuwandeln.
  • Die MNIST-Ressourcendatei ist in der StockSharp-Umgebung nicht erforderlich.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Port of the MetaTrader expert TestMnistOnnx.
/// Converts a rolling grid of candle closes into pattern classes and trades on a selected class.
/// The original mouse-drawn image is replaced with market data derived features.
/// </summary>
public class MnistPatternClassifierStrategy : Strategy
{
	private readonly StrategyParam<int> _lookbackPeriod;
	private readonly StrategyParam<int> _targetClass;
	private readonly StrategyParam<decimal> _confidenceThreshold;
	private readonly StrategyParam<DataType> _candleType;

	private RelativeStrengthIndex _rsi = null!;
	private AverageTrueRange _atr = null!;

	private readonly Queue<decimal> _closeWindow = new();
	private decimal _firstClose;
	private decimal _previousClose;

	private int _lastClass = -1;
	private decimal _lastConfidence;
	private int _cooldown;

	private enum PatternBiases
	{
		Neutral,
		Bullish,
		Bearish,
	}

	/// <summary>
	/// Number of finished candles that form the MNIST-like grid.
	/// </summary>
	public int LookbackPeriod
	{
		get => _lookbackPeriod.Value;
		set => _lookbackPeriod.Value = value;
	}

	/// <summary>
	/// Pattern class (0-9) that will trigger trading actions.
	/// </summary>
	public int TargetClass
	{
		get => _targetClass.Value;
		set => _targetClass.Value = value;
	}

	/// <summary>
	/// Minimum confidence required before orders are sent.
	/// </summary>
	public decimal ConfidenceThreshold
	{
		get => _confidenceThreshold.Value;
		set => _confidenceThreshold.Value = value;
	}


	/// <summary>
	/// Candle type that feeds the pattern grid.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="MnistPatternClassifierStrategy"/> class.
	/// </summary>
	public MnistPatternClassifierStrategy()
	{
		_lookbackPeriod = Param(nameof(LookbackPeriod), 14)
		.SetRange(10, 200)

		.SetDisplay("Lookback", "Number of candles converted into the pattern grid", "Pattern");

		_targetClass = Param(nameof(TargetClass), 1)
		.SetRange(0, 9)
		.SetDisplay("Target Class", "Pattern class that should be traded", "Pattern");

		_confidenceThreshold = Param(nameof(ConfidenceThreshold), 0.2m)
		.SetRange(0m, 1m)
		.SetDisplay("Confidence", "Minimum classification confidence", "Pattern");


		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
		.SetDisplay("Candle Type", "Primary timeframe used for the pattern", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, CandleType);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_closeWindow.Clear();
		_firstClose = 0m;
		_previousClose = 0m;
		_lastClass = -1;
		_lastConfidence = 0m;
		_cooldown = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		StartProtection(
			takeProfit: new Unit(3, UnitTypes.Percent),
			stopLoss: new Unit(2, UnitTypes.Percent));

		_rsi = new RelativeStrengthIndex
		{
			Length = LookbackPeriod,
		};

		_atr = new AverageTrueRange
		{
			Length = LookbackPeriod,
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_rsi, _atr, ProcessCandle)
		.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal atrValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		UpdateWindow(candle.ClosePrice);

		if (_closeWindow.Count < LookbackPeriod)
		{
			_previousClose = candle.ClosePrice;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_previousClose = candle.ClosePrice;
			return;
		}

		var pattern = ClassifyPattern(candle.ClosePrice, rsiValue, atrValue);

		_lastClass = pattern.PatternClass;
		_lastConfidence = pattern.Confidence;

		if (pattern.Confidence >= ConfidenceThreshold && pattern.Bias != PatternBiases.Neutral && Position == 0)
		{
			ExecuteBias(pattern.Bias);
		}

		_previousClose = candle.ClosePrice;
	}

	private void ExecuteBias(PatternBiases bias)
	{
		switch (bias)
		{
		case PatternBiases.Bullish:
			if (Position == 0)
			{
				BuyMarket();
				_cooldown = 50;
			}

			break;
		case PatternBiases.Bearish:
			if (Position == 0)
			{
				SellMarket();
				_cooldown = 50;
			}

			break;
		default:
			break;
		}
	}

	private void UpdateWindow(decimal close)
	{
		_closeWindow.Enqueue(close);

		if (_closeWindow.Count > LookbackPeriod)
		{
			_closeWindow.Dequeue();
		}

		_firstClose = _closeWindow.Count > 0 ? _closeWindow.Peek() : 0m;
	}

	private PatternResult ClassifyPattern(decimal currentClose, decimal rsiValue, decimal atrValue)
	{
		var stats = CalculateStatistics(currentClose, rsiValue, atrValue);

		var trendStrength = stats.TrendStrength;
		var rangeStrength = stats.RangeStrength;
		var breakoutRange = stats.BreakoutThreshold;
		var rangePosition = stats.RangePosition;
		var momentum = stats.Momentum;
		var rsi = stats.Rsi;
		var atr = stats.AtrNormalized;

		// Compute a blended confidence score similar to the ONNX output probability.
		var confidence = Math.Min(1m, (trendStrength + rangeStrength + Math.Min(1m, Math.Abs(momentum) / stats.MomentumThreshold) + stats.RsiDeviation + atr) / 5m);

		if (rangeStrength < stats.FlatThreshold)
		{
			return new PatternResult(0, Math.Max(confidence, 0.4m), PatternBiases.Neutral);
		}

		if (trendStrength >= stats.TrendThreshold)
		{
			if (rangePosition >= 0.75m && rangeStrength >= breakoutRange)
			{
				return new PatternResult(3, confidence, PatternBiases.Bullish);
			}

			if (momentum < 0m)
			{
				return new PatternResult(6, confidence * 0.8m, PatternBiases.Bullish);
			}

			return new PatternResult(1, confidence, PatternBiases.Bullish);
		}

		if (trendStrength <= -stats.TrendThreshold)
		{
			if (rangePosition <= 0.25m && rangeStrength >= breakoutRange)
			{
				return new PatternResult(4, confidence, PatternBiases.Bearish);
			}

			if (momentum > 0m)
			{
				return new PatternResult(7, confidence * 0.8m, PatternBiases.Bearish);
			}

			return new PatternResult(2, confidence, PatternBiases.Bearish);
		}

		if (rangeStrength >= breakoutRange)
		{
			return new PatternResult(5, confidence * 0.9m, PatternBiases.Neutral);
		}

		if (rangePosition <= 0.4m && rsi >= 55m)
		{
			return new PatternResult(8, confidence * 0.85m, PatternBiases.Bullish);
		}

		if (rangePosition >= 0.6m && rsi <= 45m)
		{
			return new PatternResult(9, confidence * 0.85m, PatternBiases.Bearish);
		}

		return new PatternResult(0, confidence * 0.7m, PatternBiases.Neutral);
	}

	private PatternStatistics CalculateStatistics(decimal currentClose, decimal rsiValue, decimal atrValue)
	{
		var window = _closeWindow.ToArray();
		decimal min = decimal.MaxValue;
		decimal max = decimal.MinValue;

		foreach (var value in window)
		{
			if (value < min)
			min = value;

			if (value > max)
			max = value;
		}

		var first = _firstClose;
		var last = currentClose;
		var range = max - min;
		var rangeStrength = first != 0m ? range / first : 0m;
		var trend = first != 0m ? (last - first) / first : 0m;
		var momentum = _previousClose != 0m ? (last - _previousClose) / _previousClose : 0m;
		var rsiDeviation = Math.Min(1m, Math.Abs(rsiValue - 50m) / 50m);
		var atrNormalized = first != 0m ? Math.Min(1m, atrValue / first) : 0m;

		var rangePosition = range > 0m ? (last - min) / range : 0.5m;

		const decimal baseThreshold = 0.001m;
		var trendThreshold = baseThreshold;
		var breakoutThreshold = baseThreshold * 1.4m;
		var flatThreshold = baseThreshold * 0.3m;
		var momentumThreshold = baseThreshold;

		return new PatternStatistics
		{
			TrendStrength = trend,
			RangeStrength = rangeStrength,
			BreakoutThreshold = breakoutThreshold,
			FlatThreshold = flatThreshold,
			RangePosition = rangePosition,
			Momentum = momentum,
			Rsi = rsiValue,
			AtrNormalized = atrNormalized,
			TrendThreshold = trendThreshold,
			MomentumThreshold = momentumThreshold,
			RsiDeviation = rsiDeviation,
		};
	}

	private readonly struct PatternResult
	{
		public PatternResult(int patternClass, decimal confidence, PatternBiases bias)
		{
			PatternClass = patternClass;
			Confidence = confidence;
			Bias = bias;
		}

		public int PatternClass { get; }

		public decimal Confidence { get; }

		public PatternBiases Bias { get; }
	}

	private readonly struct PatternStatistics
	{
		public decimal TrendStrength { get; init; }
		public decimal RangeStrength { get; init; }
		public decimal BreakoutThreshold { get; init; }
		public decimal FlatThreshold { get; init; }
		public decimal RangePosition { get; init; }
		public decimal Momentum { get; init; }
		public decimal Rsi { get; init; }
		public decimal AtrNormalized { get; init; }
		public decimal TrendThreshold { get; init; }
		public decimal MomentumThreshold { get; init; }
		public decimal RsiDeviation { get; init; }
	}
}