GitHub で見る

Mnist パターン分類子戦略

起源

この戦略は、MetaTrader 5 エキスパート TestMnistOnnx.mq5 (MQL ID 47225) の StockSharp 移植です。元のスクリプトはインタラクティブな ユーザーがバンドルされた MNIST ONNX モデルによって分類された数字を描画するキャンバス。 StockSharp バージョンは、の精神を維持しています。 パターン認識を使用しますが、手描きのキャンバスを、完成したキャンドルから構築されたローリング マトリックスに置き換えます。

コンセプト

  1. LookbackPeriod 個の完了したローソク足のローリング ウィンドウ (デフォルトは 28) は、MNIST 画像と同様の 28×28 グリッドとして扱われます。
  2. 範囲圧縮、トレンドの強さ、勢い、RSI の偏差、および ATR の正規化など、いくつかの統計的特徴がブレンドされています。 MQL の専門家によって生成されたニューラル ネットワークの確率を模倣する合成「信頼性」スコアに変換されます。
  3. 結果の特徴は、10 個のパターン クラス (09) の 1 つにマッピングされます。各クラスは市場体制を表します (フラット、トレンド、ブレイクアウト、プルバック、反転など)。
  4. 検出されたクラスがユーザーが選択した TargetClass と一致し、合成信頼度が ConfidenceThreshold を超える場合、 戦略は、指定された方向にポジションをオープンまたは反転します。クラスが変わったり、 信頼度がしきい値を下回ります。

パラメーター

パラメータ デフォルト 説明
LookbackPeriod 28 MNIST のようなグリッドに変換された完成したキャンドルの数。
TargetClass 1 取引アクションをトリガーするクラス インデックス (0 ~ 9)。
ConfidenceThreshold 0.6 注文の送信を許可する最小合成確率。
Volume 1 新しいポジションの注文量。
CandleType 5分間の時間枠 キャンドル更新用にサブスクライブされたデータ型。

パターンクラス

クラス 意味
0 フラットまたは低ボラティリティの統合。
1 強気傾向が継続。
2 持続的な弱気傾向。
3 強力なフォロースルーで上向きにブレイクアウト。
4 強力なフォロースルーで下側へブレイクアウト。
5 明確な偏りのない広い変動範囲。
6 上昇トレンド内での強気の引き戻し。
7 下降トレンド内での弱気の反発。
8 長期にわたる下落後の強気反転。
9 長期にわたる前進の後、弱気の反転。

取引ルール

  • 完成したローソク足のみを取引して、完成した図面に反応した元のエキスパートとの同期を保ちます。
  • 成行注文 (BuyMarketSellMarket) を使用し、反転する前にフラット化して、単一ポジションの動作を模倣します。 オリジナル脚本。
  • 信頼度スケーリングは [0, 1] に制限されます。 ConfidenceThreshold を上げると、より弱い信号がフィルタリングされます。
  • この戦略は保護停止を管理しません。リスク管理は、StockSharp で外部的に構成されることが期待されます。

使用のヒント

  • 分析したい市場リズムを反映するローソクの種類を選択します。タイムフレームが短いほど反応は速くなりますが、ノイズが多くなります。
  • TargetClassConfidenceThreshold を一緒に最適化します。一部のクラスは自然に稀であるため、より低いしきい値が必要になる場合があります。
  • 合成パターン分類子は決定的です。外部 ONNX ランタイム ライブラリへの依存関係はありません。
  • StockSharp で利用可能な組み込みのリスク保護ツール (StartProtection など) と組み合わせて、リスクを制御します。

オリジナルとの違い

  • インタラクティブな描画と ONNX 推論は、完全に自動化されたローソク足分析に置き換えられます。
  • 「信頼度」は、ニューラル ネットワークの確率ではなく、指標の決定論的なブレンドです。
  • 取引ロジックを追加して、パターン認識を実行可能な注文に変換します。
  • MNIST リソース ファイルは、StockSharp 環境では必要ありません。
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; }
	}
}