A estratégia Gandalf PRO é uma versão StockSharp do MetaTrader 4 consultor especialista Gandalf_PRO. O robô original constrói um
filtro de suavização adaptativo de uma média móvel ponderada e um componente de tendência recursiva. Quando o preço projetado se move em
pelo menos 15 pips além do preço de mercado atual, o EA entra nessa direção com um stop-loss distante e um take-profit no
nível projetado. A conversão StockSharp reproduz o mesmo filtro e lógica de decisão enquanto depende da vela de alto nível
API então cada cálculo é realizado em barras acabadas.
Lógica de negociação
Assine o período selecionado por CandleType (padrão: velas de 1 hora) e processe apenas velas concluídas.
Mantenha um histórico contínuo de preços de fechamento grande o suficiente para cobrir o máximo de CountBuy e CountSell mais uma barra extra.
Recrie a função MetaTrader Out(): calcule médias móveis lineares ponderadas e simples (usando um deslocamento de uma barra), derive o
componentes recursivos s e t com os fatores de preço e tendência configurados e obtenha o preço projetado s[1] + t[1].
Para configurações longas (EnableBuy):
Verifique se o preço projetado está pelo menos 15 pips acima do último fechamento (Bid + 15*x*Point no MT4).
Se nenhuma posição longa estiver aberta, compre o volume configurado (veja BaseVolume e BuyRiskMultiplier).
Armazene o preço projetado como take-profit e calcule o stop loss subtraindo BuyStopLossPips convertido em etapas de preço.
Para configurações curtas (EnableSell):
Exija que o preço projetado fique pelo menos 15 pips abaixo do último fechamento.
Se nenhuma posição curta estiver aberta, venda o volume configurado (revertendo uma posição longa existente, se necessário).
Salve o preço projetado como take-profit e defina os pips de stop loss SellStopLossPips acima do mercado.
Enquanto existir uma posição, monitore cada vela concluída:
Saia das posições compradas se a mínima da vela cruzar o stop armazenado ou a máxima atingir o take-profit.
Saia das posições vendidas se a máxima da vela cruzar o stop ou a mínima atingir o alvo.
As saídas usam ClosePosition() que nivela a exposição líquida em StockSharp.
Parâmetros
Nome
Tipo
Padrão
Descrição
EnableBuy
bool
true
Permita que a estratégia abra posições longas.
CountBuy
int
24
Comprimento do filtro de suavização usado para projeções longas.
BuyPriceFactor
decimal
0.18
Peso do fechamento da corrente no filtro recursivo longo.
BuyTrendFactor
decimal
0.18
Peso aplicado ao termo de tendência na construção da projeção longa.
BuyStopLossPips
int
62
Distância de stop-loss para posições longas, medida em pips.
BuyRiskMultiplier
decimal
0
Multiplicador aplicado a BaseVolume antes de enviar um pedido longo (0 mantém o volume base).
EnableSell
bool
true
Permita que a estratégia abra posições curtas.
CountSell
int
24
Comprimento do filtro de suavização usado para projeções curtas.
SellPriceFactor
decimal
0.18
Peso do fechamento da corrente no filtro recursivo curto.
SellTrendFactor
decimal
0.18
Peso aplicado ao termo de tendência na construção da projeção curta.
SellStopLossPips
int
62
Distância de stop-loss para posições curtas, medida em pips.
SellRiskMultiplier
decimal
0
Multiplicador aplicado a BaseVolume antes de enviar uma ordem curta (0 mantém o volume base).
BaseVolume
decimal
1
Tamanho base do pedido usado quando ambos os multiplicadores de risco são zero.
CandleType
DataType
Período de 1 hora
Série de velas processada pela estratégia.
Diferenças do original MetaTrader EA
MetaTrader pode realizar compras e vendas independentes de ingressos simultaneamente. StockSharp usa posições líquidas, então a porta fecha ou
inverte uma posição existente antes de abrir o lado oposto.
A função de lote MT4 usou margem livre de conta. A conversão expõe BaseVolume e dois multiplicadores de risco; quando eles são zero
o volume base é usado como está, caso contrário, o volume é simplesmente dimensionado (BaseVolume * RiskMultiplier).
Os níveis de stop-loss e take-profit são executados monitorando velas concluídas. Os preenchimentos intrabarras podem, portanto, ser diferentes de MetaTrader
onde as ordens de proteção são gerenciadas pela corretora.
O ajuste Digits/Point de cinco dígitos é emulado inspecionando Security.Decimals e Security.PriceStep para converter pip
distâncias em preços absolutos.
Todos os cálculos dos indicadores são realizados em código gerenciado sem chamar iMA; o filtro recursivo é recriado em
CalculateTarget usando os mesmos coeficientes da função MQL.
Notas de uso
Atribua o instrumento desejado a Strategy.Security antes de começar. A estratégia lança uma exceção se nenhuma segurança estiver anexada.
Configure BaseVolume para corresponder ao tamanho do contrato esperado pelo seu local; ajuste os multiplicadores de risco apenas se quiser escalar
a exposição relativa ao volume base.
O histórico da vela deve conter pelo menos max(CountBuy, CountSell) + 1 barras antes que qualquer negociação possa ser gerada. Forneça o suficiente
dados de aquecimento ou inicie a estratégia com velas históricas carregadas.
O buffer de entrada de 15 pip é fixo (assim como no EA). Aumente CountBuy/CountSell para suavizar a projeção ou ajustar o
fatores de preço/tendência para corresponder ao comportamento observado em MetaTrader.
Como as saídas dependem dos extremos das velas, ative um período de tempo adequado à sua latência de execução. Prazos mais baixos reagirão mais cedo
mas requerem mais dados históricos e podem gerar mais sinais.
Detalhes de implementação
Usa SubscribeCandles() com Bind(ProcessCandle) para que cada decisão seja baseada em velas finalizadas.
Mantém uma lista compacta de fechamentos recentes e reconstrói o filtro recursivo s/t sob demanda, imitando a rotina Out().
Converte deslocamentos baseados em pip por meio do tamanho do tick do instrumento e da precisão decimal para replicar o dimensionamento MetaTrader x * Point.
ClosePosition() é invocado quando os níveis de proteção são violados, garantindo que a posição líquida seja achatada antes que outra entrada seja
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()