Открыть на GitHub

Стратегия Neural Network Template

Обзор

Стратегия повторяет MQL5-шаблон, в котором значения RSI и MACD передавались в нейронную сеть. В StockSharp отсутствует оригинальный загрузчик нейросети, поэтому вместо «чёрного ящика» используется прозрачная модель оценки, сохраняющая структуру и риск-контроль исходного эксперта. Цель — входить в рынок, когда RSI и MACD совпадают по направлению, а ожидаемое движение достаточно велико.

Индикаторы и данные

  • RSI (12 периодов) считается по цене закрытия и соответствует типичной цене из MQL5-версии.
  • MACD (12/48/12) используется для оценки гистограммы и силы импульса.
  • Таймфрейм настраивается, по умолчанию 5-минутные свечи как в исходном советнике.

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

  1. После закрытия каждой свечи стратегия обновляет скользящее окно значений RSI и гистограммы MACD. Размер окна задаётся параметром BarsToPattern.
  2. Отклонение RSI от уровня 50 и отклонение гистограммы MACD от её скользящего среднего комбинируются в показатель доверия. Для сжатия диапазона применяется гиперболический тангенс, имитирующий выход нейросети.
  3. Если абсолютное значение доверия превышает TradeLevel, а прогнозируемое движение (в пунктах) больше MinTargetPoints, стратегия открывает позицию по рынку в направлении оценки.
  4. Для каждой позиции рассчитывается индивидуальный тейк-профит: прогнозируемое движение умножается на ProfitMultiply, после чего ограничивается MaxTakeProfitPoints. Стоп-лосс выставляется симметрично в пунктах.
  5. Пока позиция открыта, каждая завершённая свеча проверяет достижение целевых уровней. При касании цены тейк-профита или стоп-лосса позиция закрывается по рынку, а внутреннее состояние сбрасывается.

Параметры

Параметр Описание
BarsToPattern Размер скользящего окна для расчётов RSI и MACD.
TradeLevel Минимальная требуемая уверенность (0-1) для открытия сделки.
ProfitMultiply Множитель, применяемый к прогнозу перед ограничением MaxTakeProfitPoints.
MinTargetPoints Минимальный прогноз по пунктам, необходимый для входа.
MaxTakeProfitPoints Максимально допустимый тейк-профит в пунктах.
StopLossPoints Расстояние стоп-лосса от цены входа в пунктах.
TradeVolume Объём каждой рыночной заявки.
CandleType Тип/таймфрейм свечей, которые подписывает стратегия.

Примечания

  • Детерминированная модель уверенности упрощает анализ и сохраняет дух оригинальной нейросетевой идеи.
  • Управление тейк-профитом и стоп-лоссом выполняется вручную внутри стратегии, чтобы у каждой сделки оставались собственные целевые уровни, как в версии MQL5.
  • Стратегия анализирует сигналы только при отсутствии открытой позиции, полностью копируя однопозиционный режим исходного эксперта.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Momentum strategy inspired by a neural-network driven template from MQL5.
/// </summary>
public class NeuralNetworkTemplateStrategy : Strategy
{
	private readonly StrategyParam<int> _barsToPattern;
	private readonly StrategyParam<int> _maxTakeProfitPoints;
	private readonly StrategyParam<int> _minTargetPoints;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<decimal> _profitMultiply;
	private readonly StrategyParam<decimal> _tradeLevel;
	private readonly StrategyParam<decimal> _volume;
	private readonly StrategyParam<DataType> _candleType;

	private RelativeStrengthIndex _rsi = null!;
	private MovingAverageConvergenceDivergenceSignal _macd = null!;

	private readonly Queue<decimal> _rsiHistory = new();
	private readonly Queue<decimal> _macdHistory = new();

	private decimal _rsiSum;
	private decimal _macdSum;
	private decimal? _targetPrice;
	private decimal? _stopPrice;
	private int _positionDirection;

