GitHub で見る

シンボル同期戦略

概要

シンボル同期戦略は、StockSharp 環境内で MetaTrader ユーティリティ SymbolSyncEA を複製します。この戦略では、メイン戦略シンボルとすべての登録されたリンクされた戦略の同期が維持されます。プライマリシンボルが変更されるたびに、ストラテジーはリンクされているすべてのストラテジーに新しい証券を自動的に伝播し、手動介入なしでワークスペース全体が同じ金融商品に従うようにします。

核となるアイデア

  • 起動時に初期戦略のセキュリティを取得し、フォールバック オプションとして再利用します。
  • 常に主要なセキュリティを反映するリンクされた戦略の構成可能なリストを保持します。
  • Security の直接割り当てまたは新しいセキュリティ識別子の指定によってトリガーされるシンボルの変更を許可します。
  • 元の Expert Advisor の動作と一致するように、手動の同期とリセット操作を提供します。

パラメーター

名前 説明 デフォルト
ChartLimit 同期できるリンクされた戦略の最大数。誤った大量更新を防ぎます。 10
SyncSecurityId リンクされた戦略に伝播されるセキュリティの識別子。空の値は戦略セキュリティにフォールバックします。 ""

パブリックメソッド

  • RegisterLinkedStrategy(Strategy strategy) – 戦略インスタンスを同期リストに追加します。正常に登録されると、true を返します。
  • UnregisterLinkedStrategy(Strategy strategy) – リストから戦略を削除します。
  • ChangeSyncSecurity(Security security) – 提供されたセキュリティ インスタンスに切り替え、リンクされているすべての戦略にそれを伝播します。
  • ChangeSyncSecurity(string securityId) – 現在の SecurityProvider を通じて識別子を解決し、ChangeSyncSecurity(Security) を呼び出します。
  • ResetToInitialSecurity() – 起動時にキャプチャされたシンボルを復元します。
  • SyncSymbols() – 保存されている識別子を変更せずに手動で再同期を強制します。

使用ワークフロー

  1. 戦略を開始する前に、SymbolSyncStrategy をインスタンス化し、プライマリ Security を設定するか、SyncSecurityId を割り当てます。
  2. アクティブなシンボルをミラーリングする必要がある子戦略ごとに RegisterLinkedStrategy を呼び出します (たとえば、異なるタイムフレームやダッシュボード)。
  3. メインシンボルを変更する必要がある場合は、ChangeSyncSecurity(Security) または ChangeSyncSecurity(string) を呼び出します。
  4. 外部コンポーネントがリンクされた戦略を変更した場合は、オプションで SyncSymbols() を呼び出して伝播を強制します。

MQL バージョンとの違い

  • MetaTrader チャート ウィンドウではなく、StockSharp Strategy インスタンスで動作します。
  • SecurityProvider 抽象化を使用して識別子を解決します。
  • 防御的なログと、同期された戦略に対する構成可能な制限を追加します。
  • 高度な自動化シナリオ向けに、明示的なリセットおよび手動同期方法を提供します。

注意事項

  • この戦略は成行注文を発行しません。インフラストラクチャヘルパーとして動作します。
  • プロジェクト要件に準拠するために、すべてのコード コメントは英語で保存されます。
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>
/// Strategy that synchronizes the security of linked strategies whenever the main symbol changes.
/// </summary>
public class SymbolSyncStrategy : Strategy
{
	private readonly StrategyParam<int> _chartLimit;
	private readonly StrategyParam<string> _syncSecurityId;

	private readonly List<Strategy> _linkedStrategies = new();

	private Security _initialSecurity;

	public SymbolSyncStrategy()
	{
		_chartLimit = Param(nameof(ChartLimit), 10)
			.SetNotNegative()
			.SetDisplay("Chart limit", "Maximum number of linked strategies that can be synchronized.", "General")
			;

		_syncSecurityId = Param(nameof(SyncSecurityId), string.Empty)
			.SetDisplay("Sync security ID", "Identifier of the security propagated to linked strategies.", "General")
			;
	}

	/// <summary>
	/// Maximum number of linked strategies that can follow the symbol changes.
	/// </summary>
	public int ChartLimit
	{
		get => _chartLimit.Value;
		set => _chartLimit.Value = value;
	}

	/// <summary>
	/// Identifier of the security that must be mirrored by linked strategies.
	/// </summary>
	public string SyncSecurityId
	{
		get => _syncSecurityId.Value;
		set => _syncSecurityId.Value = value ?? string.Empty;
	}

