GitHub で見る

Slime Mold RSI 戦略

MQL4エキスパートアドバイザー「Slime_Mold_RSI_v1.1」の直接変換です。戦略は中央値価格で計算された4つのRSI読み値(12、36、108、324)を組み合わせることで単一のパーセプトロンを構築します。各RSI値は元の0–100の範囲から-1…+1に正規化され、設定可能な重みで乗算されます。加重和のゼロ交差がポジションを反転させます。

仕組み

  • 完了した各ローソク足の中央値価格を計算し、長さ12、36、108、324の4つの相対力指数インジケーターに入力します。
  • 各RSI値を-1…+1の範囲に正規化し、対応する重みを適用します。デフォルト値(-100)は元のパーセプトロン係数(x - 100)を再現します。
  • 4つの加重入力を合計して現在のローソク足のパーセプトロン出力を生成します。
  • 最新の値を前のローソク足のパーセプトロン出力と比較してゼロ交差を検出し、取引シグナルを生成します。

取引ルール

  • ロングエントリー: 前のパーセプトロン値がゼロ未満で、現在の値がゼロ以上に上昇する場合。戦略はショートのエクスポージャーを閉じ、サイズVolumeのロングポジションを確立します。
  • ショートエントリー: 前のパーセプトロン値がゼロより大きく、現在の値がゼロ未満に下落する場合。戦略はロングポジションを決済し、サイズVolumeのショートポジションを開きます。
  • ポジション管理: 明示的な利益目標やストップロス注文はありません。ポジションは新しいゼロ交差が発生した場合にのみ変更されます。

パラメーター

  • Weight1 – 12期間の正規化RSI入力に適用する係数。
  • Weight2 – 36期間の正規化RSI入力に適用する係数。
  • Weight3 – 108期間の正規化RSI入力に適用する係数。
  • Weight4 – 324期間の正規化RSI入力に適用する係数。
  • CandleType – 戦略に供給するローソク足の時間軸。デフォルトは1時間ローソク足。

詳細

  • エントリー条件: 加重RSIパーセプトロンのゼロ交差。
  • ロング/ショート: 両方(最初のシグナル後は常にマーケットにいる)。
  • エグジット条件: 反対方向のゼロ交差がポジションを反転させる。
  • ストップ: なし。
  • デフォルト値:
    • Weight1 = -100
    • Weight2 = -100
    • Weight3 = -100
    • Weight4 = -100
    • CandleType = 1時間ローソク足
  • フィルター:
    • カテゴリ: パーセプトロン / オシレーター
    • 方向: 双方向
    • インジケーター: RSI(中央値価格)
    • ストップ: なし
    • 複雑さ: 中級(4つの長期インジケーターが必要)
    • 時間軸: 設定可能(デフォルトイントラデイ毎時)
    • 季節性: なし
    • ニューラルネットワーク: 線形パーセプトロン
    • ダイバージェンス: なし
    • リスクレベル: 選択したボリュームと重みによって異なる

注意事項

  • 実装は取引が無効になっている場合でも前のパーセプトロン出力を追跡し、取引が再開されたときの状態の継続性を保証します。
  • 元のMetaTraderスクリプトのPRICE_MEDIAN設定に合わせるために中央値価格を使用します。
  • 戦略はポジションを即座に反転させるため、重みとボリュームを選択する際に潜在的なスリッページを考慮してください。
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>
/// Slime Mold RSI perceptron strategy converted from MQL4.
/// The strategy sums weighted RSI inputs to generate zero-crossing signals.
/// </summary>
public class SlimeMoldRsiStrategy : Strategy
{
	private readonly StrategyParam<decimal> _weight1;
	private readonly StrategyParam<decimal> _weight2;
	private readonly StrategyParam<decimal> _weight3;
	private readonly StrategyParam<decimal> _weight4;
	private readonly StrategyParam<DataType> _candleType;

	private RelativeStrengthIndex _rsi12 = null!;
	private RelativeStrengthIndex _rsi36 = null!;
	private RelativeStrengthIndex _rsi108 = null!;
	private RelativeStrengthIndex _rsi324 = null!;

	private decimal? _previousPerceptron;

	/// <summary>
	/// Weight applied to the 12-period RSI input.
	/// </summary>
	public decimal Weight1
	{
		get => _weight1.Value;
		set => _weight1.Value = value;
	}

	/// <summary>
	/// Weight applied to the 36-period RSI input.
	/// </summary>
	public decimal Weight2
	{
		get => _weight2.Value;
		set => _weight2.Value = value;
	}

	/// <summary>
	/// Weight applied to the 108-period RSI input.
	/// </summary>
	public decimal Weight3
	{
		get => _weight3.Value;
		set => _weight3.Value = value;
	}

