Ver en GitHub

Estrategia de sincronización de símbolos

Descripción general

La Estrategia de sincronización de símbolos replica la utilidad MetaTrader SymbolSyncEA dentro del entorno StockSharp. La estrategia mantiene sincronizados el símbolo de la estrategia principal y todas las estrategias vinculadas registradas. Cada vez que cambia el símbolo principal, la estrategia propaga automáticamente la nueva seguridad a cada estrategia vinculada, asegurando que todo el espacio de trabajo siga el mismo instrumento sin intervención manual.

Ideas centrales

  • Capture la seguridad de la estrategia inicial al inicio y reutilícela como opción alternativa.
  • Mantenga una lista configurable de estrategias vinculadas que siempre deben reflejar la seguridad principal.
  • Permitir cambios de símbolos desencadenados por una asignación directa de Security o especificando un nuevo identificador de seguridad.
  • Proporcione operaciones de sincronización y restablecimiento manual para que coincidan con el comportamiento original del Asesor Experto.

Parámetros

Nombre Descripción Predeterminado
ChartLimit Número máximo de estrategias vinculadas que se pueden sincronizar. Evita actualizaciones masivas accidentales. 10
SyncSecurityId Identificador del valor propagado a estrategias vinculadas. Un valor vacío vuelve a la seguridad de la estrategia. ""

Métodos públicos

  • RegisterLinkedStrategy(Strategy strategy): agrega una instancia de estrategia a la lista de sincronización. Devuelve true cuando se registra correctamente.
  • UnregisterLinkedStrategy(Strategy strategy): elimina una estrategia de la lista.
  • ChangeSyncSecurity(Security security): cambia a la instancia de seguridad proporcionada y la propaga a cada estrategia vinculada.
  • ChangeSyncSecurity(string securityId) – resuelve el identificador a través del SecurityProvider actual y llama a ChangeSyncSecurity(Security).
  • ResetToInitialSecurity(): restaura el símbolo capturado al inicio.
  • SyncSymbols(): fuerza una resincronización manual sin cambiar el identificador almacenado.

Flujo de trabajo de uso

  1. Cree una instancia de SymbolSyncStrategy y establezca el Security principal o asigne SyncSecurityId antes de comenzar la estrategia.
  2. Llame a RegisterLinkedStrategy para cada estrategia secundaria que deba reflejar el símbolo activo (por ejemplo, diferentes períodos de tiempo o paneles).
  3. Siempre que el símbolo principal cambie, llame a ChangeSyncSecurity(Security) o ChangeSyncSecurity(string).
  4. Opcionalmente, llame a SyncSymbols() para forzar la propagación si un componente externo modificó una estrategia vinculada.

Diferencias respecto a la versión MQL

  • Funciona con StockSharp Strategy instancias en lugar de MetaTrader ventanas de gráficos.
  • Utiliza la abstracción SecurityProvider para resolver identificadores.
  • Agrega registro defensivo y un límite configurable para estrategias sincronizadas.
  • Ofrece métodos explícitos de reinicio y sincronización manual para escenarios de automatización avanzados.

Notas

  • La estrategia no emite órdenes de mercado; Funciona como ayudante de infraestructura.
  • Todos los comentarios del código se mantienen en inglés para cumplir con los requisitos del proyecto.
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;
	}
}