	/// <summary>
	/// Number of candles used for pattern recognition.
	/// </summary>
	public int BarsToPattern
	{
		get => _barsToPattern.Value;
		set => _barsToPattern.Value = value;
	}

	/// <summary>
	/// Upper bound for calculated take-profit in points.
	/// </summary>
	public int MaxTakeProfitPoints
	{
		get => _maxTakeProfitPoints.Value;
		set => _maxTakeProfitPoints.Value = value;
	}

	/// <summary>
	/// Minimum projected move required to open a trade.
	/// </summary>
	public int MinTargetPoints
	{
		get => _minTargetPoints.Value;
		set => _minTargetPoints.Value = value;
	}

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

	/// <summary>
	/// Multiplier applied to the projected move returned by the scoring model.
	/// </summary>
	public decimal ProfitMultiply
	{
		get => _profitMultiply.Value;
		set => _profitMultiply.Value = value;
	}

	/// <summary>
	/// Required confidence level before opening a new position.
	/// </summary>
	public decimal TradeLevel
	{
		get => _tradeLevel.Value;
		set => _tradeLevel.Value = value;
	}

	/// <summary>
	/// Trading volume for every market order.
	/// </summary>
	public decimal TradeVolume
	{
		get => _volume.Value;
		set => _volume.Value = value;
	}

	/// <summary>
	/// Candle type used by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="NeuralNetworkTemplateStrategy"/>.
	/// </summary>
	public NeuralNetworkTemplateStrategy()
	{
		_barsToPattern = Param(nameof(BarsToPattern), 3)
			.SetGreaterThanZero()
			.SetDisplay("Bars", "Candles analysed", "Model")
			;

		_maxTakeProfitPoints = Param(nameof(MaxTakeProfitPoints), 500)
			.SetGreaterThanZero()
			.SetDisplay("Max TP", "Maximum take-profit in points", "Risk");

		_minTargetPoints = Param(nameof(MinTargetPoints), 1)
			.SetGreaterThanZero()
			.SetDisplay("Min Target", "Minimum projected move in points", "Model");

		_stopLossPoints = Param(nameof(StopLossPoints), 300)
			.SetGreaterThanZero()
			.SetDisplay("Stop-Loss", "Stop-loss distance in points", "Risk");

		_profitMultiply = Param(nameof(ProfitMultiply), 0.8m)
			.SetNotNegative()
			.SetDisplay("Profit Mult", "Take-profit multiplier", "Model");

		_tradeLevel = Param(nameof(TradeLevel), 0.1m)
			.SetRange(0m, 1m)
			.SetDisplay("Trade Level", "Required confidence", "Model");

		_volume = Param(nameof(TradeVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Volume", "Order volume", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("TF", "Working timeframe", "General");
	}

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

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

		_rsi = null!;
		_macd = null!;
		_rsiHistory.Clear();
		_macdHistory.Clear();
		_rsiSum = 0m;
		_macdSum = 0m;
		_targetPrice = null;
		_stopPrice = null;
		_positionDirection = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_rsi = new RelativeStrengthIndex { Length = 12 };
		_macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = 12 },
				LongMa = { Length = 48 }
			},
			SignalMa = { Length = 12 }
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_rsi, _macd, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue rsiValue, IIndicatorValue macdValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		ManageOpenPosition(candle);

		if (!_rsi.IsFormed || !_macd.IsFormed)
			return;

		var rsiDecimal = rsiValue.ToDecimal();

		if (macdValue is not MovingAverageConvergenceDivergenceSignalValue macdComponents)
			return;

		if (macdComponents.Macd is not decimal macdLine ||
			macdComponents.Signal is not decimal signalLine)
			return;

		UpdateHistory(rsiDecimal, macdLine - signalLine);

		if (Position != 0)
			return;

