Открыть на GitHub

Стратегия Perceptron Mult

Стратегия является портом советника Peceptron_Mult.mq5 на высокоуровневый API StockSharp. Она одновременно отслеживает до трёх инструментов и использует осциллятор Acceleration/Deceleration (AC) в перцептроне с четырьмя входами. Для каждого инструмента задаются собственные веса, объём позиций и уровни защиты, благодаря чему логика многосимвольного советника сохраняется без изменений.

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

  1. Для каждого выбранного инструмента создаётся подписка на одинаковый тип свечей (по умолчанию — минутные).
  2. После закрытия свечи вычисляется осциллятор Acceleration/Deceleration:
    • Сначала считается Awesome Oscillator (AO) по максимумам и минимумам свечи (скользящие средние 5 и 34).
    • Затем от значения AO отнимается 5-периодная простая скользящая средняя AO.
  3. Для каждого инструмента поддерживается кольцевой буфер из 22 последних значений AC.
  4. Перцептрон формирует сигнал из четырёх отложенных значений AC (0, 7, 14, 21), умножая их на веса w - 100, как в оригинале на MQL.
  5. Правила входа:
    • Сумма больше нуля ⇒ открывается длинная позиция, если по инструменту нет позиции.
    • Сумма меньше нуля ⇒ открывается короткая позиция при отсутствии открытой позиции.
  6. Правила выхода:
    • Стоп-лосс и тейк-профит задаются в пунктах и пересчитываются в абсолютное смещение цены с учётом шага цены инструмента.
    • На каждой завершённой свече проверяется, достигла ли цена уровней защиты. Для лонга свеча закрывает позицию, если минимум опустился ниже стопа или максимум превысил тейк; для шорта условия зеркальные.
  7. По каждому инструменту может быть только одна позиция. Пока позиция активна, новые сигналы игнорируются, что полностью повторяет поведение MQL-советника.

Параметры

Параметр Описание
FirstSecurity, SecondSecurity, ThirdSecurity Инструменты для расчёта. Параметр можно оставить пустым, чтобы отключить слот.
FirstOrderVolume, SecondOrderVolume, ThirdOrderVolume Объём рыночной заявки для соответствующего инструмента.
FirstWeight1FirstWeight4 и аналоги Веса перцептрона (параметры x1…x12 в MQL). Внутри стратегии из каждого веса вычитается 100 перед умножением.
FirstStopLossPoints, SecondStopLossPoints, ThirdStopLossPoints Дистанция стоп-лосса в пунктах. Ноль — отключение.
FirstTakeProfitPoints, SecondTakeProfitPoints, ThirdTakeProfitPoints Дистанция тейк-профита в пунктах. Ноль — отключение.
CandleType Общий тип свечей для всех инструментов.

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

  • Используются штатные индикаторы StockSharp AwesomeOscillator и SimpleMovingAverage, которые полностью воспроизводят расчёт AC без ручных формул.
  • Буфер из 22 элементов хранится только ради имитации выборок [0, 7, 14, 21], присутствующих в исходном советнике.
  • Уровни защиты не выставляются отдельными заявками: стратегия контролирует экстремумы свечей и закрывает позиции рыночными ордерами при достижении стопа или цели, что аналогично срабатыванию стопов в MT5 на новом тике.
  • Для каждого инструмента поддерживаются независимые индикаторы, параметры объёма и риск-менеджмента, что соответствует трём символам в оригинальном коде.

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

  1. Укажите до трёх инструментов. Любой слот можно оставить пустым (null).
  2. Подберите значения стопов и тейков в пунктах так, чтобы они соответствовали шагу цены выбранных инструментов.
  3. При необходимости оптимизации изменяйте веса перцептрона — они определяют вклад лагов AC в итоговый сигнал.
  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>
/// Perceptron strategy that uses weighted moving average crossover signals
/// to determine trade direction. Simplified from multi-symbol to single security.
/// </summary>
public class PerceptronMultStrategy : 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;

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public PerceptronMultStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 200)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow EMA period", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 100)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 200)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
	}

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

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

		_fast = null;
		_slow = null;
		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
	}

	/// <inheritdoc />
	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;
		}

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

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

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

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}

		// Entry: fast crosses slow
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

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

			SellMarket();
			_entryPrice = close;
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}