Conversión del asesor experto MetaTrader 5 Spasm (edición de barabashkakvn) a la API de alto nivel de StockSharp.
Opera rupturas de un canal adaptativo dimensionado por la volatilidad reciente y alterna entre regímenes alcistas y bajistas.
Funciona en cualquier instrumento y marco temporal suministrado por el parámetro CandleType, con velas de una hora por defecto.
Preparación de datos
Se suscribe a la serie de velas definida por CandleType para el instrumento de la estrategia.
Construye un estimador de volatilidad a partir de las últimas VolatilityPeriod velas:
Cuando UseWeightedVolatility está desactivado, el estimador es una media móvil simple del rango por vela.
Cuando UseWeightedVolatility está activado, el estimador se convierte en una media móvil ponderada linealmente que enfatiza las barras más recientes.
El rango por vela es High - Low por defecto. Si UseOpenCloseRange está activado, se utiliza la diferencia absoluta entre apertura y cierre, reproduciendo el cambio de modo del EA original.
El rango promedio bruto se convierte en pasos de precio y se multiplica por VolatilityMultiplier. El resultado se trunca a un número entero de pasos y finalmente se multiplica por el tamaño del tick del instrumento para formar el umbral de ruptura.
Durante las primeras VolatilityPeriod * 3 velas terminadas, la estrategia recopila el máximo más alto y el mínimo más bajo junto con sus marcas de tiempo para decidir qué oscilación es más reciente. Esa información inicializa el estado de tendencia inicial y los precios de referencia una vez que se procesan suficientes velas.
Parámetros
Nombre
Valor predeterminado
Descripción
Volume
1
Volumen de la orden aplicado a cada entrada de mercado.
VolatilityMultiplier
5
Multiplicador aplicado a la volatilidad promediada para dimensionar el buffer de ruptura.
VolatilityPeriod
24
Número de velas utilizadas para la rutina de promedio de volatilidad y el análisis inicial de oscilaciones.
UseWeightedVolatility
false
Cambia el promedio de volatilidad de media móvil simple a media móvil ponderada linealmente.
UseOpenCloseRange
false
Usa el movimiento absoluto apertura-cierre como fuente de volatilidad en lugar del rango máximo-mínimo.
StopLossFraction
0.5
Fracción del umbral de volatilidad empleado para calcular la distancia del stop-loss. Se impone un mínimo de tres pasos de precio.
CandleType
marco temporal de 1 hora
Tipo de vela y marco temporal usado para todos los cálculos.
Lógica de trading
Seguimiento de tendencia
La estrategia mantiene _highestPrice y _lowestPrice como anclas de la oscilación actual.
Siempre que el precio avance más del umbral actual por encima del máximo almacenado, _highestPrice se actualiza al máximo de la vela. Análogamente, una caída más allá del umbral actualiza _lowestPrice al mínimo de la vela.
El booleano _isTrendUp almacena si la estrategia está actualmente en el régimen alcista (true) o bajista (false).
Reglas de entrada
Cuando _isTrendUp es false (régimen bajista) y el cierre de la vela supera _lowestPrice + threshold, la estrategia cambia al modo alcista y envía BuyMarket(Volume + Math.Abs(Position)). Esto cierra cualquier exposición corta y abre una posición larga igual a Volume.
Cuando _isTrendUp es true (régimen alcista) y el cierre de la vela cae por debajo de _highestPrice - threshold, la estrategia cambia al modo bajista y envía SellMarket(Volume + Math.Abs(Position)) para revertir a una posición corta.
Gestión de stops
Al entrar en una posición larga, el precio de stop se coloca en entry - max(threshold * StopLossFraction, 3 * priceStep).
Al entrar en una posición corta, el precio de stop se coloca en entry + max(threshold * StopLossFraction, 3 * priceStep).
Si el mínimo de una vela alcanza el stop largo o el máximo alcanza el stop corto, la posición correspondiente se cierra enviando una orden de mercado. Los stops se deshabilitan cuando StopLossFraction se establece en cero.
Controles de riesgo e infraestructura
StartProtection() se llama durante el inicio para que las protecciones de riesgo integradas se activen tan pronto como comience la estrategia.
La estrategia solo reacciona a las velas terminadas para evitar el ruido intrabarra, reflejando el recalculado barra a barra del EA original.
Todos los comentarios y nombres de parámetros se mantienen en inglés según los requisitos.
Diferencias con la versión MQL
El EA original recalculaba los umbrales en cada tick. En este puerto, la lógica se ejecuta en velas completadas porque la API de alto nivel opera con suscripciones de velas.
La aplicación del stop-loss ocurre en datos de velas. Los golpes de stop intrabarra que se revierten dentro de la misma barra se evalúan por lo tanto en los límites de la vela.
Las propiedades del símbolo como spread y niveles de stop específicos del bróker no están disponibles en la misma forma en StockSharp. Se usa un mínimo conservador de tres pasos de precio cuando la distancia de stop calculada es demasiado pequeña, reproduciendo el fallback de la implementación MetaTrader.
Notas de uso
Asegúrese de que el instrumento de la estrategia expone un PriceStep válido. Si no se proporciona, el código establece el paso en 1 por defecto.
La estrategia es agnóstica en cuanto a la dirección y puede usarse en instrumentos spot, futuros o CFD siempre que el feed entregue las velas configuradas.
No se define ningún objetivo de take-profit; las salidas ocurren solo mediante cambios de régimen o activación 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()