		EvaluateEntry(candle, rsiDecimal, macdLine, signalLine);
	}

	private void UpdateHistory(decimal rsiValue, decimal macdHistogram)
	{
		_rsiHistory.Enqueue(rsiValue);
		_rsiSum += rsiValue;
		if (_rsiHistory.Count > BarsToPattern)
			_rsiSum -= _rsiHistory.Dequeue();

		_macdHistory.Enqueue(macdHistogram);
		_macdSum += macdHistogram;
		if (_macdHistory.Count > BarsToPattern)
			_macdSum -= _macdHistory.Dequeue();
	}

	private void EvaluateEntry(ICandleMessage candle, decimal rsiValue, decimal macdLine, decimal signalLine)
	{
		if (_rsiHistory.Count < BarsToPattern || _macdHistory.Count < BarsToPattern)
			return;

		var priceStep = Security?.PriceStep ?? 1m;
		if (priceStep <= 0m)
			priceStep = 1m;

		var normalizedRsi = Clamp((rsiValue - 50m) / 50m, -1m, 1m);
		var macdHistogram = macdLine - signalLine;
		var macdAverage = _macdHistory.Count == 0 ? 0m : _macdSum / _macdHistory.Count;
		var macdDeviation = macdHistogram - macdAverage;
		var normalizedMomentum = (decimal)Math.Tanh((double)(macdDeviation * 5m));

		var combinedScore = normalizedRsi * 0.6m + normalizedMomentum * 0.4m;
		var confidence = Math.Min(1m, Math.Abs(combinedScore));
		var projectedMove = macdDeviation * BarsToPattern;
		var projectedPoints = projectedMove / priceStep;

		if (combinedScore > 0m)
		{
			if (confidence < TradeLevel)
				return;

			if (projectedPoints < MinTargetPoints)
				return;

			var takeProfit = candle.ClosePrice + Math.Min(projectedMove * ProfitMultiply, MaxTakeProfitPoints * priceStep);
			var stopLoss = candle.ClosePrice - StopLossPoints * priceStep;

			if (takeProfit <= candle.ClosePrice)
				return;

			BuyMarket(TradeVolume);
			_targetPrice = takeProfit;
			_stopPrice = stopLoss;
			_positionDirection = 1;
		}
		else if (combinedScore < 0m)
		{
			if (confidence < TradeLevel)
				return;

			if (projectedPoints > -MinTargetPoints)
				return;

			var takeProfit = candle.ClosePrice + Math.Max(projectedMove * ProfitMultiply, -MaxTakeProfitPoints * priceStep);
			var stopLoss = candle.ClosePrice + StopLossPoints * priceStep;

			if (takeProfit >= candle.ClosePrice)
				return;

			SellMarket(TradeVolume);
			_targetPrice = takeProfit;
			_stopPrice = stopLoss;
			_positionDirection = -1;
		}
	}

	private void ManageOpenPosition(ICandleMessage candle)
	{
		if (Position > 0m)
		{
			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				SellMarket(Math.Abs(Position));
				ResetTargets();
				return;
			}

			if (_targetPrice.HasValue && candle.HighPrice >= _targetPrice.Value)
			{
				SellMarket(Math.Abs(Position));
				ResetTargets();
			}
		}
		else if (Position < 0m)
		{
			if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
			{
				BuyMarket(Math.Abs(Position));
				ResetTargets();
				return;
			}

			if (_targetPrice.HasValue && candle.LowPrice <= _targetPrice.Value)
			{
				BuyMarket(Math.Abs(Position));
				ResetTargets();
			}
		}
		else if (_positionDirection != 0)
		{
			ResetTargets();
		}
	}

	private void ResetTargets()
	{
		_targetPrice = null;
		_stopPrice = null;
		_positionDirection = 0;
	}

	private static decimal Clamp(decimal value, decimal min, decimal max)
	{
		if (value < min)
			return min;

		if (value > max)
			return max;

		return value;
	}
}