GitHub で見る

ネヴァリャシュカの方向性戦略

概要

Nevalyashka 戦略は、元の MetaTrader 4 Expert Advisor Nevalyashka.mq4 の C# 移植です。 EA は取引方向を繰り返し反転します。単一の成行注文をオープンし、ストップロス、テイクプロフィット、または手動アクションによってポジションがクローズされるまで待機し、同じ量で反対方向に即座に再エントリーします。 StockSharp 実装は、すべての重要な設定を戦略パラメーターとして公開しながら、この動作を再現します。

取引ロジック

  1. 初期化

    • ストラテジーが開始されると、商品の PriceStep からピップ サイズが計算されます。 3 桁および 5 桁の外国為替記号の場合、ステップは MetaTrader ポイントの定義と一致するように 10 倍されます。
    • StartProtection は、ピップから価格ポイントに変換されたストップロスとテイクプロフィットの距離で構成されています。保護命令はその後のポジションごとに付与されます。
    • 最初の成行注文は、InitialDirection で定義された方向に送信されます (デフォルト: ショート)。リクエストされた数量は、証券の VolumeStepMinVolume、および MaxVolume の値を使用して、最も近い有効なロットに四捨五入されます。
  2. 位置追跡

    • OnPositionChanged は、純エクスポージャのあらゆる変化をキャプチャします。新しいポジションがオープンされると、ストラテジーは約定したボリュームを保存し、取引サイドを記憶します。
    • ポジションが完全にフラットに戻ると、ストラテジーはすぐに逆方向に新しい成行注文を発行し、以前に保存されたロットサイズを再利用します。
  3. 障害対応

    • ブローカーが注文登録を拒否した場合、保留方向フラグがクリアされ、プラットフォーム オペレーターが内部状態を古くすることなく手動で再試行したりパラメータを調整したりできるようになります。

結果として得られるワークフローは、元のスクリプトの「ローリーポリ」のアイデアを反映しています。つまり、ボットは常に市場に存在し、固定出口でロングポジションとショートポジションを交互に行います。

パラメーター

名前 説明 デフォルト 注意事項
StopLossPips ピップ単位での保護停止の距離。 50 ピップサイズの計算により価格ポイントに変換されます。停止を無効にするには、0 に設定します。
TakeProfitPips ピップ単位でのプロテクティブテイクプロフィットの距離。 50 ストップロスと同じ方法で変換されます。テイクプロフィットを無効にするには、0 に設定します。
Volume 最初の取引に使用されるロットサイズ。 1 最初のフィルの後、戦略は将来のすべてのエントリに対して実際に実行されたボリュームを再利用します。
InitialDirection 最初の成行注文の側面。 Sell 希望の開始バイアスに合わせて、BuySell から選択します。

実装メモ

  • ローソク足やインジケーターのサブスクリプションは必要ありません。この戦略はポジション イベントと注文確認にのみ反応します。
  • IsFormedAndOnlineAndAllowTrading() は、コネクタが取引の準備ができていることを確認するために、すべてのエントリの前に参照されます。
  • 出来高の四捨五入では MidpointRounding.AwayFromZero を使用するため、端数ロットは常にゼロではなく取引可能なレベルにスナップされます。
  • pip 変換ロジックは、ハードコードされた仮定ではなく商品メタデータに依存するため、異なる価格形式の FX、CFD、または先物シンボルにわたってポートが機能します。

MQL バージョンとの違い

  • StockSharp バリアントは、MT4 スクリプトから最初のショートを強制するのではなく、パラメーターとして開始方向を公開します。
  • ストップロス注文とテイクプロフィット注文は、StartProtection を通じて管理され、あらゆる StockSharp コネクタと互換性のあるネイティブ保護注文が生成されます。
  • 注文が拒否されると、内部の保留状態がクリアされ、無効なリクエストが繰り返し送信されるのを防ぎます。

これらの調整により、元のアドバイザーの精神が維持されながら、StockSharp の高レベルの API とシームレスに統合されます。

using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Nevalyashka Direction: RSI reversal with ATR stops.
/// </summary>
public class NevalyashkaDirectionStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevRsi;
	private decimal _entryPrice;

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

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI 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 RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

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

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

		_prevRsi = 0;
		_entryPrice = 0;
	}

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

		_prevRsi = 0;
		_entryPrice = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		if (_prevRsi == 0 || atrVal <= 0)
		{
			_prevRsi = rsiVal;
			return;
		}

		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 2m || close <= _entryPrice - atrVal * 1.5m || rsiVal > 70)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 2m || close >= _entryPrice + atrVal * 1.5m || rsiVal < 30)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (rsiVal < 35 && _prevRsi >= 35)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (rsiVal > 65 && _prevRsi <= 65)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevRsi = rsiVal;
	}
}