A Estratégia KA-Gold Bot é uma conversão StockSharp de alto nível do consultor especialista original MetaTrader 4 "KA-Gold Bot". Ele combina um canal estilo Keltner com filtros de tendência e gerenciamento de risco agressivo que inclui stop-loss fixo, take-profit e proteção de rastreamento em vários estágios. A negociação é permitida apenas durante uma janela intradiária configurável e novas posições são bloqueadas quando o spread ao vivo excede um limite.
Lógica de negociação
Preparação de indicadores
Uma média móvel exponencial (EMA) com comprimento KeltnerPeriod constrói a linha média do canal.
Uma média móvel simples dos intervalos das velas (máxima menos mínima) com o mesmo período estima a meia largura do canal.
As médias móveis exponenciais de curto e longo prazo (EmaShortPeriod e EmaLongPeriod) acompanham o impulso rápido e a tendência de período de tempo mais alto, respectivamente.
Todos os valores dos indicadores são registrados para as duas velas concluídas mais recentemente para espelhar os cálculos baseados em turnos do MT4.
Condições de entrada
Os cálculos são executados somente quando a vela atual fecha e a estratégia está conectada ao mercado com permissões de negociação concedidas.
As bandas superior e inferior do canal são derivadas adicionando/subtraindo o intervalo médio da linha média EMA para a vela anterior (shift = 1) e a anterior (shift = 2).
Configuração longa:
O fechamento anterior ultrapassa a banda superior mais recente.
O mesmo fechamento está acima da compra EMA, confirmando uma tendência de alta.
O EMA curto cruza abaixo da faixa superior mais antiga para acima da mais recente (EMA_short[2] < Upper[2] e EMA_short[1] > Upper[1]).
Configuração curta:
O fechamento anterior fica abaixo da banda inferior recente.
O mesmo fechamento está abaixo da compra EMA, confirmando uma tendência de baixa.
O EMA curto cruza acima da banda inferior mais antiga para abaixo da mais recente (EMA_short[2] > Lower[2] e EMA_short[1] < Lower[1]).
Apenas uma posição é permitida por vez. Se uma negociação já estiver aberta, o sinal será ignorado.
Filtros de tempo e propagação
Quando UseTimeFilter está ativado, novas entradas são restritas à janela [StartHour:StartMinute, EndHour:EndMinute) usando o horário local do Exchange. Sessões noturnas serão suportadas se o horário de término for anterior ao horário de início.
As assinaturas de cotação de nível 1 acompanham os melhores preços de compra/venda. Antes de fazer um pedido, a estratégia converte o spread atual em pontos de instrumento e o compara com MaxSpreadPoints. Os pedidos são ignorados, com registro, sempre que o limite é violado.
Gerenciamento de riscos
O tamanho padrão da posição é FixedVolume. Se UseRiskPercent for true, o tamanho da negociação será recalculado a partir do patrimônio do portfólio como RiskPercent% / (riskPips * PipValue), onde riskPips é igual a StopLossPips (substituição para TrailingStopPips quando nenhum stop fixo for definido). O resultado final é normalizado para o passo de volume do instrumento e fixado entre os limites de troca mínimo e máximo.
Quando uma posição longa é aberta, a estratégia armazena:
Stop-loss inicial em entry - StopLossPips * pipSize (se definido).
Take-profit inicial em entry + TakeProfitPips * pipSize (se definido).
Sinalizadores de estado final, que redefinem os rastreadores do lado curto.
As negociações curtas refletem a mesma lógica com direções de preços invertidas.
Proteção de rastreamento
As atualizações de oferta/venda ao vivo alimentam dois mecanismos finais:
Assim que o lucro flutuante exceder TrailingTriggerPips, o trailing se torna ativo.
O trailing stop está posicionado a TrailingStopPips de distância do preço favorável atual e só avança quando o movimento excede TrailingStopPips + TrailingStepPips além do nível de stop anterior.
Para posições longas, o trailing stop nunca cai abaixo do stop de proteção original e, para posições curtas, nunca sobe acima dele.
O monitoramento de saída é realizado tanto nas cotações recebidas quanto nas velas finalizadas:
Uma posição é fechada imediatamente quando o preço atinge o stop ativo (original ou móvel).
Os lucros também são bloqueados quando a máxima/mínima da vela atinge o nível de lucro armazenado.
Após fechar uma posição, o estado de proteção é totalmente redefinido para evitar dados obsoletos.
Parâmetros
Parâmetro
Descrição
Padrão
CandleType
Tipo de dados que descreve o prazo de execução.
Período de 1 minuto
KeltnerPeriod
Período para a linha média EMA e a média do intervalo do canal.
50
EmaShortPeriod
Comprimento EMA rápido usado para confirmação de cruzamento.
10
EmaLongPeriod
Comprimento EMA lento atuando como filtro de tendência.
200
FixedVolume
Volume do pedido substituto quando o dimensionamento percentual está desativado.
1
UseRiskPercent
Ative o dimensionamento de posição com base em porcentagem.
true
RiskPercent
Porcentagem de patrimônio arriscado por negociação.
1
StopLossPips
Distância do stop loss fixo em pips (0 desabilita).
500
TakeProfitPips
Distância do take-profit fixo em pips (0 desabilita).
500
TrailingTriggerPips
Lucro em pips necessário para ativar o trailing stop.
300
TrailingStopPips
Distância entre o preço e o trailing stop, uma vez ativo.
300
TrailingStepPips
Lucro adicional mínimo (em pips) antes do trailing stop ser avançado.
100
UseTimeFilter
Alterne para o filtro da sessão de negociação.
true
StartHour / StartMinute
A sessão começa no horário local do Exchange.
02:30
EndHour / EndMinute
A sessão termina no horário local do Exchange.
21:00
MaxSpreadPoints
Spread máximo permitido nos pontos do instrumento (0 desativa a verificação).
65
PipValue
Valor monetário de um pip, utilizado para dimensionamento de posições com base no risco.
1
Notas adicionais
A conversão do pip segue os decimais do instrumento de câmbio: uma cotação de cinco dígitos (número ímpar de decimais) multiplica o passo do preço por 10 para emular a lógica do tamanho do pip MT4.
A estratégia assina velas e dados de nível 1, mas não registra indicadores adicionais no gráfico, em conformidade com as diretrizes de alto nível API.
As saídas protetoras dependem de ordens de mercado emitidas pela estratégia; nenhuma ordem stop ou limite separada é colocada na bolsa.
O suporte Python não está incluído nesta entrega, correspondendo à solicitação original.
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;
/// <summary>
/// Simplified from "KA Gold Bot" MetaTrader expert.
/// Uses Keltner channel (EMA + ATR-based bands) with EMA crossover for entries.
/// Buys when short EMA crosses above long EMA and close is above Keltner center.
/// Sells on reverse conditions.
/// </summary>
public class KaGoldBotStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _keltnerPeriod;
private readonly StrategyParam<int> _emaShortPeriod;
private readonly StrategyParam<int> _emaLongPeriod;
private ExponentialMovingAverage _emaShort;
private ExponentialMovingAverage _emaLong;
// Manual ATR-like range average for Keltner
private readonly Queue<decimal> _rangeQueue = new();
private decimal _rangeSum;
private ExponentialMovingAverage _emaKeltner;
private decimal? _prevEmaShort;
private decimal? _prevEmaLong;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int KeltnerPeriod
{
get => _keltnerPeriod.Value;
set => _keltnerPeriod.Value = value;
}
public int EmaShortPeriod
{
get => _emaShortPeriod.Value;
set => _emaShortPeriod.Value = value;
}
public int EmaLongPeriod
{
get => _emaLongPeriod.Value;
set => _emaLongPeriod.Value = value;
}
public KaGoldBotStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_keltnerPeriod = Param(nameof(KeltnerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Keltner Period", "EMA period for Keltner channel center", "Indicators");
_emaShortPeriod = Param(nameof(EmaShortPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("EMA Short Period", "Short EMA for crossover signal", "Indicators");
_emaLongPeriod = Param(nameof(EmaLongPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Long Period", "Long EMA for crossover signal", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_emaShort = new ExponentialMovingAverage { Length = EmaShortPeriod };
_emaLong = new ExponentialMovingAverage { Length = EmaLongPeriod };
_emaKeltner = new ExponentialMovingAverage { Length = KeltnerPeriod };
_rangeQueue.Clear();
_rangeSum = 0;
_prevEmaShort = null;
_prevEmaLong = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_emaShort, _emaLong, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _emaShort);
DrawIndicator(area, _emaLong);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaShortValue, decimal emaLongValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
// Process Keltner EMA manually
var keltnerInput = new DecimalIndicatorValue(_emaKeltner, close, candle.OpenTime);
var keltnerResult = _emaKeltner.Process(keltnerInput);
var emaKeltnerValue = keltnerResult.IsEmpty ? close : keltnerResult.GetValue<decimal>();
// Calculate range average (manual SMA of high-low) for Keltner bands
var range = candle.HighPrice - candle.LowPrice;
_rangeQueue.Enqueue(range);
_rangeSum += range;
while (_rangeQueue.Count > KeltnerPeriod)
_rangeSum -= _rangeQueue.Dequeue();
if (_prevEmaShort == null || _prevEmaLong == null)
{
_prevEmaShort = emaShortValue;
_prevEmaLong = emaLongValue;
return;
}
// Keltner bands
var rangeAvg = _rangeQueue.Count > 0 ? _rangeSum / _rangeQueue.Count : 0;
var upper = emaKeltnerValue + rangeAvg;
var lower = emaKeltnerValue - rangeAvg;
// Buy: short EMA crosses above long EMA and close above Keltner center
var buySignal = _prevEmaShort.Value <= _prevEmaLong.Value && emaShortValue > emaLongValue
&& close > emaKeltnerValue;
// Sell: short EMA crosses below long EMA and close below Keltner center
var sellSignal = _prevEmaShort.Value >= _prevEmaLong.Value && emaShortValue < emaLongValue
&& close < emaKeltnerValue;
if (buySignal)
{
if (Position < 0)
BuyMarket();
if (Position <= 0)
BuyMarket();
}
else if (sellSignal)
{
if (Position > 0)
SellMarket();
if (Position >= 0)
SellMarket();
}
_prevEmaShort = emaShortValue;
_prevEmaLong = emaLongValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_emaShort = null;
_emaLong = null;
_emaKeltner = null;
_rangeQueue.Clear();
_rangeSum = 0;
_prevEmaShort = null;
_prevEmaLong = null;
base.OnReseted();
}
}
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 ka_gold_bot_strategy(Strategy):
def __init__(self):
super(ka_gold_bot_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._keltner_period = self.Param("KeltnerPeriod", 20)
self._ema_short_period = self.Param("EmaShortPeriod", 10)
self._ema_long_period = self.Param("EmaLongPeriod", 50)
self._prev_ema_short = None
self._prev_ema_long = None
self._range_queue = []
self._range_sum = 0.0
self._keltner_ema = 0.0
self._keltner_count = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def KeltnerPeriod(self):
return self._keltner_period.Value
@KeltnerPeriod.setter
def KeltnerPeriod(self, value):
self._keltner_period.Value = value
@property
def EmaShortPeriod(self):
return self._ema_short_period.Value
@EmaShortPeriod.setter
def EmaShortPeriod(self, value):
self._ema_short_period.Value = value
@property
def EmaLongPeriod(self):
return self._ema_long_period.Value
@EmaLongPeriod.setter
def EmaLongPeriod(self, value):
self._ema_long_period.Value = value
def OnReseted(self):
super(ka_gold_bot_strategy, self).OnReseted()
self._prev_ema_short = None
self._prev_ema_long = None
self._range_queue = []
self._range_sum = 0.0
self._keltner_ema = 0.0
self._keltner_count = 0
def OnStarted2(self, time):
super(ka_gold_bot_strategy, self).OnStarted2(time)
self._prev_ema_short = None
self._prev_ema_long = None
self._range_queue = []
self._range_sum = 0.0
self._keltner_ema = 0.0
self._keltner_count = 0
ema_short = ExponentialMovingAverage()
ema_short.Length = self.EmaShortPeriod
ema_long = ExponentialMovingAverage()
ema_long.Length = self.EmaLongPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema_short, ema_long, self._process_candle).Start()
def _process_candle(self, candle, ema_short_value, ema_long_value):
if candle.State != CandleStates.Finished:
return
ema_short_val = float(ema_short_value)
ema_long_val = float(ema_long_value)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
# Manual Keltner EMA
keltner_period = self.KeltnerPeriod
if self._keltner_count == 0:
self._keltner_ema = close
else:
alpha = 2.0 / (keltner_period + 1)
self._keltner_ema = close * alpha + self._keltner_ema * (1 - alpha)
self._keltner_count += 1
# Range average for Keltner bands
bar_range = high - low
self._range_queue.append(bar_range)
self._range_sum += bar_range
while len(self._range_queue) > keltner_period:
self._range_sum -= self._range_queue.pop(0)
if self._prev_ema_short is None or self._prev_ema_long is None:
self._prev_ema_short = ema_short_val
self._prev_ema_long = ema_long_val
return
# Buy: short EMA crosses above long EMA and close above Keltner center
buy_signal = (self._prev_ema_short <= self._prev_ema_long and
ema_short_val > ema_long_val and close > self._keltner_ema)
# Sell: short EMA crosses below long EMA and close below Keltner center
sell_signal = (self._prev_ema_short >= self._prev_ema_long and
ema_short_val < ema_long_val and close < self._keltner_ema)
if buy_signal:
if self.Position <= 0:
self.BuyMarket()
elif sell_signal:
if self.Position >= 0:
self.SellMarket()
self._prev_ema_short = ema_short_val
self._prev_ema_long = ema_long_val
def CreateClone(self):
return ka_gold_bot_strategy()