Открыть на GitHub

Шаблон торгового робота AIS3

Обзор

Шаблон AIS3 — это прорывная стратегия MetaTrader, использующая два согласованных таймфрейма. Основной таймфрейм (по умолчанию 15 минут) фиксирует максимум, минимум, закрытие и диапазон предыдущей свечи. Вспомогательный таймфрейм (по умолчанию 1 минута) измеряет краткосрочную волатильность, чтобы управлять динамическим трейлингом. В StockSharp стратегия переписана поверх высокоуровневого API, поэтому её можно запускать в Designer, Shell или собственном приложении без изменений в логике.

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

  • Подписки на данные: стратегия подписывается на два потока свечей и на книгу заявок/Level-1, чтобы получать актуальные цены bid/ask аналогично вызовам MarketInfo в MQL4.
  • Проверка прорыва:
    • Лонг допускается, если закрытие предыдущей свечи выше её середины, а текущая цена ask пробивает максимум плюс измеренный спред. Сделка открывается по текущему ask.
    • Шорт открывается, когда закрытие ниже середины и bid пробивает предыдущий минимум.
    • Перед входом проверяется, что расстояния до стопа и тейка превышают минимальный буфер, а сам стоп остаётся по правильную сторону от цены входа даже с учётом спреда.
  • Защитные уровни:
    • Стоп-лосс равен диапазон_основного_таймфрейма × StopMultiplier и размещается над максимумом (для лонга) или под минимумом (для шорта) пробойной свечи.
    • Тейк-профит равен диапазон_основного_таймфрейма × TakeMultiplier и откладывается от цены входа в сторону позиции.
  • Ведение позиции:
    • Расстояние трейлинг-стопа вычисляется как диапазон_вспомогательного_таймфрейма × TrailMultiplier.
    • Стоп передвигается только когда позиция в прибыли, новый уровень не нарушает стоп/фриз буферы и превышает предыдущий минимум на величину TrailStepMultiplier × спред.
    • При достижении стопа или тейка заявка закрывается рыночным ордером.

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

  • Резерв счёта: параметр AccountReserve резервирует часть капитала. Если после открытия сделки резерв опустился бы ниже допустимого уровня, позиция не открывается.
  • Резерв на сделку: OrderReserve ограничивает долю доступного капитала в каждой сделке. Объём рассчитывается как riskBudget / |entry - stop| и приводится к шагу объёма инструмента. При отсутствии данных по портфелю используется BaseVolume.
  • Буферы стопа и фриза: параметры StopBufferTicks и FreezeBufferTicks эмулируют ограничения брокера (MODE_STOPLEVEL, MODE_FREEZELEVEL), переводя минимальное расстояние в цену через шаг котировки.
  • Минимальный шаг трейлинга: TrailStepMultiplier соответствует коэффициенту acd.TrailStepping в шаблоне MetaTrader и гарантирует, что между двумя обновлениями стопа цена пройдёт не меньше одного спреда.

Параметры

Параметр Описание
AccountReserve Доля капитала, зарезервированная под просадки (0–0,95).
OrderReserve Доля доступного капитала, выделяемая на риск одной сделки (по умолчанию до 0,5).
PrimaryCandleType Таймфрейм для определения прорыва (15 минут по умолчанию).
SecondaryCandleType Быстрый таймфрейм для расчёта трейлинг-стопа (1 минута по умолчанию).
TakeMultiplier Множитель основного диапазона для тейк-профита.
StopMultiplier Множитель основного диапазона для стоп-лосса.
TrailMultiplier Множитель вспомогательного диапазона для трейлинга.
BaseVolume Резервный объём при отсутствии данных по портфелю.
StopBufferTicks Минимальный зазор (в тиках) между ценой входа и стопом/тейком.
FreezeBufferTicks Дополнительный зазор для учёта «freeze»-зоны брокера.
TrailStepMultiplier Коэффициент спреда, задающий минимальный шаг обновления трейлинг-стопа.

