Esta estrategia de StockSharp reproduce el experto MetaTrader GANN_FAN usando la API de alto nivel. Combina filtros de tendencia de medias móviles ponderadas linealmente con confirmación de Momentum, una puerta de dirección MACD y una reconstrucción basada en fractales del Gann Fan para determinar el sesgo alcista o bajista. La gestión de riesgos refleja el robot original con entradas apiladas de estilo martingala, stops fijos, protección de trailing y movimientos de punto de equilibrio opcionales.
Lógica de trading
Filtro de tendencia – Dos medias móviles ponderadas linealmente (LWMA) construidas sobre el precio típico (H+L+C)/3 definen la tendencia rápida y lenta. Los trades largos requieren que la LWMA rápida permanezca por encima de la LWMA lenta; los trades cortos necesitan el cruce inverso.
Confirmación de Momentum – La estrategia calcula el oscilador de Momentum clásico como 100 * Close / Close(n) y evalúa la desviación del nivel neutro 100 durante los últimos tres velas cerradas. Al menos una desviación debe superar el umbral configurado para confirmar la fortaleza en la dirección del trade.
Dirección MACD – Una señal MACD configurable (períodos de EMA rápida, lenta y de señal) debe estar de acuerdo con la tendencia. Las entradas largas requieren que la línea MACD sea mayor que la línea de señal, mientras que los cortos necesitan que la línea MACD permanezca por debajo de la línea de señal.
Orientación del Gann Fan – Los fractales confirmados de Bill Williams reconstruyen los rayos del Gann Fan alcista y bajista. Los dos fractales descendentes más recientes forman el rayo alcista; su pendiente debe ser positiva para permitir largos. Los dos últimos fractales ascendentes definen el rayo bajista; su pendiente debe ser negativa para autorizar ventas en corto.
Apilamiento de posiciones – Cuando llega una nueva señal, la estrategia puede añadir a una posición existente hasta el máximo configurado. Cada orden adicional aumenta el volumen multiplicando el lote base por el exponente de lote, emulando el dimensionamiento de martingala utilizado en la versión MQL.
Gestión de riesgos
Stop-loss y take-profit fijos – Expresados en pasos de precio del instrumento, convertidos automáticamente por la estrategia usando Security.PriceStep.
Control de punto de equilibrio – Cuando está habilitado, una vez que el beneficio alcanza la distancia de disparo, el stop se adelanta a la entrada más/menos el offset configurado.
Stop de seguimiento – Se activa después de alcanzar la distancia de disparo. El stop puede seguir al mercado ya sea por una distancia fija del cierre o bloqueando el valor más bajo (para largos) / más alto (para cortos) de las velas más recientes más un factor de margen.
Interruptor de salida forzada – Establecer Force Exit en true liquida inmediatamente cualquier exposición abierta en la próxima vela terminada.
Parámetros
Parámetro
Descripción
Volume
Tamaño base de la orden utilizado para la primera entrada.
Fast LWMA / Slow LWMA
Períodos de las medias móviles ponderadas linealmente utilizadas para el filtro de tendencia.
Momentum Period / Threshold
Retrospectiva del cálculo de Momentum y desviación mínima de 100 requerida para operar.
MACD Fast / Slow / Signal
Períodos de EMA para el filtro de confirmación MACD.
Fractal History
Número máximo de puntos de fractal confirmados almacenados para construir los rayos del Gann Fan.
Max Trades
Número máximo de entradas apiladas permitidas en una sola dirección.
Lot Exponent
Multiplicador aplicado al volumen base para cada entrada adicional.
Stop Loss / Take Profit
Distancias de protección en pasos de precio.
Enable Trailing
Habilita la gestión del stop de seguimiento.
Trail Trigger / Distance / Padding
Disparador de beneficio, distancia de trailing y margen extra (en pasos de precio) utilizado al hacer trailing mediante extremos de vela.
Use Candle Trail
Habilita el trailing basado en velas además del trailing de distancia fija.
Trailing Candles
Número de velas terminadas recientes consideradas al calcular los niveles de trailing basados en velas.
Enable Break-even
Activa o desactiva la lógica de punto de equilibrio.
Break-even Trigger / Offset
Disparador de beneficio y offset (en pasos de precio) para mover el stop al punto de equilibrio.
Use Gann Filter
Impone la orientación alcista/bajista del Gann Fan para las entradas.
Force Exit
Obliga a la estrategia a cerrar todas las posiciones en la siguiente barra.
Candle Type
Series de velas utilizadas para cálculos y generación de órdenes.
Notas
Todos los cálculos de indicadores funcionan exclusivamente en velas terminadas proporcionadas por SubscribeCandles y Bind para cumplir con las mejores prácticas de la API de alto nivel de StockSharp.
Las distancias de trailing y punto de equilibrio se adaptan automáticamente al tamaño del tick del instrumento. Cuando PriceStep no está disponible, las características de protección permanecen inactivas hasta que el conector lo proporcione.
La estrategia mantiene estados separados para posiciones largas y cortas, asegurando que los niveles de trailing y punto de equilibrio se reinicien cuando la exposición cambia de dirección.
Para imitar el experto MetaTrader de cerca, las alertas, notificaciones y objetos de gráfico explícitos del código original son reemplazados por la reconstrucción nativa del Gann Fan de StockSharp usando fractales.
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 GannFanStrategy : 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 GannFanStrategy()
{
_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_fan_strategy(Strategy):
def __init__(self):
super(gann_fan_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_fan_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_fan_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_fan_strategy()