在 GitHub 上查看
KA-Gold Bot 策略
KA-Gold Bot 策略 是 MetaTrader 4「KA-Gold Bot」EA 的 StockSharp 高阶版移植。策略结合 Keltner 通道、双 EMA 趋势过滤与多阶段风险管理:固定止损/止盈、分段追踪止损,以及基于实时报价的点差过滤。只有在指定的交易时段内且实时点差满足要求时,才会开仓。
交易逻辑
指标准备
KeltnerPeriod 长度的指数移动平均 (EMA) 作为通道中轴。
- 同周期的简单移动平均应用于 K 线振幅(高点减低点),得到通道半宽度。
EmaShortPeriod 与 EmaLongPeriod 两条 EMA 分别捕捉短线动量与大周期趋势。
- 所有指标值都会保存最近两个已完成 K 线的数据,以复刻 MT4 中的
shift 引用方式。
入场条件
- 仅在当前 K 线收盘、策略已连接且允许交易时评估信号。
- 最近两根 K 线的上轨/下轨通过中轴 ± 平均振幅计算得到。
- 做多条件:
- 前一根收盘价突破最新上轨;
- 同一收盘价高于慢速 EMA,确认上升趋势;
- 快速 EMA 从上一根的上轨下方穿越到最新上轨上方(
EMA_short[2] < Upper[2] 且 EMA_short[1] > Upper[1])。
- 做空条件:
- 前一根收盘价跌破最新下轨;
- 同一收盘价低于慢速 EMA,确认下降趋势;
- 快速 EMA 从上一根的下轨上方穿越到最新下轨下方(
EMA_short[2] > Lower[2] 且 EMA_short[1] < Lower[1])。
- 仅允许单笔持仓,如已有仓位则忽略新信号。
时间与点差过滤
- 当
UseTimeFilter 为 true 时,新仓位只会在 [StartHour:StartMinute, EndHour:EndMinute) 的交易时段内触发,使用交易所本地时间。若结束时间早于开始时间,则视为跨日时段。
- 策略订阅 Level-1 行情,实时记录买一/卖一。开仓前会将当前点差换算为价格步长 (
PriceStep) 的倍数,并与 MaxSpreadPoints 比较,超过阈值则跳过交易并记录日志。
仓位管理
- 默认使用
FixedVolume 固定手数。若 UseRiskPercent 为 true,则按账户权益的 RiskPercent% 计算风险金额,并除以 riskPips * PipValue 得到下单量,其中 riskPips 优先采用 StopLossPips(如未设置,则退回 TrailingStopPips)。计算结果会按交易所的最小变动手数归一,并限制在允许的最小/最大量之间。
- 做多时设置:
- 固定止损
entry - StopLossPips * pipSize(若启用);
- 固定止盈
entry + TakeProfitPips * pipSize(若启用);
- 重置追踪止损状态,并清空空头保护变量。
- 做空逻辑完全对称。
追踪止损
- 实时报价驱动多空两套追踪机制:
- 浮动盈利达到
TrailingTriggerPips 后激活追踪;
- 追踪止损始终保持在当前有利价格之外
TrailingStopPips 个点,且只有在盈利超出上一止损位 TrailingStopPips + TrailingStepPips 时才会上移/下移;
- 做多时,追踪止损不会低于初始止损;做空时则不会高于初始止损。
- 退出监控同样在报价与收盘 K 线上执行:
- 价格触及任意有效止损(初始或追踪)立即平仓;
- 若最高/最低价触及止盈水平,也会立即了结仓位。
- 平仓后所有保护状态清零,避免遗留数据影响下一笔交易。
参数
| 参数 |
说明 |
默认值 |
CandleType |
执行所用的 K 线数据类型。 |
1 分钟 K 线 |
KeltnerPeriod |
Keltner 中轴及振幅均值的周期。 |
50 |
EmaShortPeriod |
快速 EMA 周期。 |
10 |
EmaLongPeriod |
慢速 EMA 周期,用作趋势过滤。 |
200 |
FixedVolume |
固定下单量(关闭风险百分比时使用)。 |
1 |
UseRiskPercent |
是否启用按百分比计算持仓。 |
true |
RiskPercent |
单笔交易占账户权益的百分比。 |
1 |
StopLossPips |
固定止损的点数距离(0 表示禁用)。 |
500 |
TakeProfitPips |
固定止盈的点数距离(0 表示禁用)。 |
500 |
TrailingTriggerPips |
激活追踪止损所需的盈利点数。 |
300 |
TrailingStopPips |
追踪止损与价格之间的距离。 |
300 |
TrailingStepPips |
追踪止损每次移动所需的最小新增盈利。 |
100 |
UseTimeFilter |
是否启用交易时段过滤。 |
true |
StartHour / StartMinute |
交易时段起始时间(交易所本地)。 |
02:30 |
EndHour / EndMinute |
交易时段结束时间(交易所本地)。 |
21:00 |
MaxSpreadPoints |
允许的最大点差(以价格步长计,0 表示不限制)。 |
65 |
PipValue |
每个点 (pip) 的货币价值,用于风险持仓计算。 |
1 |
其他说明
- Pip 换算遵循交易所小数位规则:若品种为 5 位报价(小数位为奇数),则将价格步长乘以 10,与 MT4 中的 pip 定义保持一致。
- 策略同时订阅 K 线与 Level-1 数据,但不会额外向
Strategy.Indicators 注册指标,符合高阶 API 要求。
- 止损/止盈由策略以市价单执行,未向交易所提交真实的止损/止盈委托。
- 根据需求,本次未提供 Python 版本或对应文件夹。
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()