Открыть на GitHub

Скользящая средняя с фреймами

Конвертация советника MetaTrader 5 «Moving Average with Frames». Оригинальная система оценивает взаимное положение цены открытия и закрытия бара относительно смещённой простой скользящей средней (SMA) и выводит несколько линий баланса («фреймы») после оптимизации. В версии для StockSharp реализована торговая логика: обработка только завершённых баров, работа в неттинговом режиме с одной позицией и сохранение правил управления капиталом исходника.

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

  • Источник данных – стратегия подписывается на выбранный таймфрейм (CandleType) и обрабатывает только финальные свечи, повторяя условие MetaTrader if(rt[1].tick_volume>1) return;.
  • Индикатор – простая скользящая средняя с периодом MovingPeriod. Выход SMA сдвигается вперёд на MovingShift закрытых баров за счёт внутреннего буфера значений.
  • Прогрев – до накопления минимум 100 завершённых свечей входы запрещены, как и в оригинале (Bars(_Symbol,_Period)>100).
  • Условия входа
    • Покупка – бар открывается ниже смещённой SMA и закрывается выше неё.
    • Продажа – бар открывается выше смещённой SMA и закрывается ниже неё.
    • Стратегия удерживает единственную позицию: при смене направления противоположная позиция закрывается перед открытием новой.
  • Выходы – лонг закрывается, когда цена открытия выше, а закрытия ниже смещённой SMA; шорт закрывается на обратном пересечении. После закрытия на том же баре новая сделка не открывается, что соответствует поведению MQL5.

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

  • MaximumRisk – рассчитывает базовый объём как Portfolio.CurrentValue * MaximumRisk / price, если брокер предоставляет стоимость портфеля. При отсутствии данных используется свойство Volume.
  • DecreaseFactor – после более чем одного подряд убыточного выхода объём уменьшается на volume * losses / DecreaseFactor, полностью копируя алгоритм MetaTrader. Любая прибыльная сделка сбрасывает счётчик.
  • Нормализация объёма – итоговый объём приводится к шагу VolumeStep, ограничивается диапазоном [MinVolume, MaxVolume] и округляется до двух знаков, если биржа не публикует шаг.

Дополнительные замечания

  • Отрисовка оптимизационных «фреймов» не переносилась, так как StockSharp предоставляет собственные инструменты визуализации результатов. Торговые правила и тайминг сигналов сохранены.
  • Значения индикатора берутся напрямую из обработчика Bind, вызовов GetValue нет.
  • Учёт серии убыточных сделок реализован в OnOwnTradeReceived, что корректно обрабатывает частичные исполнения и неттинговую модель.

Параметры

Параметр Значение по умолчанию Описание
MaximumRisk 0.02 Доля капитала, используемая при открытии позиции.
DecreaseFactor 3 Делитель, уменьшающий объём после двух и более подряд убыточных сделок.
MovingPeriod 12 Период простой скользящей средней по ценам закрытия.
MovingShift 6 Количество завершённых свечей, на которое сдвигается SMA вперёд по времени.
CandleType Таймфрейм 1 час Основной поток свечей для расчётов.

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

  1. Привяжите стратегию к нужному инструменту и портфелю в Designer либо в коде.
  2. Подберите таймфрейм, соответствующий графику MetaTrader, на котором использовался советник.
  3. Настройте MaximumRisk и DecreaseFactor под размер депозита и допустимый риск.
  4. Проведите тестирование, чтобы убедиться, что сигналы пересечений совпадают с результатами оригинального советника.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Moving Average With Frames: SMA crossover with candle body confirmation.
/// Buys when close crosses above shifted SMA, sells when crosses below.
/// </summary>
public class MovingAverageWithFramesStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _smaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _entryPrice;
	private decimal _prevClose;

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

		_smaLength = Param(nameof(SmaLength), 12)
			.SetDisplay("SMA Length", "SMA period.", "Indicators");

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

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

	public int SmaLength
	{
		get => _smaLength.Value;
		set => _smaLength.Value = value;
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();

		_entryPrice = 0;
		_prevClose = 0;
	}

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

		var sma = new SimpleMovingAverage { Length = SmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		if (atrVal <= 0)
			return;

		var close = candle.ClosePrice;

		if (_prevClose == 0)
		{
			_prevClose = close;
			return;
		}

		if (Position > 0)
		{
			if (close < smaVal && _prevClose >= smaVal)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (close <= _entryPrice - atrVal * 2m)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close > smaVal && _prevClose <= smaVal)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			else if (close >= _entryPrice + atrVal * 2m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (close > smaVal && _prevClose <= smaVal)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (close < smaVal && _prevClose >= smaVal)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevClose = close;
	}
}