Открыть на GitHub

Стратегия Executer AC

Executer AC — точный порт оригинального советника MetaTrader 5, использующего индикатор Билла Вильямса Accelerator Oscillator (AC). Реализация на StockSharp полностью повторяет структуру сигналов MQL5-версии и добавляет удобные параметры для управления риском и визуализации на графике.

Логика работы

Стратегия обрабатывает только закрытые свечи выбранного таймфрейма и анализирует четыре последних значения AC:

  • AC[0] — значение на только что закрывшейся свече (в исходном коде это ac[1]).
  • AC[1], AC[2], AC[3] — более старые значения, используемые для определения последовательности столбиков.

Последовательность действий полностью соответствует MQL5-советнику:

  1. Управление открытой позицией
    • Длинная позиция закрывается, если AC[0] < AC[1] (ускорение снижается).
    • Короткая позиция закрывается, если AC[0] > AC[1] (ускорение растёт).
    • Активируется трейлинг-стоп, который подтягивает защитный стоп после прохождения TrailingStop + TrailingStep пунктов прибыли.
  2. Поиск входа при отсутствии позиции
    • Рост выше нулевой линии: при AC[0] > 0, AC[1] > 0 и AC[0] > AC[1] > AC[2] открывается покупка.
    • Падение выше нулевой линии: при AC[0] > 0, AC[1] > 0 и AC[0] < AC[1] < AC[2] < AC[3] открывается продажа.
    • Рост ниже нулевой линии: при AC[0] < 0, AC[1] < 0 и AC[0] > AC[1] > AC[2] > AC[3] открывается покупка.
    • Падение ниже нулевой линии: при AC[0] < 0, AC[1] < 0 и AC[0] < AC[1] < AC[2] открывается продажа.
    • Пересечения нуля: переход сверху вниз (AC[0] > 0, AC[1] < 0) приводит к покупке, переход снизу вверх (AC[0] < 0, AC[1] > 0) — к продаже.

Все проверки выполняются только после подтверждения, что индикатор сформирован и торговля разрешена.

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

  • Стоп-лосс и тейк-профит задаются в пунктах и автоматически переводятся в ценовые единицы с учётом шага цены инструмента. Нулевое значение отключает уровень.
  • Трейлинг-стоп реализован аналогично оригиналу: после прохождения прибыли, превышающей сумму TrailingStop и TrailingStep, стоп переносится к текущей цене на расстояние TrailingStop (для покупок вниз, для продаж вверх) и требует дальнейшего улучшения не меньше шага TrailingStep.
  • Защита позиции: метод StartProtection() активирует штатные механизмы StockSharp для контроля разрыва соединения.

Настраиваемые параметры

Параметр Описание
TradeVolume Объём сделки. Приводится к допустимому шагу и ограничениям инструмента.
StopLossPips Дистанция стоп-лосса в пунктах. 0 — стоп отключён.
TakeProfitPips Дистанция тейк-профита в пунктах. 0 — цель отключена.
TrailingStopPips Базовая дистанция трейлинг-стопа в пунктах.
TrailingStepPips Минимальное дополнительное движение в пунктах перед следующим подтягиванием трейлинга.
CandleType Таймфрейм свечей, на которых рассчитывается индикатор AC.

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

  • Для трёх- и пятизнаковых форекс-инструментов размер пункта корректируется (шаг цены умножается на 10), как и в MetaTrader.
  • История AC хранится в массиве фиксированного размера, что позволяет точно повторить сравнения ac[1]…ac[4] без запросов к истории сделок или свечей.
  • После закрытия позиции обработчик немедленно выходит, поэтому на той же свече не выполняются новые входы — как и в исходном советнике.
  • Перемещение трейлинг-стопа одновременно обновляет внутреннее состояние и фактический уровень стоп-лосса, имитируя вызов PositionModify.

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

  1. Выбирайте таймфрейм, соответствующий рабочему стилю (изначально стратегия применялась на внутридневных графиках Forex).
  2. Подбирайте параметры стопов и трейлинга под волатильность инструмента: слишком малые значения приведут к частым выбиваниям.
  3. Сочетайте стратегию с внешними ограничениями риска (например, брокерскими стопами или контролем по счёту).
  4. При одновременном запуске нескольких стратегий учитывайте совокупную загрузку депозита и лимиты по инструменту.
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>
/// Executer AC strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class ExecuterAcStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public ExecuterAcStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}