La estrategia Gandalf PRO es una versión StockSharp del MetaTrader 4 asesor experto Gandalf_PRO. El robot original construye un
filtro de suavizado adaptativo a partir de una media móvil ponderada y un componente de tendencia recursivo. Cuando el precio proyectado se mueve a
al menos 15 pips más allá del precio de mercado actual, el EA entra en esa dirección con un stop-loss distante y una toma de ganancias en el
nivel proyectado. La conversión StockSharp reproduce el mismo filtro y lógica de decisión mientras se basa en la vela de alto nivel.
API para que cada cálculo se realice en barras terminadas.
Lógica comercial
Suscríbase al período de tiempo seleccionado por CandleType (predeterminado: velas de 1 hora) y procese solo velas completadas.
Mantenga un historial continuo de precios de cierre lo suficientemente grande como para cubrir el máximo de CountBuy y CountSell más una barra adicional.
Recree la función MetaTrader Out(): calcule promedios móviles ponderados lineales y simples (usando un desplazamiento de una barra), obtenga el
componentes recursivos s y t con el precio configurado y los factores de tendencia, y obtener el precio proyectado s[1] + t[1].
Para configuraciones largas (EnableBuy):
Compruebe que el precio proyectado esté al menos 15 pips por encima del último cierre (Bid + 15*x*Point en MT4).
Si no hay ninguna posición larga abierta, compre el volumen configurado (consulte BaseVolume y BuyRiskMultiplier).
Almacene el precio proyectado como toma de ganancias y calcule el límite de pérdidas restando BuyStopLossPips convertido en pasos de precio.
Para configuraciones breves (EnableSell):
Exija que el precio proyectado se sitúe al menos 15 pips por debajo del último cierre.
Si no hay ninguna posición corta abierta, venda el volumen configurado (revirtiendo una posición larga existente si es necesario).
Guarde el precio proyectado como toma de ganancias y establezca el límite de pérdidas SellStopLossPips pips por encima del mercado.
Mientras exista una posición, monitoree cada vela terminada:
Salir de posiciones largas si el mínimo de la vela cruza el stop almacenado o el máximo alcanza la toma de ganancias.
Salga de los cortos si el máximo de la vela cruza el stop o el mínimo alcanza el objetivo.
Las salidas utilizan ClosePosition(), lo que aplana la exposición neta en StockSharp.
Parámetros
Nombre
Tipo
Predeterminado
Descripción
EnableBuy
bool
true
Permita que la estrategia abra posiciones largas.
CountBuy
int
24
Longitud del filtro suavizante utilizado para proyecciones largas.
BuyPriceFactor
decimal
0.18
Peso del cierre actual en el filtro recursivo largo.
BuyTrendFactor
decimal
0.18
Ponderación aplicada al término de tendencia al construir la proyección larga.
BuyStopLossPips
int
62
Distancia de stop-loss para posiciones largas, medida en pips.
BuyRiskMultiplier
decimal
0
Multiplicador aplicado a BaseVolume antes de enviar una orden larga (0 mantiene el volumen base).
EnableSell
bool
true
Permita que la estrategia abra posiciones cortas.
CountSell
int
24
Longitud del filtro suavizante utilizado para proyecciones cortas.
SellPriceFactor
decimal
0.18
Peso del cierre actual en el filtro recursivo corto.
SellTrendFactor
decimal
0.18
Ponderación aplicada al término de tendencia al construir la proyección corta.
SellStopLossPips
int
62
Distancia de stop-loss para posiciones cortas, medida en pips.
SellRiskMultiplier
decimal
0
Multiplicador aplicado a BaseVolume antes de enviar una orden corta (0 mantiene el volumen base).
BaseVolume
decimal
1
Tamaño de orden base utilizado cuando ambos multiplicadores de riesgo son cero.
CandleType
DataType
plazo de 1 hora
Serie de velas procesadas por la estrategia.
Diferencias con el original MetaTrader EA
MetaTrader puede mantener entradas de compra y venta independientes simultáneamente. StockSharp utiliza posiciones netas, por lo que el puerto cierra o
invierte una posición existente antes de abrir el lado opuesto.
La función de lote MT4 utilizaba margen libre de cuenta. La conversión expone BaseVolume y dos multiplicadores de riesgo; cuando son cero
el volumen base se usa tal cual; de lo contrario, el volumen simplemente se escala (BaseVolume * RiskMultiplier).
Los niveles de stop-loss y take-profit se ejecutan monitoreando las velas completadas. Por lo tanto, los rellenos intrabarra pueden diferir de MetaTrader
donde las órdenes de protección son gestionadas por el corredor.
El ajuste de cinco dígitos Digits/Point se emula inspeccionando Security.Decimals y Security.PriceStep para convertir pip.
distancias a precios absolutos.
Todos los cálculos de los indicadores se realizan en código administrado sin llamar a iMA; el filtro recursivo se recrea en
CalculateTarget usando los mismos coeficientes que la función MQL.
Notas de uso
Asigne el instrumento deseado a Strategy.Security antes de comenzar. La estrategia genera una excepción si no se adjunta ningún valor.
Configure BaseVolume para que coincida con el tamaño del contrato esperado por su lugar; ajuste los multiplicadores de riesgo solo si desea escalar
la exposición relativa al volumen base.
El historial de velas debe contener al menos max(CountBuy, CountSell) + 1 barras antes de que se pueda generar cualquier operación. Proporcionar suficiente
datos de calentamiento o iniciar la estrategia con velas históricas cargadas.
El búfer de entrada de 15 pips es fijo (al igual que en EA). Aumente CountBuy/CountSell para suavizar la proyección o modificar la
factores de precio/tendencia para que coincidan con el comportamiento observado en MetaTrader.
Debido a que las salidas dependen de los extremos de las velas, habilite un período de tiempo que se adapte a su latencia de ejecución. Los plazos más cortos reaccionarán antes
pero requieren más datos históricos y pueden generar más señales.
Detalles de implementación
Utiliza SubscribeCandles() con Bind(ProcessCandle) para que cada decisión se base en velas finalizadas.
Mantiene una lista compacta de cierres recientes y reconstruye el filtro recursivo s/t a pedido, imitando la rutina Out().
Convierte compensaciones basadas en pips a través del tamaño de tick del instrumento y la precisión decimal para replicar la escala MetaTrader x * Point.
ClosePosition() se invoca cuando se superan los niveles de protección, lo que garantiza que la posición neta se nivele antes de que se realice otra entrada.
considerado.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Gandalf PRO trend-following strategy using adaptive smoothing filter.
/// Opens trades when projected price exceeds a buffer threshold.
/// </summary>
public class GandalfProProjectionStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _filterLength;
private readonly StrategyParam<decimal> _priceFactor;
private readonly StrategyParam<decimal> _trendFactor;
private readonly StrategyParam<int> _atrLength;
private readonly List<decimal> _closeBuffer = new();
private decimal _entryPrice;
public GandalfProProjectionStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_filterLength = Param(nameof(FilterLength), 24)
.SetDisplay("Filter Length", "Smoothing filter length.", "Filter");
_priceFactor = Param(nameof(PriceFactor), 0.18m)
.SetDisplay("Price Factor", "Close price weight in filter.", "Filter");
_trendFactor = Param(nameof(TrendFactor), 0.18m)
.SetDisplay("Trend Factor", "Trend term weight in filter.", "Filter");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period for entry buffer.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FilterLength
{
get => _filterLength.Value;
set => _filterLength.Value = value;
}
public decimal PriceFactor
{
get => _priceFactor.Value;
set => _priceFactor.Value = value;
}
public decimal TrendFactor
{
get => _trendFactor.Value;
set => _trendFactor.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_closeBuffer.Clear();
_entryPrice = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
_closeBuffer.Add(candle.ClosePrice);
var maxDepth = FilterLength + 2;
while (_closeBuffer.Count > maxDepth)
_closeBuffer.RemoveAt(0);
if (_closeBuffer.Count <= FilterLength || atrVal <= 0)
return;
var close = candle.ClosePrice;
var target = CalculateTarget();
if (target == null)
return;
var targetPrice = target.Value;
var buffer = atrVal * 0.3m;
// Manage position
if (Position > 0)
{
// Exit if projection flips below close or on stop
if (targetPrice < close - buffer)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (targetPrice > close + buffer)
{
BuyMarket();
_entryPrice = 0;
}
}
// Entry
if (Position == 0)
{
if (targetPrice > close + buffer)
{
_entryPrice = close;
BuyMarket();
}
else if (targetPrice < close - buffer)
{
_entryPrice = close;
SellMarket();
}
}
}
private decimal? CalculateTarget()
{
var n = FilterLength;
if (n < 2 || _closeBuffer.Count < n + 1)
return null;
var sum = 0m;
for (var i = 1; i <= n; i++)
sum += GetClose(i);
var sm = sum / n;
var weightedSum = 0m;
for (var i = 0; i < n; i++)
{
var price = GetClose(i + 1);
var weight = n - i;
weightedSum += price * weight;
}
var denominator = (decimal)n * (n + 1) / 2m;
if (denominator <= 0m)
return null;
var lm = weightedSum / denominator;
var divisor = n - 1;
if (divisor <= 0)
return null;
var s = new decimal[n + 2];
var t = new decimal[n + 2];
var tn = (6m * lm - 6m * sm) / divisor;
var sn = 4m * sm - 3m * lm - tn;
s[n] = sn;
t[n] = tn;
for (var k = n - 1; k > 0; k--)
{
var close = GetClose(k);
s[k] = PriceFactor * close + (1m - PriceFactor) * (s[k + 1] + t[k + 1]);
t[k] = TrendFactor * (s[k] - s[k + 1]) + (1m - TrendFactor) * t[k + 1];
}
return s[1] + t[1];
}
private decimal GetClose(int index)
{
var idx = _closeBuffer.Count - 1 - index;
if (idx < 0) idx = 0;
if (idx >= _closeBuffer.Count) idx = _closeBuffer.Count - 1;
return _closeBuffer[idx];
}
}
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
from StockSharp.Algo.Indicators import AverageTrueRange
class gandalf_pro_projection_strategy(Strategy):
def __init__(self):
super(gandalf_pro_projection_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._filter_length = self.Param("FilterLength", 24) \
.SetDisplay("Filter Length", "Smoothing filter length", "Filter")
self._price_factor = self.Param("PriceFactor", 0.18) \
.SetDisplay("Price Factor", "Close price weight in filter", "Filter")
self._trend_factor = self.Param("TrendFactor", 0.18) \
.SetDisplay("Trend Factor", "Trend term weight in filter", "Filter")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period for entry buffer", "Indicators")
self._close_buffer = []
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FilterLength(self):
return self._filter_length.Value
@property
def PriceFactor(self):
return self._price_factor.Value
@property
def TrendFactor(self):
return self._trend_factor.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(gandalf_pro_projection_strategy, self).OnStarted2(time)
self._close_buffer = []
self._entry_price = 0.0
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, atr_val):
if candle.State != CandleStates.Finished:
return
av = float(atr_val)
close = float(candle.ClosePrice)
self._close_buffer.append(close)
max_depth = self.FilterLength + 2
while len(self._close_buffer) > max_depth:
self._close_buffer.pop(0)
fl = self.FilterLength
if len(self._close_buffer) <= fl or av <= 0:
return
target = self._calculate_target()
if target is None:
return
buffer_dist = av * 0.3
# Manage position
if self.Position > 0:
if target < close - buffer_dist:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if target > close + buffer_dist:
self.BuyMarket()
self._entry_price = 0.0
# Entry
if self.Position == 0:
if target > close + buffer_dist:
self._entry_price = close
self.BuyMarket()
elif target < close - buffer_dist:
self._entry_price = close
self.SellMarket()
def _calculate_target(self):
n = self.FilterLength
if n < 2 or len(self._close_buffer) < n + 1:
return None
total = 0.0
for i in range(1, n + 1):
total += self._get_close(i)
sm = total / n
weighted_sum = 0.0
for i in range(n):
price = self._get_close(i + 1)
weight = n - i
weighted_sum += price * weight
denominator = n * (n + 1) / 2.0
if denominator <= 0:
return None
lm = weighted_sum / denominator
divisor = n - 1
if divisor <= 0:
return None
pf = float(self.PriceFactor)
tf = float(self.TrendFactor)
s = [0.0] * (n + 2)
t = [0.0] * (n + 2)
tn = (6.0 * lm - 6.0 * sm) / divisor
sn = 4.0 * sm - 3.0 * lm - tn
s[n] = sn
t[n] = tn
for k in range(n - 1, 0, -1):
c = self._get_close(k)
s[k] = pf * c + (1.0 - pf) * (s[k + 1] + t[k + 1])
t[k] = tf * (s[k] - s[k + 1]) + (1.0 - tf) * t[k + 1]
return s[1] + t[1]
def _get_close(self, index):
idx = len(self._close_buffer) - 1 - index
if idx < 0:
idx = 0
if idx >= len(self._close_buffer):
idx = len(self._close_buffer) - 1
return self._close_buffer[idx]
def OnReseted(self):
super(gandalf_pro_projection_strategy, self).OnReseted()
self._close_buffer = []
self._entry_price = 0.0
def CreateClone(self):
return gandalf_pro_projection_strategy()