Esta estrategia replica las ideas centrales del asesor experto MetaTrader 4 "Gann Line" (ID de origen 24877) usando la API de alto nivel de StockSharp. Mantiene los mismos filtros de tendencia, Momentum y MACD a largo plazo mientras expresa todas las herramientas de gestión monetaria en pasos de precio, lo que hace que la lógica sea independiente del broker.
Lógica de trading
Filtro de tendencia (temporalidad principal)
Se aplican dos medias móviles ponderadas linealmente (LWMA) al precio típico de la vela (high + low + close) / 3.
Un sesgo largo requiere que la LWMA rápida cierre por encima de la LWMA lenta; un sesgo corto requiere lo contrario.
Confirmación de Momentum (temporalidad superior)
Un oscilador Momentum calculado en una temporalidad superior configurable verifica cuánto se desvía el oscilador del nivel de equilibrio (100).
Al menos uno de los últimos tres valores de Momentum terminados debe superar el umbral de desviación configurado antes de que se permita cualquier trade.
Filtro MACD lento (temporalidad muy alta)
Un filtro MACD calculado en una temporalidad lenta (mensual por defecto) debe confirmar la dirección: la línea principal MACD por encima de la señal para largos, por debajo para cortos.
Gestión de posiciones
Los objetivos fijos de stop-loss y take-profit se convierten de pasos de precio a precios absolutos cuando se abre un trade.
La lógica de punto de equilibrio opcional mueve el stop al precio de entrada más un offset una vez que el trade ha avanzado una cantidad determinada de pasos en beneficio.
La lógica de trailing opcional desplaza el stop detrás del máximo más alto (para largos) o del mínimo más bajo (para cortos) una vez que el precio ha recorrido un número configurable de pasos.
Gestión de riesgos
Todas las distancias (stop-loss, take-profit, punto de equilibrio y trailing) se ingresan en pasos de precio. El helper los convierte a precios usando el PriceStep del instrumento.
La estrategia trabaja con la propiedad base Volume. Si es cero, se usa un contrato/lote por defecto.
Solo se gestiona una posición neta única. Las señales opuestas cierran el trade actual antes de abrir uno nuevo.
Diferencias respecto a la versión MQL4
El asesor experto original dependía de una línea de tendencia Gann dibujada manualmente. StockSharp no expone objetos del gráfico, por lo que el puerto reemplaza esa verificación con la confirmación de pendiente LWMA.
El trailing basado en dinero, los cierres parciales y las verificaciones de capital de toda la cuenta del script se simplifican en cálculos deterministas basados en pasos.
Las notificaciones (alertas, correos electrónicos, pushes móviles) no se generan porque las estrategias de StockSharp típicamente registran en la salida de la plataforma.
Parámetros
Nombre
Descripción
Fast LWMA
Longitud de la LWMA rápida utilizada para el filtro de tendencia.
Slow LWMA
Longitud de la LWMA lenta utilizada para el filtro de tendencia.
Momentum Period
Retrospectiva del oscilador Momentum en la temporalidad secundaria.
Momentum Threshold
Desviación mínima de 100 requerida por cualquiera de los últimos tres valores de Momentum.
MACD Fast / Slow / Signal
Longitudes EMA del filtro MACD lento.
Take Profit (steps)
Distancia de take-profit en pasos de precio.
Stop Loss (steps)
Distancia de stop-loss en pasos de precio.
Use Trailing, Trail Activation, Trail Distance
Habilitar trailing, beneficio necesario antes de que comience el trailing, y distancia entre el extremo de precio y el stop de trailing.
Use BreakEven, BreakEven Activation, BreakEven Offset
Habilitar punto de equilibrio, beneficio requerido antes de mover el stop, y beneficio adicional bloqueado después.
Primary Timeframe
Tipo de vela utilizado por el cruce LWMA.
Momentum Timeframe
Tipo de vela enviado al oscilador Momentum.
MACD Timeframe
Tipo de vela enviado al filtro MACD lento.
Consejos de uso
Seleccione un instrumento y establezca el Primary Timeframe deseado. Las otras temporalidades tienen por defecto 1 hora (Momentum) y 30 días (MACD), pero se pueden personalizar para reproducir el mapeo de coeficientes original.
Configure Volume y los parámetros de riesgo basados en pasos para que coincidan con las especificaciones del contrato de su broker.
Ejecute la estrategia en Designer o a través de código. Monitoree el registro para verificar que los filtros, los movimientos de punto de equilibrio y los ajustes de trailing aparezcan como se espera.
Optimice los umbrales de Momentum y MACD para adaptar la lógica portada a diferentes mercados o temporalidades.
Mejoras adicionales
Integrar un stop global basado en capital similar al script original.
Reemplazar el filtro de pendiente LWMA con una línea de tendencia personalizada dibujada en el gráfico una vez que StockSharp exponga eventos de objetos.
Agregar toma de beneficios parcial para imitar el comportamiento de múltiples objetivos de la versión MQL4.
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 GannLineStrategy : 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 GannLineStrategy()
{
_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 gann_line_strategy(Strategy):
def __init__(self):
super(gann_line_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(gann_line_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(gann_line_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 gann_line_strategy()