GitHub で見る

未来パターン記憶戦略

概要

FuturePatternMemoryStrategy は、古典的な MetaTrader エキスパート FutureMA および FutureMACD の StockSharp 移植です。オリジナルのロボットは一連の指標の違いを CSV ファイルに記録し、保存された統計を再利用して、現在の状況が強気のブレイクアウトに有利か弱気のブレイクアウトに有利かを判断しました。この C# バージョンは同じ考え方を維持していますが、ファイル システムをメモリ内のパターン ウェアハウスに置き換え、すべてのノブを戦略パラメーターとして公開します。この戦略は、平滑化移動平均スプレッド (FutureMA ロジック) または MACD ヒストグラム (FutureMACD ロジック) のいずれかで動作できます。

この戦略では、完成した各キャンドルを 5 段階で評価します。

  1. インジケーター投影 – 選択したオシレーター (MA スプレッドまたは MACD ヒストグラム) を計算し、構成可能なスケーリング係数で正規化します。値は整数に離散化され、コンパクトなパターン署名が作成されます。
  2. パターン ハッシュ – 最新の AnalysisBars の正規化された値のスライディング ウィンドウを維持します。新しいバーが閉じるたびに、ウィンドウは現在の市場コンテキストを識別する一意のハッシュ文字列に変換されます。
  3. 過去のスイング分析 – 以前の FractalDepth ローソク足を検査し、最も古い始値と最高値/最低値の間の距離を測定し、それらの範囲をポイントに変換します。これらの距離は、元のロボットが CSV ファイルに蓄積した報酬の期待値です。
  4. 加重メモリ更新 – ハッシュ キーは、パターン ディクショナリ内のエントリを取得または作成するために使用されます。強気と弱気のテイクプロフィット予想は、ForgettingFactor によって制御される加重移動平均で更新されます。これは、MQL コードからの「忘れっぽさ」係数 (zabyvaemost) を再現します。
  5. シグナルの評価と実行 – 強気の予想が優勢で、パターンが MinimumMatches 回以上見られ、予測利益が MinimumTakeProfit を超えている場合、戦略はロングポジションに入るかロングポジションに追加されます。弱気ブランチは対称的に機能します。プロテクティブレベルは保存された統計から導出され、オプションで取引が有利に進むにつれて追跡されます。

変換メモ

  • 両方の MetaTrader エキスパートは、Source パラメータを介して 1 つの構成可能な戦略に統合され、再コンパイルせずに MA ベースのエンジンと MACD ベースのエンジンを切り替えることができます。
  • ファイルベースの永続性は、実行中にすべての統計をメモリに保持する Dictionary<string, PatternStats> に置き換えられました。これにより、ファイル I/O が回避され、StockSharp サンドボックス モデル内に留まります。
  • ポジション管理は、元のストップ/ターゲット配置を複製します。ストップは完全な平均スイングを使用し、テイクプロフィットは StatisticalTakeRatio (元の Stat_Take_Profit) を使用します。 EnableTrailingStop が true の場合、MQL エキスパートが注文を変更したのとまったく同じように、ストップは利益距離の 4 分の 1 ステップで移動します。
  • 手動モード (ManualMode) は自動発注を無効にしますが、元の Ruchnik フラグの動作と一致する統計の収集を継続します。
  • スケーリングイン (AllowAddOn) は、dokupka フラグを模倣し、新しいバーでパターンが繰り返されるたびに戦略でボリュームを追加できるようにします。