	/// <summary>
	/// Weight applied to the 324-period RSI input.
	/// </summary>
	public decimal Weight4
	{
		get => _weight4.Value;
		set => _weight4.Value = value;
	}

	/// <summary>
	/// Candle type used for RSI calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="SlimeMoldRsiStrategy"/> class.
	/// </summary>
	public SlimeMoldRsiStrategy()
	{
		_weight1 = Param(nameof(Weight1), -100m)
			.SetDisplay("Weight 1", "Weight applied to the 12-period RSI input", "Perceptron")
			
			.SetOptimize(-200m, 200m, 10m);

		_weight2 = Param(nameof(Weight2), -100m)
			.SetDisplay("Weight 2", "Weight applied to the 36-period RSI input", "Perceptron")
			
			.SetOptimize(-200m, 200m, 10m);

		_weight3 = Param(nameof(Weight3), -100m)
			.SetDisplay("Weight 3", "Weight applied to the 108-period RSI input", "Perceptron")
			
			.SetOptimize(-200m, 200m, 10m);

		_weight4 = Param(nameof(Weight4), -100m)
			.SetDisplay("Weight 4", "Weight applied to the 324-period RSI input", "Perceptron")
			
			.SetOptimize(-200m, 200m, 10m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for candles used in calculations", "General");
	}

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

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

		// Drop cached indicator instances and perceptron history.
		_rsi12 = null!;
		_rsi36 = null!;
		_rsi108 = null!;
		_rsi324 = null!;
		_previousPerceptron = null;
	}

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

		// Create RSI indicators for each horizon used by the original perceptron.
		_rsi12 = new RelativeStrengthIndex { Length = 12 };
		_rsi36 = new RelativeStrengthIndex { Length = 36 };
		_rsi108 = new RelativeStrengthIndex { Length = 108 };
		_rsi324 = new RelativeStrengthIndex { Length = 324 };

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

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

		if (_rsi12 is null || _rsi36 is null || _rsi108 is null || _rsi324 is null)
			return;

		// Median price replicates PRICE_MEDIAN used in the original script.
		var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;

		var input = new DecimalIndicatorValue(_rsi12, medianPrice, candle.ServerTime) { IsFinal = true };
		_rsi12.Process(input);
		_rsi36.Process(new DecimalIndicatorValue(_rsi36, medianPrice, candle.ServerTime) { IsFinal = true });
		_rsi108.Process(new DecimalIndicatorValue(_rsi108, medianPrice, candle.ServerTime) { IsFinal = true });
		_rsi324.Process(new DecimalIndicatorValue(_rsi324, medianPrice, candle.ServerTime) { IsFinal = true });

		// Wait until every RSI is fully formed before evaluating signals.
		if (!_rsi12.IsFormed || !_rsi36.IsFormed || !_rsi108.IsFormed || !_rsi324.IsFormed)
			return;

		var rsi12Value = _rsi12.GetCurrentValue();
		var rsi36Value = _rsi36.GetCurrentValue();
		var rsi108Value = _rsi108.GetCurrentValue();
		var rsi324Value = _rsi324.GetCurrentValue();

		var currentPerceptron =
			(Weight1 * NormalizeRsi(rsi12Value)) +
			(Weight2 * NormalizeRsi(rsi36Value)) +
			(Weight3 * NormalizeRsi(rsi108Value)) +
			(Weight4 * NormalizeRsi(rsi324Value));

		// Initialize the history with the first complete value.
		if (_previousPerceptron is null)
		{
			_previousPerceptron = currentPerceptron;
			return;
		}

		var previousPerceptron = _previousPerceptron.Value;

		// Even if trading is disabled, keep the state in sync with the incoming data.
		// indicators already checked above via IsFormed

		// Zero-crossing from negative to positive triggers a long entry.
		if (previousPerceptron < 0m && currentPerceptron > 0m && Position <= 0m)
		{
				BuyMarket();
				LogInfo($"Long entry. Previous perceptron: {previousPerceptron:F2}, current: {currentPerceptron:F2}");
		}
		// Zero-crossing from positive to negative triggers a short entry.
		else if (previousPerceptron > 0m && currentPerceptron < 0m && Position >= 0m)
		{
				SellMarket();
				LogInfo($"Short entry. Previous perceptron: {previousPerceptron:F2}, current: {currentPerceptron:F2}");
		}

		// Store the latest perceptron value for the next signal evaluation.
		_previousPerceptron = currentPerceptron;
	}

	private static decimal NormalizeRsi(decimal rsiValue)
	{
		// Transform RSI from [0,100] into [-1,+1] as in the original script.
		return ((rsiValue / 100m) - 0.5m) * 2m;
	}
}