Ver no GitHub

Estratégia de sincronização de símbolos

Visão geral

A Estratégia de Sincronização de Símbolos replica o utilitário MetaTrader SymbolSyncEA dentro do ambiente StockSharp. A estratégia mantém sincronizados o símbolo da estratégia principal e todas as estratégias vinculadas cadastradas. Sempre que o símbolo primário muda, a estratégia propaga automaticamente a nova segurança para cada estratégia vinculada, garantindo que todo o espaço de trabalho siga o mesmo instrumento sem intervenção manual.

Ideias centrais

  • Capture a segurança da estratégia inicial na inicialização e reutilize-a como uma opção alternativa.
  • Mantenha uma lista configurável de estratégias vinculadas que sempre devem espelhar a segurança principal.
  • Permitir alterações de símbolos acionadas por uma atribuição Security direta ou pela especificação de um novo identificador de segurança.
  • Fornece sincronização manual e operações de redefinição para corresponder ao comportamento original do Expert Advisor.

Parâmetros

Nome Descrição Padrão
ChartLimit Número máximo de estratégias vinculadas que podem ser sincronizadas. Evita atualizações em massa acidentais. 10
SyncSecurityId Identificador do título propagado para estratégias vinculadas. Um valor vazio retorna para a segurança da estratégia. ""

Métodos públicos

  • RegisterLinkedStrategy(Strategy strategy) – adiciona uma instância de estratégia à lista de sincronização. Retorna true quando registrado com sucesso.
  • UnregisterLinkedStrategy(Strategy strategy) – remove uma estratégia da lista.
  • ChangeSyncSecurity(Security security) – alterna para a instância de segurança fornecida e a propaga para todas as estratégias vinculadas.
  • ChangeSyncSecurity(string securityId) – resolve o identificador por meio do SecurityProvider atual e chama ChangeSyncSecurity(Security).
  • ResetToInitialSecurity() – restaura o símbolo capturado na inicialização.
  • SyncSymbols() – força uma ressincronização manual sem alterar o identificador armazenado.

Fluxo de trabalho de uso

  1. Instancie SymbolSyncStrategy e defina o Security primário ou atribua SyncSecurityId antes de iniciar a estratégia.
  2. Chame RegisterLinkedStrategy para cada estratégia filha que deve espelhar o símbolo ativo (por exemplo, diferentes períodos de tempo ou painéis).
  3. Sempre que o símbolo principal mudar, chame ChangeSyncSecurity(Security) ou ChangeSyncSecurity(string).
  4. Opcionalmente, chame SyncSymbols() para forçar a propagação se um componente externo modificou uma estratégia vinculada.

Diferenças em comparação com a versão MQL

  • Funciona com StockSharp Strategy instâncias em vez de MetaTrader janelas de gráfico.
  • Usa a abstração SecurityProvider para resolver identificadores.
  • Adiciona registro defensivo e um limite configurável para estratégias sincronizadas.
  • Oferece métodos explícitos de redefinição e sincronização manual para cenários de automação avançados.

Notas

  • A estratégia não emite ordens de mercado; funciona como um auxiliar de infraestrutura.
  • Todos os comentários do código são mantidos em inglês para atender aos requisitos do projeto.
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;
	}
}