取引ロジックの詳細

  • インジケーターソース
    • MA スプレッド: 中央価格の 2 つの平滑化移動平均 (SMMA 6 および SMMA 24) を計算し、その差を使用します。
    • MACD ヒストグラム: MACD メインラインとシグナルラインの差を計算します (デフォルトでは 12/26/9 構成)。
  • 正規化: NormalizationFactortocnost を再現します。生の差を整数署名に変換する前にスケーリングします。変換では、pip ベースの単位を維持するために、100 * MinPriceStep で除算されます。
  • パターンメモリ: 辞書には、サインごとに、強気の一致の数、平均強気の距離、弱気の一致の数、および平均弱気の距離が保存されます。値は重み付けされた式 (current + input * ForgettingFactor) / (1 + ForgettingFactor) で更新されます。
  • エントリールール:
    • ロング: 強気の期待値 ≥ 弱気の期待値、強気の一致 > MinimumMatches、予想される強気の距離 > MinimumTakeProfit
    • 短い: 弱気期待 ≥ 強気期待、弱気一致 > MinimumMatches、予想弱気距離 > MinimumTakeProfit
  • リスク管理: ストップロスは、ポジションに対する完全な平均スイングに設定されます。テイクプロフィットはそのスイングの StatisticalTakeRatio を使用します。元のトレーリング ルーチンと同様に、トレーリング ストップは価格が距離の 4 分の 1 移動した後に移動します。

パラメーター

パラメータ 説明 デフォルト
CandleType 計算に使用される主な時間枠。 30分キャンドル
Source MA スプレッド (FutureMA) と MACD ヒストグラム (FutureMACD) のどちらかを選択します。 MaSpread
FastMaLength / SlowMaLength Source = MaSpread の場合の SMMA の長さ。 6/24
MacdFastLength / MacdSlowLength / MacdSignalLength MACD 期間、Source = MacdHistogram 12/26/9
AnalysisBars パターン シグネチャを形成するバーの数。 8
FractalDepth ブレイクアウト距離を測定するために使用される過去のローソク足の数。 4
MinimumMatches 取引を行う前に保存される必要なオカレンスの数。 5
MinimumTakeProfit 信号を受け入れるために予想される最小距離 (ポイント単位)。 30
NormalizationFactor インジケーターの差に適用されるスケーリング係数。 10
ForgettingFactor パターンメモリ内の新しい測定値に適用される重み。 1.5
StatisticalTakeRatio 測定されたスイングに対するテイクプロフィット率。 0.5
EnableTrailingStop クォーターステップのトレーリングストップロジックを有効にします。 false
ManualMode 統計を収集しますが、注文はスキップします。 false
AllowAddOn パターンが繰り返されるときに拡大縮小を許可します。 true
Volume エントリーに使用される注文サイズ。 0.1

実践的なアドバイス

  • この戦略は離散化されたハッシュに依存しているため、NormalizationFactorAnalysisBars を慎重に選択してください。値が大きすぎると署名がまばらになり、値が小さすぎると個別の状態が混合されます。
  • ライブ取引で実行する場合、実行間の永続性が必要な場合は、セッション後にパターン ディクショナリをエクスポートすることを検討してください。
  • MQL バージョンではシンボル/期間ごとにデータが保存されていたため、統計の相互汚染を避けるために、銘柄/時間枠ごとに専用のストラテジー インスタンスを保持することをお勧めします。
