GitHub で見る

FineTuning MA ローソク足 Duplex 戦略

概要

  • MetaTrader 5 エキスパートアドバイザー Exp_FineTuningMACandle_Duplex の C# ポート。
  • ロングとショートのロジックを別々に調整できるように、FineTuningMA ローソク足インジケーターを2つの独立したストリームで再現します。
  • StockSharp の高レベルストラテジー API 向けに設計: サブスクリプション、インジケーター、リスク管理、チャート描画はすべてフレームワークによって自動的に管理されます。

FineTuningMA ローソク足モデル

  • 元のインジケーターは最後の Length バーに3つの加重指数(Rank1Rank3)と対応するシフト係数を適用して合成ローソク足を構築します。
  • 結果として得られた加重始値と終値が比較されてカラーコードが生成されます: 2 は強気、1 はニュートラル、0 は弱気。
  • 実際のローソク足の本体が設定可能な Gap より小さい場合、合成始値は前の合成終値に設定されます。これは MQL5 バージョンの「フラットボディ」ロジックを再現します。
  • このポートのインジケーターは取引ルールがカラー遷移のみに依存するため、カラーストリーム(小数値 0/1/2)のみを出力します。

取引ロジック

  1. 2つのローソク足フィード(LongCandleTypeShortCandleType)をサブスクライブします。同じ時間軸または異なる時間軸を指定できます。
  2. 各フィードに対して、独自の重み付けパラメーターとシグナルオフセット(SignalBar)を持つ専用の FineTuningMA インジケーターインスタンスが作成されます。
  3. 完了したローソク足イベントは以下のルールで処理されます:
    • ロングエグジット – 前のカラーが 0 と等しい場合、既存のロングポジションがクローズされます。
    • ロングエントリー – 前のカラーが 2 と等しく、現在のカラーが 2 から変化した場合、買い注文が送信されます(既存のショートをカバーした後)。
    • ショートエグジット – 前のカラーが 2 と等しい場合、既存のショートポジションがカバーされます。
    • ショートエントリー – 前のカラーが 0 と等しく、現在のカラーが 0 から変化した場合、売り注文が送信されます(既存のロングをカバーした後)。
  4. 注文量は OrderVolume によって制御されます。リバーサルが必要な場合、ストラテジーはポジションを1つの成行注文で反転させるために現在の絶対ポジションを自動的に加算します。
  5. オプションの保護バリア(TakeProfitPointsStopLossPoints)は価格ポイントに換算され、StartProtection を通じて適用されます。

パラメーター

ロングストリーム

  • LongCandleType – ロングインジケーターストリームのローソク足データ型(時間軸)。
  • LongLength – 加重計算に使用するバーの数。
  • LongRank1, LongRank2, LongRank3 – ルックバックウィンドウ全体の重みカーブを形成する指数係数。
  • LongShift1, LongShift2, LongShift3 – ウィンドウの始まりまたは終わりに向けて重みを傾ける追加修正値(0…1)。
  • LongGap – 合成始値を前の合成終値と等しく保つ実際のローソク足本体の最大サイズ。
  • LongSignalBar – シグナルを読み取る前にスキップする完了ローソク足の数(0 は最後に閉じたローソク足を評価、1 は前のものを使用、など)。
  • EnableLongEntries – ロングエントリーを切り替えます。
  • EnableLongExits – 自動ロングエグジットを切り替えます。

ショートストリーム

  • ShortCandleType – ショートインジケーターストリームのローソク足データ型。
  • ShortLength, ShortRank1, ShortRank2, ShortRank3, ShortShift1, ShortShift2, ShortShift3, ShortGap, ShortSignalBar – ショートストリームに適用されるロングサイドの対応パラメーターと同一。
  • EnableShortEntries – ショートエントリーを切り替えます。
  • EnableShortExits – 自動ショートエグジットを切り替えます。

トレーディング

  • OrderVolume – 新しいポジションのベース数量。リバーサルはこの値に現在の絶対ポジションを自動的に加算します。
  • TakeProfitPoints – 価格ポイントで表されるオプションのテイクプロフィット距離(0で無効化)。
  • StopLossPoints – 価格ポイントで表されるオプションのストップロス距離(0で無効化)。

注意事項

  • 元のエキスパートアドバイザーにはバランスまたはマージンに基づくマネー管理モードが含まれていました。ポートはよりシンプルな固定 OrderVolume パラメーターを公開します。希望するポジションサイジングに合わせて調整してください。
  • StartProtection は楽器が有効な価格ステップ(Security.Step > 0)を公開する場合のみ呼び出されます。
  • Python バージョンは意図的に提供されていません。
  • チャートエリアは自動的に作成されます: ロングとショートのローソク足フィードが異なる場合は2つの別パネルが表示され、そうでなければ1つのチャートのみが表示されます。
  • ストラテジーは完了したローソク足に依存します。イントラバーの更新には反応しません。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// FineTuning MA Candle Duplex strategy using WMA crossover.
/// Buys when fast WMA crosses above slow WMA, sells on reverse.
/// </summary>
public class FineTuningMaCandleDuplexStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private WeightedMovingAverage _fast;
	private WeightedMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// Fast WMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow WMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="FineTuningMaCandleDuplexStrategy"/> class.
	/// </summary>
	public FineTuningMaCandleDuplexStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast WMA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 85)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow WMA period", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 200)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

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

		_fast = null;
		_slow = null;
		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

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

		_fast = new WeightedMovingAverage { Length = FastPeriod };
		_slow = new WeightedMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

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

		if (!_fast.IsFormed || !_slow.IsFormed)
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}

		// WMA crossover
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 80;
		}
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 80;
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}