GitHub で見る

ニューラル ネットワーク テンプレート戦略

概要

この戦略は、RSI および MACD の機能をニューラル ネットワークにフィードする MQL5 エキスパート アドバイザー テンプレートの動作を複製します。 StockSharp には元のプロジェクトのカスタム ネットワーク ローダーが同梱されていないため、この戦略では、同じ市場構造とリスク管理を維持しながら、ブラックボックス ネットワークを決定論的なスコアリング モデルに置き換えます。目標は、RSI と MACD の両方が方向性に同意し、予想される動きが取引を正当化するのに十分な大きさであるときに勢いを捉えることです。

指標とデータ

  • 相対強度指数 (RSI、12 期間) はローソク足の終値で計算され、元の典型的な価格入力を反映しています。
  • Moving Average Convergence Divergence (MACD 12/48/12) は、運動量ヒストグラムおよび信頼度の代用として使用されます。
  • 時間枠 構成可能。デフォルトはソースの専門家と一致する 5 分足のローソク足です。

取引ロジック

  1. 終了したローソクごとに、戦略は BarsToPattern によって制御されるウィンドウを使用して、RSI と MACD のヒストグラム値のローリング キューを更新します。
  2. 50 からの RSI の偏差と、ローリング平均からの 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;
	}
}