using System;
using System.Collections.Generic;
using System.Text;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Pattern memory strategy that records normalized MA spread sequences,
/// tracks fractal outcomes, and trades when a recognized pattern has favorable statistics.
/// </summary>
public class FuturePatternMemoryStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastMaLength;
	private readonly StrategyParam<int> _slowMaLength;
	private readonly StrategyParam<int> _patternLength;
	private readonly StrategyParam<int> _minMatches;

	private readonly Queue<int> _patternWindow = new();
	private readonly Dictionary<string, (int buyCount, int sellCount)> _patterns = new();
	private decimal _entryPrice;
	private int _barIndex;
	private int _lastEntryBar;

	public FuturePatternMemoryStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for analysis.", "General");

		_fastMaLength = Param(nameof(FastMaLength), 6)
			.SetDisplay("Fast MA", "Fast EMA period.", "Indicators");

		_slowMaLength = Param(nameof(SlowMaLength), 24)
			.SetDisplay("Slow MA", "Slow EMA period.", "Indicators");

		_patternLength = Param(nameof(PatternLength), 5)
			.SetDisplay("Pattern Length", "Number of bars in pattern signature.", "Pattern");

		_minMatches = Param(nameof(MinMatches), 3)
			.SetDisplay("Min Matches", "Minimum pattern occurrences before trading.", "Pattern");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastMaLength
	{
		get => _fastMaLength.Value;
		set => _fastMaLength.Value = value;
	}

	public int SlowMaLength
	{
		get => _slowMaLength.Value;
		set => _slowMaLength.Value = value;
	}

	public int PatternLength
	{
		get => _patternLength.Value;
		set => _patternLength.Value = value;
	}

	public int MinMatches
	{
		get => _minMatches.Value;
		set => _minMatches.Value = value;
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_patternWindow.Clear();
		_patterns.Clear();
		_entryPrice = 0;
		_barIndex = 0;
		_lastEntryBar = 0;
	}

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

		_patternWindow.Clear();
		_patterns.Clear();
		_entryPrice = 0;
		_barIndex = 0;
		_lastEntryBar = 0;

		var fastEma = new ExponentialMovingAverage { Length = FastMaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowMaLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fastEma);
			DrawIndicator(area, slowEma);
			DrawOwnTrades(area);
		}
	}

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

		_barIndex++;
		var close = candle.ClosePrice;

		// Normalize the MA spread into a discrete value
		var spread = fastValue - slowValue;
		var normalized = spread > 0 ? 1 : (spread < 0 ? -1 : 0);

		_patternWindow.Enqueue(normalized);
		while (_patternWindow.Count > PatternLength)
			_patternWindow.Dequeue();

		if (_patternWindow.Count < PatternLength)
			return;

		var key = BuildPatternKey(_patternWindow);

		// Record outcome: if price went up, it's a buy match, otherwise sell
		if (!_patterns.TryGetValue(key, out var stats))
			stats = (0, 0);

		if (close > fastValue)
			stats = (stats.buyCount + 1, stats.sellCount);
		else if (close < fastValue)
			stats = (stats.buyCount, stats.sellCount + 1);

		_patterns[key] = stats;

		// Cooldown: minimum SlowMaLength*4 bars between any trade action
		var cooldownBars = SlowMaLength * 4;
		var cooledDown = _barIndex - _lastEntryBar >= cooldownBars;

		// Position management
		var exitedThisBar = false;
		if (Position > 0)
		{
			if (cooledDown && (spread < 0 || (_entryPrice > 0 && close < _entryPrice * 0.985m)))
			{
				SellMarket();
				_lastEntryBar = _barIndex;
				exitedThisBar = true;
			}
		}
		else if (Position < 0)
		{
			if (cooledDown && (spread > 0 || (_entryPrice > 0 && close > _entryPrice * 1.015m)))
			{
				BuyMarket();
				_lastEntryBar = _barIndex;
				exitedThisBar = true;
			}
		}

		// Entry based on pattern statistics (cooldown between entries)
		if (!exitedThisBar && Position == 0 && cooledDown)
		{
			var total = stats.buyCount + stats.sellCount;
			if (total >= MinMatches)
			{
				if (stats.buyCount > stats.sellCount && spread > 0)
				{
					_entryPrice = close;
					_lastEntryBar = _barIndex;
					BuyMarket();
				}
				else if (stats.sellCount > stats.buyCount && spread < 0)
				{
					_entryPrice = close;
					_lastEntryBar = _barIndex;
					SellMarket();
				}
			}
		}
	}

	private static string BuildPatternKey(IEnumerable<int> values)
	{
		var sb = new StringBuilder();
		var first = true;
		foreach (var v in values)
		{
			if (!first) sb.Append('_');
			sb.Append(v);
			first = false;
		}
		return sb.ToString();
	}
}