La estrategia BladeRunner es una traducción del asesor experto de MetaTrader que combina rupturas de fractales con confirmación de tendencia y momentum. El port de StockSharp mantiene la estructura multi-timeframe del script original analizando tres fuentes de velas diferentes: una serie primaria para la ejecución de operaciones, una serie de marco temporal superior para el filtro de momentum, y una serie lenta para el filtro de tendencia MACD. Las órdenes se abren con escala configurable, stop-loss y distancias de take-profit expresadas en pasos de precio.
Lógica de trading
Filtro de ruptura de fractal – la estrategia escanea velas completadas en busca de patrones de fractal de Bill Williams. Se acepta un fractal alcista (superior) cuando la vela formada dos barras antes hace un nuevo máximo de swing y la barra de confirmación abre por debajo del precio del fractal y la LWMA de 20 períodos del precio típico. Los fractales bajistas aplican las reglas simétricas.
Confirmación de tendencia – las medias móviles ponderadas linealmente (LWMA) rápida y lenta calculadas en la serie de velas primaria definen la tendencia subyacente. Los longs requieren que la LWMA rápida esté por encima de la lenta, mientras que los shorts requieren la alineación opuesta.
Filtro de momentum – un oscilador de momentum calculado en la corriente de velas de marco temporal superior debe desviarse de 100 al menos por el umbral configurado en cualquiera de las tres últimas observaciones. Esto reproduce las verificaciones de spike de momentum de la versión MQL.
Filtro MACD – un MACD calculado en el marco temporal lento debe tener su línea principal por encima (long) o por debajo (short) de la línea de señal, reflejando el filtro mensual utilizado por el asesor experto.
Confirmación de ruptura – el cierre de la vela primaria más reciente debe romper más allá del nivel fractal almacenado antes de que se envíe la orden.
Cuando todos los filtros se alinean, la estrategia abre una posición de mercado usando el tamaño de lote configurado. La exposición existente en la dirección opuesta se cierra antes de revertir. Se permiten entradas adicionales hasta que se alcanza el número máximo de operaciones de escala.
Detalles de implementación
Tres suscripciones de velas se crean a través de la API de alto nivel. Cada fuente se vincula directamente a los indicadores requeridos sin añadirlos a la colección global de indicadores.
Las LWMAs operan sobre el precio típico (HLC/3) para coincidir con la implementación MQL. El MACD también consume precios típicos.
La detección de fractales almacena una ventana deslizante de velas completadas y valores de filtro asociados. Solo se mantiene la dirección de fractal validada más reciente, lo que evita señales duplicadas en la misma estructura.
El historial de momentum se mantiene como un array de tamaño fijo, evitando asignaciones dinámicas mientras se reproduce el look-back del EA original.
El dimensionamiento de órdenes respeta las restricciones de intercambio a través de ajustes de paso de volumen, volumen mínimo y máximo.
El helper integrado StartProtection aplica distancias de stop-loss y take-profit expresadas en pasos de precio, coincidiendo con los valores de pip fijos de MetaTrader.
Parámetros
Nombre
Descripción
Por defecto
CandleType
Serie de velas primaria utilizada para la generación de señales.
Velas de 15 minutos
MomentumCandleType
Serie de marco temporal superior para el filtro de momentum.
Velas de 1 hora
MacdCandleType
Serie de velas utilizada por el filtro de tendencia MACD.
Velas diarias
FastMaPeriod
Longitud de la LWMA rápida.
6
SlowMaPeriod
Longitud de la LWMA lenta.
85
FilterMaPeriod
LWMA utilizada para validar las rupturas de fractal.
20
MomentumPeriod
Período de promediado del indicador de momentum.
14
MomentumThreshold
Desviación absoluta mínima del momentum desde 100.
0.3
FractalLookback
Número de velas retenidas para el análisis de fractales.
200
MaxTrades
Número máximo de órdenes de escala por dirección.
3
OrderVolume
Volumen base para cada orden de mercado.
1 contrato
TakeProfitSteps
Distancia de take-profit expresada en pasos de precio.
50
StopLossSteps
Distancia de stop-loss expresada en pasos de precio.
20
Gestión de riesgos
Los niveles de stop-loss y take-profit se adjuntan automáticamente a cada posición a través de StartProtection.
La estrategia siempre cierra la exposición opuesta antes de abrir operaciones en la nueva dirección para evitar situaciones de cobertura.
El volumen se ajusta a las restricciones del instrumento antes de colocar órdenes. El límite MaxTrades limita los pasos de escala totales por dirección.
Diferencias del EA original
Las utilidades de stop de equity, trailing stop y break-even de MetaTrader no están implementadas. El control de riesgo de StockSharp se puede agregar externamente si es necesario.
La lógica de trailing basada en dinero y las notificaciones push se omiten porque StockSharp proporciona flujos de trabajo de notificación alternativos.
El filtro MACD usa velas diarias por defecto en lugar de barras mensuales. Ajuste MacdCandleType a un marco temporal mensual cuando sea compatible con la fuente de datos conectada.
La validación de fractales depende de la última vela de confirmación almacenada en la ventana deslizante. Esto produce el mismo efecto práctico que el bucle en el script MQL mientras evita escaneos repetidos.
Notas de uso
Configure los tipos de velas para que coincidan con los instrumentos y marcos temporales compatibles con su fuente de datos.
Alinee OrderVolume, TakeProfitSteps y StopLossSteps con el tamaño de tick y el paso de volumen del instrumento.
Ajuste MomentumThreshold y las longitudes de LWMA durante pruebas walk-forward para adaptar la sensibilidad de ruptura a diferentes mercados.
Habilite el dibujo en el gráfico para visualizar las tres LWMAs y verificar que las rupturas de fractal se alinean con los filtros de tendencia antes de operar en vivo.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class BladeRunnerStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public BladeRunnerStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class blade_runner_strategy(Strategy):
def __init__(self):
super(blade_runner_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(blade_runner_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(blade_runner_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return blade_runner_strategy()