Conversão do assessor especialista MetaTrader 5 Spasm (edição de barabashkakvn) para a API de alto nível do StockSharp.
Opera rompimentos de um canal adaptativo dimensionado pela volatilidade recente e alterna entre regimes de alta e baixa.
Funciona em qualquer instrumento e período fornecido pelo parâmetro CandleType, com padrão de velas de uma hora.
Preparação de dados
Subscreve a série de velas definida por CandleType para o instrumento da estratégia.
Constrói um estimador de volatilidade a partir das últimas VolatilityPeriod velas:
Quando UseWeightedVolatility está desabilitado, o estimador é uma média móvel simples do intervalo por vela.
Quando UseWeightedVolatility está habilitado, o estimador torna-se uma média móvel linearmente ponderada que enfatiza as barras mais recentes.
O intervalo por vela é High - Low por padrão. Se UseOpenCloseRange estiver habilitado, é usada a diferença absoluta entre abertura e fechamento, reproduzindo a troca de modo do EA original.
O intervalo médio bruto é convertido em passos de preço e multiplicado por VolatilityMultiplier. O resultado é truncado para um número inteiro de passos e finalmente multiplicado pelo tamanho do tick do instrumento para formar o limiar de rompimento.
Durante as primeiras VolatilityPeriod * 3 velas terminadas, a estratégia coleta a máxima mais alta e a mínima mais baixa junto com seus timestamps para decidir qual oscilação é mais recente. Essa informação inicializa o estado de tendência inicial e os preços de referência uma vez que velas suficientes sejam processadas.
Parâmetros
Nome
Padrão
Descrição
Volume
1
Volume da ordem aplicado a cada entrada de mercado.
VolatilityMultiplier
5
Multiplicador aplicado à volatilidade média para dimensionar o buffer de rompimento.
VolatilityPeriod
24
Número de velas usadas para a rotina de média de volatilidade e a verificação inicial de oscilações.
UseWeightedVolatility
false
Muda a média de volatilidade de média móvel simples para média móvel linearmente ponderada.
UseOpenCloseRange
false
Usa o movimento absoluto abertura-fechamento como fonte de volatilidade em vez do intervalo máxima-mínima.
StopLossFraction
0.5
Fração do limiar de volatilidade empregado para calcular a distância do stop-loss. Um mínimo de três passos de preço é aplicado.
CandleType
período de 1 hora
Tipo de vela e período usado para todos os cálculos.
Lógica de trading
Rastreamento de tendência
A estratégia mantém _highestPrice e _lowestPrice como âncoras da oscilação atual.
Sempre que o preço avança mais do que o limiar atual acima da máxima armazenada, _highestPrice é atualizado para a máxima da vela. Analogamente, uma queda além do limiar atualiza _lowestPrice para a mínima da vela.
O booleano _isTrendUp armazena se a estratégia está atualmente no regime de alta (true) ou de baixa (false).
Regras de entrada
Quando _isTrendUp é false (regime de baixa) e o fechamento da vela excede _lowestPrice + threshold, a estratégia muda para o modo de alta e envia BuyMarket(Volume + Math.Abs(Position)). Isso fecha qualquer exposição a vendido e abre uma posição comprada igual a Volume.
Quando _isTrendUp é true (regime de alta) e o fechamento da vela cai abaixo de _highestPrice - threshold, a estratégia muda para o modo de baixa e envia SellMarket(Volume + Math.Abs(Position)) para reverter a uma posição vendida.
Gestão de stops
Ao entrar em uma posição comprada, o preço de stop é colocado em entry - max(threshold * StopLossFraction, 3 * priceStep).
Ao entrar em uma posição vendida, o preço de stop é colocado em entry + max(threshold * StopLossFraction, 3 * priceStep).
Se a mínima de uma vela atingir o stop comprado ou a máxima atingir o stop vendido, a posição correspondente é fechada enviando uma ordem de mercado. Os stops são desabilitados quando StopLossFraction é definido como zero.
Controles de risco e infraestrutura
StartProtection() é chamado durante a inicialização para que as proteções de risco integradas se tornem ativas assim que a estratégia iniciar.
A estratégia reage apenas a velas terminadas para evitar ruído intrabarra, espelhando o recálculo barra a barra do EA original.
Todos os comentários e nomes de parâmetros são mantidos em inglês conforme os requisitos.
Diferenças da versão MQL
O EA original recalculava limiares a cada tick. Neste porto, a lógica é executada em velas completadas porque a API de alto nível opera com subscrições de velas.
A aplicação do stop-loss ocorre em dados de velas. Acionamentos de stop intrabarra que se revertam dentro da mesma barra são portanto avaliados nos limites da vela.
Propriedades do símbolo como spread e níveis de stop específicos do broker não estão disponíveis na mesma forma no StockSharp. Um mínimo conservador de três passos de preço é usado quando a distância de stop calculada é muito pequena, reproduzindo o fallback da implementação MetaTrader.
Notas de uso
Certifique-se de que o instrumento da estratégia expõe um PriceStep válido. Se não for fornecido, o código define o passo como 1 por padrão.
A estratégia é agnóstica em relação à direção e pode ser usada em instrumentos spot, futuros ou CFD, desde que o feed entregue as velas configuradas.
Nenhum alvo de take-profit é definido; as saídas ocorrem apenas via mudanças de regime ou acionamentos de stop-loss.
namespace StockSharp.Samples.Strategies;
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Volatility breakout strategy converted from the MetaTrader Spasm expert advisor.
/// Tracks directional swings using adaptive thresholds derived from ATR.
/// Buys when price breaks above recent high + ATR*multiplier, sells when price breaks below recent low - ATR*multiplier.
/// </summary>
public class SpasmStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _volatilityMultiplier;
private decimal _highestPrice;
private decimal _lowestPrice;
private decimal _prevRange;
private bool _initialized;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal VolatilityMultiplier
{
get => _volatilityMultiplier.Value;
set => _volatilityMultiplier.Value = value;
}
public SpasmStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis", "General");
_volatilityMultiplier = Param(nameof(VolatilityMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Volatility Multiplier", "Multiplier applied to ATR for breakout bands", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highestPrice = 0m;
_lowestPrice = decimal.MaxValue;
_prevRange = 0m;
_initialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highestPrice = 0m;
_lowestPrice = decimal.MaxValue;
_prevRange = 0m;
_initialized = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_initialized)
{
_highestPrice = candle.HighPrice;
_lowestPrice = candle.LowPrice;
_prevRange = candle.HighPrice - candle.LowPrice;
_initialized = true;
return;
}
// Update extremes
if (candle.HighPrice > _highestPrice)
_highestPrice = candle.HighPrice;
if (candle.LowPrice < _lowestPrice)
_lowestPrice = candle.LowPrice;
var threshold = _prevRange * VolatilityMultiplier;
if (threshold <= 0)
return;
// Breakout above lowest + threshold => buy
if (candle.ClosePrice > _lowestPrice + threshold && Position <= 0)
{
BuyMarket();
// Reset extremes after entry
_highestPrice = candle.HighPrice;
_lowestPrice = candle.LowPrice;
}
// Breakout below highest - threshold => sell
else if (candle.ClosePrice < _highestPrice - threshold && Position >= 0)
{
SellMarket();
// Reset extremes after entry
_highestPrice = candle.HighPrice;
_lowestPrice = candle.LowPrice;
}
_prevRange = candle.HighPrice - candle.LowPrice;
}
}
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.Strategies import Strategy
class spasm_strategy(Strategy):
def __init__(self):
super(spasm_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for analysis", "General")
self._volatility_multiplier = self.Param("VolatilityMultiplier", 2.0) \
.SetDisplay("Volatility Multiplier", "Multiplier applied to ATR for breakout bands", "Trading")
self._highest_price = 0.0
self._lowest_price = float('inf')
self._prev_range = 0.0
self._initialized = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def VolatilityMultiplier(self):
return self._volatility_multiplier.Value
def OnReseted(self):
super(spasm_strategy, self).OnReseted()
self._highest_price = 0.0
self._lowest_price = float('inf')
self._prev_range = 0.0
self._initialized = False
def OnStarted2(self, time):
super(spasm_strategy, self).OnStarted2(time)
self._highest_price = 0.0
self._lowest_price = float('inf')
self._prev_range = 0.0
self._initialized = False
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(self._on_process) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if not self._initialized:
self._highest_price = high
self._lowest_price = low
self._prev_range = high - low
self._initialized = True
return
if high > self._highest_price:
self._highest_price = high
if low < self._lowest_price:
self._lowest_price = low
threshold = self._prev_range * float(self.VolatilityMultiplier)
if threshold <= 0:
self._prev_range = high - low
return
if close > self._lowest_price + threshold and self.Position <= 0:
self.BuyMarket()
self._highest_price = high
self._lowest_price = low
elif close < self._highest_price - threshold and self.Position >= 0:
self.SellMarket()
self._highest_price = high
self._lowest_price = low
self._prev_range = high - low
def CreateClone(self):
return spasm_strategy()