Открыть на GitHub

Стратегия KA-Gold Bot

KA-Gold Bot — это порт советника MetaTrader «KA-Gold Bot». Стратегия торгует пробои пользовательского канала Кельтнера и подтверждает сигналы трендовыми фильтрами EMA. Реализация использует высокоуровневые подписки StockSharp, чтобы сохранить параметры управляемыми через интерфейс и готовыми к оптимизации.

Логика торговли

  • Рассчитываются три экспоненциальные средние:
    • EMA(10) — быстрый индикатор импульса.
    • EMA(200) — фильтр глобального тренда.
    • EMA(period) — центральная линия канала, на ту же длину усредняется диапазон свечей (High-Low).
  • Простое скользящее среднее от диапазона формирует границы:
    • Верхняя граница = EMA(period) + SMA(high-low, period).
    • Нижняя граница = EMA(period) − SMA(high-low, period).
  • Лонг открывается, когда на последней закрытой свече выполняются условия:
    • Цена закрытия выше верхней границы.
    • Цена закрытия выше EMA(200).
    • EMA(10) пересекает верхнюю границу снизу вверх между предыдущей и текущей свечой.
  • Шорт симметричен:
    • Цена закрытия ниже нижней границы.
    • Цена закрытия ниже EMA(200).
    • EMA(10) пересекает нижнюю границу сверху вниз между предыдущей и текущей свечой.
  • Одновременно допускается только одна позиция.

Размер позиции

  • Фиксированный лот — торговать объемом BaseVolume.
  • Процент от капитала — при UseRiskPercent = true текущая стоимость портфеля умножается на RiskPercent, результат масштабируется по метатрейдеровской схеме (деление на 100000) и округляется до шага BaseVolume с учётом ограничений инструмента (MinVolume, MaxVolume, VolumeStep). При отсутствии данных по капиталу стратегия возвращается к фиксированному объёму.

Управление риском

  • Стоп-лосс и тейк-профит задаются в пунктах. Пункт пересчитывается в абсолютную цену через PriceStep; для инструментов с 3 или 5 знаками используется правило MetaTrader pip = step × 10.
  • После открытия позиции выставляются защитные ордера (стоп и тейк) объёмом текущей позиции.
  • Трейлинг активируется, когда плавающая прибыль достигает TrailingTriggerPips:
    • Для лонга стоп следует за ценой на расстоянии TrailingStopPips.
    • Для шорта стоп держится на той же дистанции выше цены.
    • Перестановка выполняется, только если улучшение не меньше TrailingStepPips, чтобы избежать излишнего шума.
  • При закрытии позиции все защитные заявки отменяются.

Фильтры по времени и спреду

  • Временное окно задаётся параметрами UseTimeFilter, StartHour, StartMinute, EndHour, EndMinute. Окно работает по принципу [начало, конец), ночной режим поддерживается (если конец раньше начала, окно проходит через полночь).
  • Спред-фильтр сравнивает текущий спред в шагах цены (BestAsk - BestBid) с MaxSpreadPoints. Если котировки отсутствуют, проверка пропускается.

Особенности реализации

  • Candles подписываются через SubscribeCandles().Bind(...); значения EMA(10) и EMA(200) поступают напрямую, а канал и средний диапазон обновляются внутри обработчика без GetValue.
  • Для повторения CopyBuffer хранятся только последние две свечи и соответствующие значения индикаторов.
  • Стопы и трейлинг реализованы через высокоуровневые методы BuyStop, SellStop, BuyLimit, SellLimit, что повторяет PositionModify из MetaTrader.
  • Расчёт риска опирается на Portfolio.CurrentValue или Portfolio.BeginValue; при нулевых данных включается fallback.

Параметры

Параметр Описание Значение по умолчанию
KeltnerPeriod Период EMA и сглаживания диапазона. 50
FastEmaPeriod Длина быстрой EMA. 10
SlowEmaPeriod Длина медленной EMA. 200
BaseVolume Базовый торговый объём. 0.01
UseRiskPercent Использовать расчёт по проценту риска. true
RiskPercent Процент капитала на сделку. 1
StopLossPips Размер стоп-лосса в пунктах. 500
TakeProfitPips Размер тейк-профита в пунктах (0 — отключить). 500
TrailingTriggerPips Прибыль для включения трейлинга. 300
TrailingStopPips Расстояние трейлинг-стопа. 300
TrailingStepPips Минимальное улучшение для переноса стопа. 100
UseTimeFilter Включить торговое окно. true
StartHour, StartMinute Время начала окна. 02:30
EndHour, EndMinute Время окончания (исключительно). 21:00
MaxSpreadPoints Максимальный допустимый спред (0 — без ограничения). 65
CandleType Таймфрейм сигналов. 5-минутные свечи

Отличия от оригинала MetaTrader

  • Перенос трейлинг-стопа выполняется стоп-заявками на бирже, что эквивалентно PositionModify, но требует подтверждения торговой площадки.
  • Средняя ширина канала считается через SMA(high-low), как и в исходном коде.
  • Для оценки доступных средств используется стоимость портфеля, что может слегка отличаться от FreeMargin в MetaTrader, но сохраняет концепцию риска в процентах.
  • При отсутствии котировок best bid/ask спред-фильтр пропускается, что соответствует поведению советника при «плавающем» спреде.

