La Estrategia de Spreader es un enfoque de trading en pares inspirado en el asesor experto original de MetaTrader "Spreader". La estrategia monitoriza dos instrumentos con correlación positiva y busca beneficiarse de divergencias a corto plazo manteniendo un perfil neutral al mercado. Una vez que la posición combinada alcanza el objetivo monetario deseado, la estrategia cierra ambas patas y espera la siguiente configuración.
El algoritmo está diseñado para velas de un minuto por defecto, replicando el comportamiento del EA original, pero el marco temporal puede ajustarse cuando la estrategia se carga en Designer, Shell o el ejecutor de la API.
Lógica de trading
Preparación de datos
Se suscribe a las velas del instrumento principal (el asignado a la estrategia) y del instrumento secundario.
Almacena los últimos 2 * ShiftLength + 1 valores de cierre para cada instrumento. La longitud de desplazamiento predeterminada es 30 barras.
Solo reacciona a velas completadas y requiere que ambos instrumentos produzcan una barra con el mismo tiempo de apertura.
Filtro de tendencia
Calcula los cambios de precio entre el cierre actual y el cierre ShiftLength barras atrás, así como el cambio entre las muestras medias y más antiguas para ambos instrumentos.
Si los dos cambios de cualquiera de los instrumentos tienen el mismo signo, la estrategia lo interpreta como una tendencia persistente y omite la operación.
Verificación de correlación
Asegura que el signo del último cambio en ambos instrumentos sea idéntico. Si el signo difiere, la correlación se considera negativa y no se abre ningún spread.
Alineación de volatilidad
Calcula la magnitud absoluta de las oscilaciones recientes (a para la pata principal, b para la secundaria) y usa su ratio para escalar el volumen de cobertura. Los ratios fuera del rango [0.3, 3] son rechazados porque indican comportamiento inestable.
Entrada
Elige la dirección de la pata principal comparando las oscilaciones normalizadas: si el movimiento principal es más fuerte, la estrategia compra el instrumento principal y vende la pata secundaria; de lo contrario, vende la pata principal y compra la secundaria.
Las órdenes se envían con ejecución de mercado y los volúmenes se normalizan para respetar el paso de lote y los límites mínimos y máximos de cada instrumento.
Gestión de posiciones
Si solo la pata secundaria está abierta (por ejemplo, debido a problemas de conectividad), la estrategia abre la pata principal faltante en la dirección opuesta para restaurar la cobertura.
Si solo queda la pata principal, se cierra inmediatamente para evitar exposición direccional.
Cuando ambas patas están presentes, la estrategia monitoriza el beneficio flotante combinado y cierra ambas posiciones una vez alcanzado el objetivo monetario configurado.
Verificaciones de seguridad
El trading se desactiva cuando el tamaño del contrato (multiplicador) de los dos instrumentos difiere, ya que el EA original asume especificaciones contractuales iguales.
Todas las solicitudes de trading se ignoran hasta que la estrategia esté conectada, sincronizada y autorizada para operar por el entorno de alojamiento (IsFormedAndOnlineAndAllowTrading).
Parámetros
Parámetro
Descripción
SecondSecurity
Instrumento que forma la pata de cobertura del spread. Este parámetro es obligatorio.
PrimaryVolume
Volumen de orden base para el instrumento principal. El volumen secundario se escala usando el ratio de oscilación.
TargetProfit
Beneficio absoluto, expresado en la moneda de la cuenta, tras el cual se cierran ambas patas.
ShiftLength
Número de barras usadas al comparar oscilaciones recientes. La estrategia usa 2 * ShiftLength + 1 velas de cada instrumento.
CandleType
Series de velas usadas para el análisis. Por defecto marco temporal de 1 minuto.
Consejos
La estrategia funciona mejor en instrumentos con correlación positiva estable y perfiles de volatilidad similares (por ejemplo, pares de divisas muy relacionados o futuros sobre índices).
Las especificaciones del contrato deben estar alineadas (tamaño del tick, paso de lote, margen); de lo contrario, la normalización del volumen puede reducir significativamente el tamaño de las órdenes.
Dado que la estrategia depende de datos de velas, asegúrese de que ambos instrumentos reciban actualizaciones de barra sincronizadas del proveedor de datos.
Requisitos
Dos instrumentos líquidos con correlación positiva.
Acceso a datos de mercado y permisos de trading para ambos instrumentos a través de los conectores de StockSharp.
Cartera asignada a la instancia de la estrategia.
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 SpreaderStrategy : 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 SpreaderStrategy()
{
_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 spreader_strategy(Strategy):
def __init__(self):
super(spreader_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(spreader_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(spreader_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 spreader_strategy()