Открыть на GitHub

FiftyFiveMaBarComparisonStrategy

Обзор

Стратегия повторяет эксперта MetaTrader 5 «55 MA»: сравнивает значения 55-периодной скользящей средней на двух барах и открывает позицию, как только разница превышает заданный порог. Расчёты выполняются только по закрытым свечам в пределах пользовательского торгового окна, при необходимости направление сигналов можно инвертировать. Алгоритм полностью сохраняет исходное поведение — при отсутствии условий для покупки всегда открывается короткая позиция.

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

  1. Подписка на выбранный тип свечей и расчёт скользящей средней с заданными параметрами периода, метода и используемой цены.
  2. Ведение буфера последних значений, чтобы получать MA для индексов BarA и BarB, даже если включён горизонтальный сдвиг.
  3. Для каждой завершённой свечи внутри интервала [StartHour, EndHour):
    • Получить MA в точках BarA + MaShift и BarB + MaShift.
    • Если значение в BarA больше значения в BarB на величину, превосходящую DifferenceThreshold, открыть длинную позицию (или короткую при включённом ReverseSignals).
    • Если значение в BarA меньше значения в BarB на величину, превосходящую DifferenceThreshold, открыть короткую позицию (или длинную при ReverseSignals).
    • Если ни одно условие не выполнено, стратегия, как и оригинальный советник, инициирует продажу.
  4. Заявки отправляются по рынку с объёмом Volume. При активном CloseOppositePositions объём увеличивается, чтобы закрыть встречную позицию перед открытием новой.
  5. Опциональные стоп-лосс и тейк-профит подключаются через StartProtection. Дистанции задаются в пунктах (pip); для инструментов с 3 или 5 знаками после запятой один пункт равен PriceStep * 10.

Параметры

Имя Тип Значение по умолчанию Описание
CandleType DataType Таймфрейм 1 минута Ряд свечей, по которому ведётся расчёт и формируются сигналы.
StopLossPips int 30 Размер стоп-лосса в пунктах. 0 — отключить.
TakeProfitPips int 50 Размер тейк-профита в пунктах. 0 — отключить.
StartHour int 8 Начало торгового окна (включительно, часы 0–23).
EndHour int 21 Конец торгового окна (не включительно). Должен быть больше StartHour.
DifferenceThreshold decimal 0.0001 Минимальная абсолютная разница между сравниваемыми MA для генерации сигнала.
BarA int 0 Индекс первого бара для сравнения (0 — текущая свеча).
BarB int 1 Индекс второго бара.
ReverseSignals bool false Инверсия условий на покупку/продажу.
CloseOppositePositions bool false При включении закрывает встречные позиции за счёт увеличения объёма сделки.
MaShift int 0 Горизонтальный сдвиг скользящей средней; положительные значения обращаются к более ранним значениям.
MaLength int 55 Период скользящей средней.
MaMethod MovingAverageMethods Exponential Метод сглаживания (Simple, Exponential, Smoothed, Weighted).
AppliedPrice AppliedPriceTypes Median Тип цены для расчёта (Close, Open, High, Low, Median, Typical, Weighted).

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

  • Базовый объём задаётся свойством стратегии Volume; при активном CloseOppositePositions он автоматически дополняется величиной встречной позиции.
  • StartProtection вызывается только если расстояние стоп-лосса или тейк-профита больше нуля.

Примечания

  • Торговое окно определяется временем инструмента: сигналы вне диапазона [StartHour, EndHour) игнорируются.
  • При отрицательном MaShift стратегия ожидает накопления истории, аналогично тому, как исходный советник возвращает EMPTY_VALUE для смещённых буферов.
  • Из-за сохранения оригинальной логики при отсутствии условий на покупку выполняется продажа. При необходимости можно увеличить DifferenceThreshold, чтобы уменьшить количество таких входов.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// 55 MA bar comparison strategy. Compares candle body with MA direction.
/// </summary>
public class FiftyFiveMaBarComparisonStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _maPeriod;

	private decimal? _prevMa;

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

	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	public FiftyFiveMaBarComparisonStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_maPeriod = Param(nameof(MaPeriod), 55)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Moving average period", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevMa = null;
	}

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

		_prevMa = null;

		var sma = new SimpleMovingAverage { Length = MaPeriod };

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

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevMa = maVal;
			return;
		}

		if (_prevMa == null)
		{
			_prevMa = maVal;
			return;
		}

		var close = candle.ClosePrice;
		var bullishBar = candle.ClosePrice > candle.OpenPrice;
		var bearishBar = candle.ClosePrice < candle.OpenPrice;
		var maRising = maVal > _prevMa.Value;
		var maFalling = maVal < _prevMa.Value;

		_prevMa = maVal;

		// Bullish bar + rising MA + close above MA → buy
		if (bullishBar && maRising && close > maVal && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Bearish bar + falling MA + close below MA → sell
		else if (bearishBar && maFalling && close < maVal && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
	}
}