Рекомендации по применению

  • Перед запуском убедитесь, что доступны обе серии свечей и поток лучших цен bid/ask — без них логика прорывов будет искажена.
  • Базовые значения (TakeMultiplier = 1, StopMultiplier = 2, TrailMultiplier = 3) повторяют пример из MQL4. Их можно адаптировать под особенности инструмента.
  • Трейлинг реализован виртуально: стратегия закрывает позицию рыночным ордером, не модифицируя заявки, как и в оригинальном EA.
  • В конструкторе вызывается StartProtection(), поэтому аварийные остановы остаются активны даже при временных сбоях связи.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// AIS3 Trading Robot: breakout strategy with ATR-based stops and trailing.
/// Enters on breakout above/below previous candle range with EMA filter.
/// </summary>
public class Ais3TradingRobotTemplateStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _takeMultiplier;
	private readonly StrategyParam<decimal> _stopMultiplier;

	private decimal _prevHigh;
	private decimal _prevLow;
	private decimal _entryPrice;
	private decimal _stopPrice;

	public Ais3TradingRobotTemplateStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_emaLength = Param(nameof(EmaLength), 20)
			.SetDisplay("EMA Length", "EMA period for trend filter.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");

		_takeMultiplier = Param(nameof(TakeMultiplier), 2.0m)
			.SetDisplay("Take Multiplier", "ATR multiplier for TP.", "Risk");

		_stopMultiplier = Param(nameof(StopMultiplier), 1.5m)
			.SetDisplay("Stop Multiplier", "ATR multiplier for SL.", "Risk");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	public decimal TakeMultiplier
	{
		get => _takeMultiplier.Value;
		set => _takeMultiplier.Value = value;
	}

	public decimal StopMultiplier
	{
		get => _stopMultiplier.Value;
		set => _stopMultiplier.Value = value;
	}

	/// <inheritdoc />
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevHigh = 0;
		_prevLow = 0;
		_entryPrice = 0;
		_stopPrice = 0;
	}

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

		_prevHigh = 0;
		_prevLow = 0;
		_entryPrice = 0;
		_stopPrice = 0;

		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ema, atr, ProcessCandle)
			.Start();

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

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

		if (_prevHigh == 0 || atrVal <= 0)
		{
			_prevHigh = candle.HighPrice;
			_prevLow = candle.LowPrice;
			return;
		}

		var close = candle.ClosePrice;
		var takeDistance = atrVal * TakeMultiplier;
		var stopDistance = atrVal * StopMultiplier;

		// Manage position
		if (Position > 0)
		{
			if (close - _entryPrice >= takeDistance)
			{
				SellMarket();
				_entryPrice = 0;
				_stopPrice = 0;
			}
			else if (_stopPrice > 0 && close <= _stopPrice)
			{
				SellMarket();
				_entryPrice = 0;
				_stopPrice = 0;
			}
			else
			{
				var trail = close - stopDistance;
				if (trail > _stopPrice) _stopPrice = trail;
			}
		}
		else if (Position < 0)
		{
			if (_entryPrice - close >= takeDistance)
			{
				BuyMarket();
				_entryPrice = 0;
				_stopPrice = 0;
			}
			else if (_stopPrice > 0 && close >= _stopPrice)
			{
				BuyMarket();
				_entryPrice = 0;
				_stopPrice = 0;
			}
			else
			{
				var trail = close + stopDistance;
				if (trail < _stopPrice || _stopPrice == 0) _stopPrice = trail;
			}
		}

		// Entry on breakout + EMA filter
		if (Position == 0)
		{
			if (close > _prevHigh && close > emaVal)
			{
				_entryPrice = close;
				_stopPrice = close - stopDistance;
				BuyMarket();
			}
			else if (close < _prevLow && close < emaVal)
			{
				_entryPrice = close;
				_stopPrice = close + stopDistance;
				SellMarket();
			}
		}

		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
	}
}