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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class symbol_sync_strategy(Strategy):
"""Fast/slow SMA crossover (10/30) on 5-min candles."""
def __init__(self):
super(symbol_sync_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnStarted2(self, time):
super(symbol_sync_strategy, self).OnStarted2(time)
fast = SimpleMovingAverage()
fast.Length = 10
slow = SimpleMovingAverage()
slow.Length = 30
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(fast, slow, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def OnProcess(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
if fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return symbol_sync_strategy()