	private SimpleMovingAverage _smaFast;
	private SimpleMovingAverage _smaSlow;
	private int _candleCount;
	private int _lastTradeCandle;

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_linkedStrategies.Clear();
		_initialSecurity = default;
		_smaFast = default;
		_smaSlow = default;
		_candleCount = default;
		_lastTradeCandle = default;
		_syncSecurityId.Value = string.Empty;
	}

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

		_initialSecurity = Security;

		if (SyncSecurityId.IsEmpty() && Security != null)
			SyncSecurityId = Security.Id;

		_smaFast = new SimpleMovingAverage { Length = 10 };
		_smaSlow = new SimpleMovingAverage { Length = 30 };

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

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

		_candleCount++;

		if (_candleCount - _lastTradeCandle < 200)
			return;

		if (fast > slow && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
			_lastTradeCandle = _candleCount;
		}
		else if (fast < slow && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
			_lastTradeCandle = _candleCount;
		}
	}

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

		_initialSecurity = null;
	}

	/// <summary>
	/// Registers an additional strategy that must mirror the current security.
	/// </summary>
	/// <param name="strategy">Strategy that will receive symbol updates.</param>
	/// <returns><c>true</c> when the strategy is registered; otherwise <c>false</c>.</returns>
	public bool RegisterLinkedStrategy(Strategy strategy)
	{
		if (strategy == null)
			throw new ArgumentNullException(nameof(strategy));

		if (_linkedStrategies.Contains(strategy))
			return false;

		var limit = Math.Max(ChartLimit, 0);
		if (_linkedStrategies.Count >= limit)
		{
			LogWarning($"Chart limit of {limit} reached. Strategy '{strategy.Name}' cannot be synchronized.");
			return false;
		}

		_linkedStrategies.Add(strategy);
		ApplySymbol(strategy);
		return true;
	}

	/// <summary>
	/// Removes a strategy from the synchronization list.
	/// </summary>
	/// <param name="strategy">Strategy previously added with <see cref="RegisterLinkedStrategy"/>.</param>
	/// <returns><c>true</c> when the strategy was removed.</returns>
	public bool UnregisterLinkedStrategy(Strategy strategy)
	{
		if (strategy == null)
			throw new ArgumentNullException(nameof(strategy));

		return _linkedStrategies.Remove(strategy);
	}

	/// <summary>
	/// Restores the initial security captured when the strategy started.
	/// </summary>
	public void ResetToInitialSecurity()
	{
		if (_initialSecurity == null)
			return;

		ChangeSyncSecurity(_initialSecurity);
	}

	/// <summary>
	/// Changes the synchronization security using a resolved <see cref="Security"/> instance.
	/// </summary>
	/// <param name="security">Security that should be mirrored by linked strategies.</param>
	/// <returns><c>true</c> when the identifier changed.</returns>
	public bool ChangeSyncSecurity(Security security)
	{
		if (security == null)
			throw new ArgumentNullException(nameof(security));

		if (Security != security)
			Security = security;

		if (SyncSecurityId.EqualsIgnoreCase(security.Id))
		{
			SyncSymbols();
			return false;
		}

		SyncSecurityId = security.Id;
		SyncSymbols();
		return true;
	}

	/// <summary>
	/// Changes the synchronization security by resolving the identifier through <see cref="Strategy.Connector"/>.
	/// </summary>
	/// <param name="securityId">Identifier of the security to use for synchronization.</param>
	/// <returns><c>true</c> when the identifier resolved to a new security.</returns>
	public bool ChangeSyncSecurity(string securityId)
	{
		if (securityId.IsEmpty())
			throw new ArgumentNullException(nameof(securityId));

		if (Connector != null)
		{
			var resolved = Connector.LookupById(securityId);
			if (resolved != null)
				return ChangeSyncSecurity(resolved);

			LogWarning($"Security '{securityId}' not found by the security provider.");
		}

		SyncSecurityId = securityId;
		SyncSymbols();
		return false;
	}

	/// <summary>
	/// Synchronizes the security across every registered strategy.
	/// </summary>
	/// <returns><c>true</c> when a security was resolved and propagated.</returns>
	public bool SyncSymbols()
	{
		var security = ResolveSecurity();
		if (security == null)
		{
			LogWarning("No synchronization security resolved. Linked strategies keep their current assignments.");
			return false;
		}

		if (Security != security)
			Security = security;

		foreach (var strategy in _linkedStrategies)
			ApplySymbol(strategy);

		return true;
	}

	private void ApplySymbol(Strategy strategy)
	{
		if (strategy == null)
			return;

		var security = ResolveSecurity();
		if (security == null)
			return;

		if (strategy.Security == security)
			return;

		strategy.Security = security;
	}

	private Security ResolveSecurity()
	{
		if (Security != null && (SyncSecurityId.IsEmpty() || SyncSecurityId.EqualsIgnoreCase(Security.Id)))
			return Security;

		if (!SyncSecurityId.IsEmpty() && Connector != null)
		{
			var resolved = Connector.LookupById(SyncSecurityId);
			if (resolved != null)
				return resolved;
		}

		return Security;
	}
}