Открыть на GitHub

Стратегия Averaging By Signal

Обзор

Averaging By Signal Strategy — это порт советника MetaTrader AveragingBySignal.mq4 на высокоуровневый API StockSharp. Оригинальный эксперт объединял вход по пересечению скользящих средних, мартингейл-усреднение, общее тейк-профит-сопровождение и опциональный трейлинг, активный только для первой сделки. Версия на C# воспроизводит те же элементы и адаптирует их под неттинговую модель исполнения StockSharp и систему индикаторов библиотеки.

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

  1. Подписка на выбранный таймфрейм (CandleType) и расчёт двух скользящих средних с заданными периодами и методами (FastPeriod/FastMethod, SlowPeriod/SlowMethod).
  2. Анализ только закрытых свечей: после формирования бара сравниваются предыдущие и текущие значения обеих средних, чтобы выявить пересечение.
  3. Сигналы:
    • быстрое пересечение вверх → длинный сигнал;
    • быстрое пересечение вниз → короткий сигнал;
    • отсутствие пересечения → никаких действий.
  4. При новом длинном сигнале и отсутствии открытой длинной корзины отправляется рыночная заявка на покупку с базовым объёмом.
  5. При новом коротком сигнале аналогично открывается первая короткая позиция.
  6. Правила усреднения:
    • расстояние до следующего слоя задаётся параметром LayerDistancePips в пипсах MetaTrader;
    • если AveragingBySignal = true, дополнительные сделки разрешены только после подтверждающего сигнала; при false достаточно достижения ценового уровня;
    • логика для коротких позиций зеркальная;
    • объём каждой новой сделки рассчитывается по выбранному режиму LotSizing, количество сделок ограничено MaxLayers.
  7. Управление корзиной:
    • все исполненные ордера сохраняются по принципу FIFO, чтобы восстановить среднюю цену длинной и короткой корзины;
    • общий тейк-профит рассчитывается как средняя цена ± TakeProfitPips. Достижение уровня закрывает всю корзину одной операцией;
    • если EnableTrailing включён и в корзине только один ордер, после TrailingStartPips прибыли активируется трейлинг. Стоп подтягивается при улучшении цены минимум на TrailingStepPips.
  8. Стратегия работает в неттинговой схеме: противоположные сигналы сначала гасят текущую позицию, затем формируют новую корзину.

Управление объёмом и расчёт пипсов

  • InitialVolume задаёт базовый лот. При режиме LotSizing = Multiplier каждый следующий слой умножает базу на Multiplier^номер слоя, как в MQL.
  • Запрашиваемый объём приводится к VolumeStep, MinVolume и MaxVolume инструмента, чтобы заявки соответствовали биржевым ограничениям.
  • Размер пипса вычисляется из Security.PriceStep с учётом «двойных» котировок: пятизначные пары используют шаг 0.0001, четырёхзначные — исходный шаг.

Параметры

Имя Тип Значение по умолчанию Описание
CandleType DataType таймфрейм 1 час Таймфрейм для индикаторов.
InitialVolume decimal 0.1 Объём первой сделки в корзине.
LotSizing LotSizingMode Multiplier Фиксированные лоты или геометрическое наращивание.
Multiplier decimal 2 Множитель объёма при усреднении (для режима Multiplier).
FastPeriod int 28 Период быстрой скользящей средней.
FastMethod MovingAverageMethod LinearWeighted Метод расчёта быстрой средней.
SlowPeriod int 50 Период медленной скользящей средней.
SlowMethod MovingAverageMethod Smoothed Метод расчёта медленной средней.
TakeProfitPips int 15 Расстояние общего тейк-профита (0 отключает уровень).
AveragingBySignal bool true Требовать новый сигнал для открытия следующего слоя.
LayerDistancePips decimal 10 Минимальное неблагоприятное движение (в пипсах) до усреднения.
MaxLayers int 10 Максимум сделок в одну сторону вместе с первой.
EnableTrailing bool false Включение трейлинг-стопа для одиночных позиций.
TrailingStartPips decimal 10 Прибыль, после которой активируется трейлинг.
TrailingStepPips decimal 1 Минимальное улучшение цены для подтягивания стопа.

Отличия от оригинального советника

  • В MetaTrader 4 допускалось хеджирование. StockSharp использует неттинг, поэтому при смене сигнала новый ордер сначала закрывает противоположный объём, затем формирует новую корзину.
  • Общий тейк-профит реализован через рыночное закрытие, а не через OrderModify для каждого тикета.
  • Трейлинг выполняется по ценам закрытия свечей. В MQL стоп обновлялся на тиковом уровне, поэтому реакция может быть немного позже, но пороговые значения совпадают.
  • Проверки свободной маржи и ручное управление проскальзыванием убраны: контроль выполняется брокером StockSharp.

Рекомендации по использованию

  • Убедитесь, что у инструмента корректно заполнены PriceStep, VolumeStep, MinVolume и MaxVolume, иначе пересчёт пипсов и объёмов будет некорректным.
  • Всегда держите FastPeriod строго меньше SlowPeriod — стратегия автоматически остановится, если пересечение станет невозможным.
  • Отключайте AveragingBySignal, если нужно классическое ценовое усреднение без подтверждающего сигнала.
  • На более низких таймфреймах выходы отрабатывают быстрее, но возрастает шум и число усреднений.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Averaging By Signal strategy: WMA crossover with averaging logic.
/// Buys when fast WMA crosses above slow WMA, sells on cross below.
/// </summary>
public class AveragingBySignalStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }

	public AveragingBySignalStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast WMA", "Fast WMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow WMA", "Slow WMA period", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0;
		_prevSlow = 0;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = 0;
		_prevSlow = 0;
		_hasPrev = false;
		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, slow, ProcessCandle).Start();
	}

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

		if (_hasPrev)
		{
			if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
				BuyMarket();
			else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
				SellMarket();
		}
		else
		{
			if (fastValue > slowValue && Position <= 0)
				BuyMarket();
			else if (fastValue < slowValue && Position >= 0)
				SellMarket();
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
		_hasPrev = true;
	}
}