La Estrategia KA-Gold Bot es una conversión de alto nivel StockSharp del asesor experto original MetaTrader 4 "KA-Gold Bot". Combina un canal estilo Keltner con filtros de tendencias y una gestión de riesgos agresiva que incluye stop-loss fijo, take-profit y protección de seguimiento de varias etapas. Solo se permite operar durante una ventana intradiaria configurable y las nuevas posiciones se bloquean cuando el diferencial en vivo excede un umbral.
Lógica de trading
Preparación de indicadores
Una media móvil exponencial (EMA) con longitud KeltnerPeriod construye la línea media del canal.
Una media móvil simple de rangos de velas (máximo menos mínimo) con el mismo período estima la mitad del ancho del canal.
Los promedios móviles exponenciales a corto y largo plazo (EmaShortPeriod y EmaLongPeriod) rastrean el impulso rápido y la tendencia de marco temporal más alto, respectivamente.
Todos los valores de los indicadores se registran para las dos velas completadas más recientes para reflejar los cálculos basados en cambios de MT4.
Condiciones de entrada
Los cálculos se ejecutan sólo cuando se cierra la vela actual y la estrategia está conectada al mercado con los permisos comerciales otorgados.
Las bandas superior e inferior del canal se obtienen sumando/restando el rango promedio de la línea media EMA tanto para la vela anterior (shift = 1) como para la anterior (shift = 2).
Configuración larga:
El cierre anterior rompe por encima de la banda superior más reciente.
El mismo cierre está por encima del EMA largo, lo que confirma una tendencia alcista.
El EMA corto cruza desde debajo de la banda superior más antigua hasta encima de la más reciente (EMA_short[2] < Upper[2] y EMA_short[1] > Upper[1]).
Configuración breve:
El cierre anterior cae por debajo de la banda inferior reciente.
El mismo cierre está por debajo del largo EMA, lo que confirma una tendencia bajista.
El EMA corto cruza desde arriba de la banda inferior más antigua hasta debajo de la más reciente (EMA_short[2] > Lower[2] y EMA_short[1] < Lower[1]).
Sólo se permite una posición a la vez. Si ya hay una operación abierta, la señal se ignora.
Filtros de tiempo y difusión
Cuando UseTimeFilter está habilitado, las nuevas entradas están restringidas a la ventana [StartHour:StartMinute, EndHour:EndMinute) usando la hora local del intercambio. Se admiten sesiones nocturnas si la hora de finalización es anterior a la hora de inicio.
Las suscripciones a cotizaciones de nivel 1 realizan un seguimiento de los mejores precios de oferta y demanda. Antes de realizar una orden, la estrategia convierte el diferencial actual en puntos de instrumento y lo compara con MaxSpreadPoints. Los pedidos se omiten y se registran cada vez que se supera el umbral.
Gestión de riesgos
El tamaño de posición por defecto es FixedVolume. Si UseRiskPercent es true, el tamaño de la operación se recalcula a partir del valor de la cartera como RiskPercent% / (riskPips * PipValue), donde riskPips es igual a StopLossPips (recurre a TrailingStopPips cuando no se define ningún tope fijo). El resultado final se normaliza al paso de volumen del instrumento y se fija entre los límites de intercambio mínimo y máximo.
Cuando se abre una posición larga, la estrategia almacena:
Stop-loss inicial en entry - StopLossPips * pipSize (si está definido).
Toma de ganancias inicial en entry + TakeProfitPips * pipSize (si está definido).
Banderas de estado finales, que reinician los rastreadores del lado corto.
Las operaciones cortas reflejan la misma lógica con direcciones de precios invertidas.
Protección de seguimiento
Las actualizaciones de oferta y demanda en vivo alimentan dos motores de seguimiento:
Una vez que el beneficio flotante supera TrailingTriggerPips, el seguimiento se activa.
El trailing stop se posiciona TrailingStopPips lejos del precio favorable actual y solo avanza cuando el movimiento excede TrailingStopPips + TrailingStepPips más allá del nivel de stop anterior.
Para posiciones largas, el trailing stop nunca cae por debajo del stop protector original, y para posiciones cortas nunca sube por encima de él.
El seguimiento de las salidas se realiza tanto en las cotizaciones entrantes como en las velas terminadas:
Una posición se cierra inmediatamente cuando el precio alcanza el tope activo (original o final).
Las ganancias también se bloquean una vez que el máximo/mínimo de la vela toca el nivel de obtención de beneficios almacenado.
Después de cerrar una posición, el estado de protección se restablece por completo para evitar datos obsoletos.
Parámetros
Parámetro
Descripción
Predeterminado
CandleType
Tipo de datos que describe el plazo de ejecución.
marco de tiempo de 1 minuto
KeltnerPeriod
Período para la línea media EMA y el rango promedio del canal.
50
EmaShortPeriod
Longitud rápida EMA utilizada para la confirmación de cruce.
10
EmaLongPeriod
Longitud lenta de EMA que actúa como filtro de tendencias.
200
FixedVolume
Volumen de pedido alternativo cuando el tamaño porcentual está deshabilitado.
1
UseRiskPercent
Habilite el tamaño de posición basado en porcentaje.
true
RiskPercent
Porcentaje de capital arriesgado por operación.
1
StopLossPips
Distancia del stop-loss fijo en pips (0 inhabilitaciones).
500
TakeProfitPips
Distancia de la toma de ganancias fija en pips (0 inhabilitaciones).
500
TrailingTriggerPips
Beneficio en pips necesarios para activar el trailing stop.
300
TrailingStopPips
Distancia entre el precio y el trailing stop una vez activo.
300
TrailingStepPips
Beneficio adicional mínimo (en pips) antes de que se avance el trailing stop.
100
UseTimeFilter
Alternar para el filtro de sesión de negociación.
true
StartHour / StartMinute
Inicio de sesión en hora local de intercambio.
02:30
EndHour / EndMinute
La sesión finaliza en hora local de intercambio.
21:00
MaxSpreadPoints
Spread máximo permitido en puntos del instrumento (0 desactiva la verificación).
65
PipValue
Valor monetario de un pip, utilizado para dimensionar posiciones basadas en riesgos.
1
Notas adicionales
La conversión de pips sigue los decimales del instrumento de intercambio: una cotización de cinco dígitos (número impar de decimales) multiplica el paso del precio por 10 para emular la lógica del tamaño de pip MT4.
La estrategia se suscribe tanto a velas como a datos de nivel 1, pero no registra indicadores adicionales en el gráfico, cumpliendo con las pautas de alto nivel API.
Las salidas protectoras dependen de las órdenes de mercado emitidas por la estrategia; no se colocan órdenes stop o límite separadas en el intercambio.
El soporte de Python no está incluido en esta entrega, lo que coincide con la solicitud 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()