Рекомендации

  • Изначально алгоритм рассчитан на XAUUSD; для других инструментов оптимизируйте периоды EMA и ширину канала.
  • Убедитесь, что значения капитала портфеля доступны, если используется режим процента риска.
  • Контролируйте шаг цены инструмента: параметры в пунктах предполагают форекс-лот (3 или 5 знаков после запятой).
using System;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Port of the MetaTrader expert "KA-Gold Bot".
/// Trades breakouts of a Keltner-style channel confirmed by trend filters from fast and slow EMA.
/// Buys when close breaks above upper Keltner band and fast EMA is above slow EMA.
/// Sells when close breaks below lower Keltner band and fast EMA is below slow EMA.
/// Exits when price crosses the opposite Keltner band or when EMA trend reverses.
/// </summary>
public class KAGoldBotStrategy : Strategy
{
	private readonly StrategyParam<int> _keltnerPeriod;
	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private readonly StrategyParam<decimal> _bandMultiplier;

	private ExponentialMovingAverage _fastEma;
	private ExponentialMovingAverage _slowEma;
	private ExponentialMovingAverage _keltnerEma;
	private SimpleMovingAverage _rangeAverage;

	private bool _prevAboveUpper;
	private bool _prevBelowLower;
	private decimal _entryPrice;

	/// <summary>
	/// Keltner channel length used for the midline EMA and range average.
	/// </summary>
	public int KeltnerPeriod
	{
		get => _keltnerPeriod.Value;
		set => _keltnerPeriod.Value = value;
	}

	/// <summary>
	/// Fast EMA period for crossover signal.
	/// </summary>
	public int FastEmaPeriod
	{
		get => _fastEmaPeriod.Value;
		set => _fastEmaPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period for trend filter.
	/// </summary>
	public int SlowEmaPeriod
	{
		get => _slowEmaPeriod.Value;
		set => _slowEmaPeriod.Value = value;
	}

	/// <summary>
	/// Multiplier for Keltner channel band width.
	/// </summary>
	public decimal BandMultiplier
	{
		get => _bandMultiplier.Value;
		set => _bandMultiplier.Value = value;
	}

	/// <summary>
	/// Candle type used for signal generation.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public KAGoldBotStrategy()
	{
		_keltnerPeriod = Param(nameof(KeltnerPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Keltner Period", "Length of the EMA and range average", "Indicators");

		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Period of the fast EMA filter", "Indicators");

		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Period of the slow EMA trend filter", "Indicators");

		_bandMultiplier = Param(nameof(BandMultiplier), 3m)
			.SetGreaterThanZero()
			.SetDisplay("Band Multiplier", "Multiplier for Keltner band width", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for calculations", "General");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_fastEma = new ExponentialMovingAverage { Length = FastEmaPeriod };
		_slowEma = new ExponentialMovingAverage { Length = SlowEmaPeriod };
		_keltnerEma = new ExponentialMovingAverage { Length = KeltnerPeriod };
		_rangeAverage = new SimpleMovingAverage { Length = KeltnerPeriod };

		_prevAboveUpper = false;
		_prevBelowLower = false;
		_entryPrice = 0;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_fastEma, _slowEma, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _fastEma);
			DrawIndicator(area, _slowEma);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var close = candle.ClosePrice;

		// Process Keltner EMA and range average manually (IsFinal=true for finished candles)
		var midResult = _keltnerEma.Process(new DecimalIndicatorValue(_keltnerEma, close, candle.OpenTime) { IsFinal = true });
		var rangeResult = _rangeAverage.Process(new DecimalIndicatorValue(_rangeAverage, candle.HighPrice - candle.LowPrice, candle.OpenTime) { IsFinal = true });

		if (!_keltnerEma.IsFormed || !_rangeAverage.IsFormed)
			return;

		var mid = midResult.GetValue<decimal>();
		var avgRange = rangeResult.GetValue<decimal>();
		var upper = mid + avgRange * BandMultiplier;
		var lower = mid - avgRange * BandMultiplier;

		var aboveUpper = close > upper;
		var belowLower = close < lower;

		// Exit logic: close crosses opposite band
		if (Position > 0 && close < lower)
		{
			SellMarket();
		}
		else if (Position < 0 && close > upper)
		{
			BuyMarket();
		}

		// Entry logic: Keltner breakout + EMA trend confirmation
		if (Position == 0)
		{
			// Buy: close breaks above upper band, fast EMA above slow EMA
			if (!_prevAboveUpper && aboveUpper && fastValue > slowValue)
			{
				BuyMarket();
				_entryPrice = close;
			}
			// Sell: close breaks below lower band, fast EMA below slow EMA
			else if (!_prevBelowLower && belowLower && fastValue < slowValue)
			{
				SellMarket();
				_entryPrice = close;
			}
		}

		_prevAboveUpper = aboveUpper;
		_prevBelowLower = belowLower;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_fastEma = null;
		_slowEma = null;
		_keltnerEma = null;
		_rangeAverage = null;
		_prevAboveUpper = false;
		_prevBelowLower = false;
		_entryPrice = 0;

		base.OnReseted();
	}
}