Открыть на GitHub

Стратегия AIS4 Trade Machine

Общее описание

AIS4 Trade Machine Strategy — это порт ручного советника «AIS4 Trade Machine» из MetaTrader в StockSharp. Стратегия сохраняет концепцию «одна позиция за раз»: трейдер задаёт абсолютные уровни Stop Loss и Take Profit, выбирает команду, а стратегия рассчитывает рабочий объём с учётом текущей стоимости портфеля и параметров инструмента. После исполнения рыночной заявки автоматически выставляется пара защитных ордеров (стоп + лимит), чтобы зафиксировать заданные уровни риска и цели прямо на бирже.

Стратегия не генерирует собственных сигналов — она служит помощником для дискреционной торговли.

Пошаговая работа

  1. Убедитесь, что у выбранного инструмента заданы PriceStep, StepPrice, VolumeStep, MinVolume и MaxVolume. Эти значения необходимы для перевода ценового риска в количество контрактов и для корректной привязки объёма к биржевым ограничениям.
  2. Перед отправкой команды задайте уровни StopPrice и TakePrice в абсолютных ценах.
  3. Установите Command в значение Buy или Sell. Стратегия:
    • Проверяет, что нет другой открытой позиции.
    • Контролирует, чтобы указанные уровни соблюдали минимальную ценовую дистанцию.
    • Вычисляет риск на сделку как OrderReserve × текущая стоимость портфеля и сверяет его с резервом капитала (AccountReserve).
    • Оценивает объём сделки через расстояние до стопа и стоимость тика инструмента.
    • Отправляет рыночный ордер и сразу регистрирует защитные заявки (SellStop + SellLimit для лонга, BuyStop + BuyLimit для шорта).
  4. После обработки команда автоматически возвращается в состояние Wait, что предотвращает повторный запуск.

Управление активной позицией

  • Измените уровни (можно оставить 0, чтобы сохранить текущие значения) и установите Command = Modify. Стратегия отменит предыдущие защитные ордера и создаст новые по обновлённым ценам.
  • Установите Command = Close, чтобы немедленно закрыть позицию по рынку и отменить все защитные заявки.

Логика управления рисками

  • AccountReserve — доля капитала, которая всегда остаётся в резерве. Пока доступная часть (equity - peak_equity × (1 - AccountReserve)) меньше требуемого риска на сделку, торговля блокируется.
  • OrderReserve — доля текущей стоимости портфеля, выделяемая на следующую сделку. Этот бюджет переводится в объём через расстояние до стопа и стоимость шага цены (PriceStep × StepPrice).
  • Если рассчитанный объём меньше MinVolume или нарушает шаг объёма VolumeStep, команда отклоняется с предупреждением в логе.

Параметры

Параметр Значение по умолчанию Описание
Command Wait Ручная команда (Buy, Sell, Modify, Close). После обработки возвращается в Wait.
StopPrice 0 Абсолютный уровень Stop Loss. Для лонга должен быть ниже цены входа, для шорта — выше.
TakePrice 0 Абсолютный уровень Take Profit. Для лонга — выше цены входа, для шорта — ниже.
AccountReserve 0.20 Доля капитала, резервируемая «всегда». Чем выше значение, тем больше подушка безопасности нужна для новых сделок.
OrderReserve 0.04 Доля капитала, выделяемая под риск одной сделки. Используется при расчёте объёма через дистанцию до стопа.
CandleType Таймфрейм 1 минута Свечной поток, по которому считываются цены для валидации и журналирования.

Замечания и ограничения

  • Одновременно поддерживается только одна позиция — так же, как в оригинальном советнике.
  • Команды, нарушающие минимальные дистанции, резерв капитала или ограничения по объёму, игнорируются; соответствующее предупреждение записывается в лог.
  • Защитные заявки переотправляются при каждом изменении или новом исполнении, чтобы объём совпадал с фактической позицией.
  • Для корректной работы необходимы точные значения PriceStep и StepPrice. Без них стратегия не сможет безопасно рассчитать объём сделки.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// AIS Trade Machine: EMA crossover strategy with ATR-based risk management.
/// Entry on EMA cross confirmed by RSI, exit on reversal or ATR stop.
/// </summary>
public class AisTradeMachineStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _stopMultiplier;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private decimal _stopPrice;

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

		_fastLength = Param(nameof(FastLength), 10)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowLength = Param(nameof(SlowLength), 30)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");

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

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

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

	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

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

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

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

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_stopPrice = 0;
	}

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

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_stopPrice = 0;

		var fast = new ExponentialMovingAverage { Length = FastLength };
		var slow = new ExponentialMovingAverage { Length = SlowLength };
		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, rsi, atr, ProcessCandle)
			.Start();

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

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

		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		var close = candle.ClosePrice;
		var stopDist = atrVal * StopMultiplier;

		var bullishCross = _prevFast <= _prevSlow && fastVal > slowVal;
		var bearishCross = _prevFast >= _prevSlow && fastVal < slowVal;

		// Stop management
		if (Position > 0 && _stopPrice > 0 && close <= _stopPrice)
		{
			SellMarket();
			_entryPrice = 0;
			_stopPrice = 0;
		}
		else if (Position < 0 && _stopPrice > 0 && close >= _stopPrice)
		{
			BuyMarket();
			_entryPrice = 0;
			_stopPrice = 0;
		}

		// Exit on opposite cross
		if (Position > 0 && bearishCross)
		{
			SellMarket();
			_entryPrice = 0;
			_stopPrice = 0;
		}
		else if (Position < 0 && bullishCross)
		{
			BuyMarket();
			_entryPrice = 0;
			_stopPrice = 0;
		}

		// Trail stop
		if (Position > 0)
		{
			var trail = close - stopDist;
			if (trail > _stopPrice) _stopPrice = trail;
		}
		else if (Position < 0 && _stopPrice > 0)
		{
			var trail = close + stopDist;
			if (trail < _stopPrice) _stopPrice = trail;
		}

		// Entry on cross + RSI confirmation
		if (Position == 0)
		{
			if (bullishCross && rsiVal > 50)
			{
				_entryPrice = close;
				_stopPrice = close - stopDist;
				BuyMarket();
			}
			else if (bearishCross && rsiVal < 50)
			{
				_entryPrice = close;
				_stopPrice = close + stopDist;
				SellMarket();
			}
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}