Compatibilidade da Estratégia com Plataformas StockSharp
Ao desenvolver estratégias de negociação no StockSharp, é importante considerar a sua compatibilidade com várias plataformas: Designer, Shell, Runner e testes na cloud. Ao seguir as recomendações abaixo, criará uma estratégia que funciona correctamente em todos os ambientes.
Parâmetros do Construtor da Estratégia
Evitar Parâmetros no Construtor
Para garantir a compatibilidade com as plataformas StockSharp, especialmente com testes na cloud, não deve adicionar parâmetros ao construtor da estratégia:
// Correto: construtor sem parâmetros
public class SmaStrategy : Strategy
{
public SmaStrategy()
{
// Inicialização de parâmetros
}
}
// Incorreto: construtor com parâmetros
public class SmaStrategy : Strategy
{
public SmaStrategy(int longLength, int shortLength) // Don't use this approach
{
// ...
}
}
As plataformas StockSharp criam instâncias de estratégias usando um construtor sem parâmetros. Se a sua estratégia exigir um construtor com parâmetros, não será inicializada correctamente.
Usar StrategyParam em Vez de Propriedades Normais
Vantagens de StrategyParam
Em vez de criar propriedades C# normais e depois sobrepor os métodos Save e Load, use StrategyParam<T> para todos os parâmetros personalizáveis:
// Correto: usando StrategyParam
private readonly StrategyParam<int> _longSmaLength;
public int LongSmaLength
{
get => _longSmaLength.Value;
set => _longSmaLength.Value = value;
}
public SmaStrategy()
{
_longSmaLength = Param(nameof(LongSmaLength), 80)
.SetDisplay("Período da SMA longa", string.Empty, "Configurações básicas");
}
// Incorreto: usando propriedades comuns
private int _longSmaLength = 80; // Don't use this approach
public int LongSmaLength
{
get => _longSmaLength;
set => _longSmaLength = value;
}
Os parâmetros criados através de StrategyParam<T> irão automaticamente:
- Ser apresentados nas interfaces de utilizador da plataforma
- Ser guardados e carregados sem sobrepor os métodos
SaveeLoad - Ser usados na optimização
- Ser serializados correctamente quando enviados para testes na cloud
Trabalhar com a Interface de Utilizador
Usar Abstracções em Vez de Acesso Directo à UI
Em vez de aceder directamente aos elementos da interface de utilizador, use as abstracções fornecidas pelo StockSharp:
// Abordagem correta: usando IChart
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Obter gráfico fornecido pelo ambiente de execução
_chart = GetChart();
if (_chart != null)
{
// O gráfico está disponível (por exemplo, no Designer ou Shell)
InitChart();
}
else
{
// O gráfico não está disponível (por exemplo, no Runner ou em backtesting na nuvem)
// A estratégia continua funcionando sem visualização
}
}
private void InitChart()
{
// Configurar gráfico pela interface abstrata
_chart.ClearAreas();
var area = _chart.AddArea();
_chartCandleElement = area.AddCandles();
// ...
}
O método Strategy.GetChart() devolve uma interface IChart se existir um gráfico disponível no ambiente de execução actual. Se a estratégia estiver a correr no Runner de consola ou em testes na cloud, onde não existe interface gráfica, o método devolverá null.
A interface IChart fornece métodos para trabalhar com gráficos:
- AddArea - para adicionar uma área ao gráfico
- RemoveArea - para remover uma área
- AddElement - para adicionar um elemento ao gráfico
- RemoveElement - para remover um elemento
- Reset - para repor valores dos elementos
Verificar a Disponibilidade do Gráfico
Verifique sempre a disponibilidade do gráfico antes de o usar:
private void DrawCandlesAndIndicators(ICandleMessage candle, IIndicatorValue longSma, IIndicatorValue shortSma)
{
if (_chart == null) return; // Important check
var data = _chart.CreateData();
data.Group(candle.OpenTime)
.Add(_chartCandleElement, candle)
.Add(_longSmaIndicatorElement, longSma)
.Add(_shortSmaIndicatorElement, shortSma);
_chart.Draw(data);
}
Threads e Sincronização
Evitar Criar Threads Adicionais
No StockSharp, não precisa de criar threads adicionais para processamento de dados. Todos os eventos (dados de mercado, transacções) chegam numa única thread:
// Correto: usando manipuladores de evento padrão
private void ProcessCandle(ICandleMessage candle)
{
// Processar vela na thread principal
var longSmaIsFormedPrev = _longSma.IsFormed;
var ls = _longSma.Process(candle);
var ss = _shortSma.Process(candle);
// ...
}
// Incorreto: criar threads adicionais
private void ProcessCandle(ICandleMessage candle)
{
// NÃO faça isso
Task.Run(() => {
var longSmaIsFormedPrev = _longSma.IsFormed;
// ...
});
}
Evitar Objectos de Sincronização
Como todos os eventos são processados numa única thread, não há necessidade de usar objectos de sincronização:
// Correto: processamento normal sem sincronização
private void ProcessCandle(ICandleMessage candle)
{
var ls = _longSma.Process(candle);
var ss = _shortSma.Process(candle);
// ...
}
// Incorreto: sincronização desnecessária
private readonly object _syncLock = new object(); // Not needed
private void ProcessCandle(ICandleMessage candle)
{
lock (_syncLock) // Not needed
{
var ls = _longSma.Process(candle);
// ...
}
}
Recursos Externos
Usar a Infraestrutura StockSharp
Em vez de aceder directamente a recursos externos (ficheiros, bases de dados, rede), use as capacidades fornecidas pelas plataformas StockSharp:
// Correto: usando mecanismos integrados para salvar dados
protected override void OnStopped()
{
// Os dados são salvos automaticamente pelos parâmetros da estratégia
base.OnStopped();
}
// Incorreto: acesso direto a recursos externos
protected override void OnStopped()
{
// NÃO faça isso
File.WriteAllText("results.txt", $"PnL: {PnL}");
// or this
using (var connection = new SqlConnection("..."))
{
// ...
}
base.OnStopped();
}
Armazenamento de Dados
Para guardar resultados da estratégia, use:
- Parâmetros da estratégia para definições e configuração
- Mecanismos de armazenamento integrados no Designer e no Shell
- Estatísticas para recolher métricas de negociação
Métodos Save e Load
Os métodos Strategy.Save e Strategy.Load foram concebidos especificamente para guardar dados adicionais da estratégia que não são definições ou parâmetros. Este é o local ideal para guardar dados necessários para restaurar o estado da estratégia:
public override void Save(SettingsStorage settings)
{
base.Save(settings); // First save strategy parameters
// Então salvar dados personalizados
settings.SetValue("CustomState", _customState);
settings.SetValue("LastSignalTime", _lastSignalTime);
}
public override void Load(SettingsStorage settings)
{
base.Load(settings); // First load strategy parameters
// Então carregar dados personalizados
if (settings.Contains("CustomState"))
_customState = settings.GetValue<string>("CustomState");
if (settings.Contains("LastSignalTime"))
_lastSignalTime = settings.GetValue<DateTimeOffset>("LastSignalTime");
}
No entanto, os principais parâmetros configuráveis devem continuar a ser implementados através de StrategyParam<T>, como descrito acima, pois isso garantirá a sua apresentação automática na interface de utilizador.
Subscrição de Dados de Mercado
Usar Regras em Vez de Subscrição Directa
Para processar dados de mercado, recomenda-se usar o Modelo de Eventos e regras:
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_shortSma = new SimpleMovingAverage { Length = ShortSmaLength };
_longSma = new SimpleMovingAverage { Length = LongSmaLength };
Indicators.Add(_shortSma);
Indicators.Add(_longSma);
var subscription = new Subscription(Série, Security);
// Correto: usando regras para processamento de dados
Connector
.WhenCandlesFinished(subscription)
.Do(ProcessCandle)
.Apply(this);
Subscribe(subscription);
}
As regras têm várias vantagens importantes sobre processadores de eventos normais:
Cancelamento automático da subscrição - as regras cancelam automaticamente a subscrição dos eventos quando a estratégia pára ou quando já não são necessárias. Não precisa de gerir subscrições manualmente.
API de alto nível - as regras fornecem uma interface mais compreensível e conveniente do que processadores de eventos padrão. Por exemplo,
WhenCandlesFinishedé muito mais claro do que subscrever o eventoCandleReceivedcom uma verificação posterior do estado da vela.Combinação de condições - as regras podem ser combinadas usando operadores como
And,Ore outros, criando condições de activação complexas:
// Exemplo de combinação de regras
var tickSub = new Subscription(DataType.Ticks, Security);
tickSub
.WhenTickTradeReceived(this)
.And(Portfolio.WhenChanged(Connector))
.Do(() => {
// Código executado apenas quando há uma nova negociação
// e o saldo da carteira muda
})
.Apply(this);
Subscribe(tickSub);
- Gestão do ciclo de vida - as regras podem ser tornadas de utilização única (
Once()), ter condições de cancelamento definidas (Until()), adicionar acções diferidas, etc.
Exemplo de Estratégia Compatível
Abaixo está um exemplo de uma estratégia que segue todas as recomendações e funcionará correctamente em todas as plataformas StockSharp:
public class SmaStrategy : Strategy
{
private readonly StrategyParam<DataType> _series;
private readonly StrategyParam<int> _longSmaLength;
private readonly StrategyParam<int> _shortSmaLength;
public DataType Série
{
get => _series.Value;
set => _series.Value = value;
}
public int LongSmaLength
{
get => _longSmaLength.Value;
set => _longSmaLength.Value = value;
}
public int ShortSmaLength
{
get => _shortSmaLength.Value;
set => _shortSmaLength.Value = value;
}
private SimpleMovingAverage _longSma;
private SimpleMovingAverage _shortSma;
private IChart _chart;
private IChartCandleElement _chartCandleElement;
private IChartIndicatorElement _longSmaIndicatorElement;
private IChartIndicatorElement _shortSmaIndicatorElement;
public SmaStrategy()
{
_longSmaLength = Param(nameof(LongSmaLength), 80)
.SetDisplay("Período da SMA longa", string.Empty, "Configurações básicas")
.SetCanOptimize(true);
_shortSmaLength = Param(nameof(ShortSmaLength), 30)
.SetDisplay("Período da SMA curta", string.Empty, "Configurações básicas")
.SetCanOptimize(true);
_series = Param(nameof(Série), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Série", string.Empty, "Configurações básicas");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_longSma = new SimpleMovingAverage { Length = LongSmaLength };
_shortSma = new SimpleMovingAverage { Length = ShortSmaLength };
Indicators.Add(_shortSma);
Indicators.Add(_longSma);
// Inicializar gráfico se disponível
_chart = GetChart();
if (_chart != null)
InitChart();
var subscription = new Subscription(Série, Security);
Connector
.WhenCandlesFinished(subscription)
.Do(ProcessCandle)
.Apply(this);
Subscribe(subscription);
}
private void InitChart()
{
_chart.ClearAreas();
var area = _chart.AddArea();
_chartCandleElement = area.AddCandles();
_longSmaIndicatorElement = area.AddIndicator(_longSma);
_longSmaIndicatorElement.Color = System.Drawing.Color.Brown;
_longSmaIndicatorElement.DrawStyle = DrawStyles.Line;
_shortSmaIndicatorElement = area.AddIndicator(_shortSma);
_shortSmaIndicatorElement.Color = System.Drawing.Color.Blue;
_shortSmaIndicatorElement.DrawStyle = DrawStyles.Line;
}
private void ProcessCandle(ICandleMessage candle)
{
var ls = _longSma.Process(candle);
var ss = _shortSma.Process(candle);
// Desenhar no gráfico se disponível
if (_chart != null)
{
var data = _chart.CreateData();
data.Group(candle.OpenTime)
.Add(_chartCandleElement, candle)
.Add(_longSmaIndicatorElement, ls)
.Add(_shortSmaIndicatorElement, ss);
_chart.Draw(data);
}
if (!_longSma.IsFormed)
return;
var isShortLessCurrent = _shortSma.GetCurrentValue() < _longSma.GetCurrentValue();
var isShortLessPrev = _shortSma.GetValue(1) < _longSma.GetValue(1);
if (isShortLessCurrent == isShortLessPrev)
return;
// Lógica de negociação
var volume = Volume + Math.Abs(Position);
if (isShortLessCurrent)
SellMarket(volume);
else
BuyMarket(volume);
}
}