GitHub で見る

2DLimits戦略

概要

2DLimitsはMetaTrader 4エキスパートアドバイザー2DLimits_EA_v2の直接ポートです。戦略は最後の2本の完了した日足ローソク足を評価し、階段状のパターン(高値/安値が共に上昇または下降)を形成している場合にのみ参加します。パターンが有効な場合、戦略は前日の極値にストップ注文を送信し、中間点のストップロスと前日の日次レンジに等しい目標でポジションを保護します。

実装はStockSharpの高レベルローソク足サブスクリプションとレベル1クオートを組み合わせて依存します。日足ローソク足はブレイクアウトレベルを提供し、最良ビッド/アスクスナップショットは、ロングセットアップが価格が中間点より下で取引される場合のみ、ショートセットアップが中間点より上の場合のみ有効になることを確保します。

戦略ロジック

日次構造フィルター

  • 戦略は完了した日足ローソク足の2日間のローリングウィンドウを維持します(ローソク足タイプパラメーターを通じて設定可能)。
  • 強気セットアップは、最新の日が前日と比較して高値と安値の両方が高くなることを必要とします。
  • 弱気セットアップは、最新の日が前日より高値と安値の両方が低くなることを必要とします。
  • 最新日の中間点は(high + low) / 2として計算され、ローソク足レンジは利益目標のために保存されます。

エントリールール

  • 一度に有効な保留注文のバッチは1つのみです。新しい日足ローソク足が閉じると、すべての注文がキャンセルされ再計算されます。
  • ロングエントリーは以下の場合に準備されます:
    • 強気構造フィルターが満たされている。
    • 最新のアスク価格が前日の中間点より下にある(元のEAのAsk < middleYチェックを反映)。
    • 前日の高値にバイストップ注文が正確に配置される。
  • ショートエントリーは以下の場合に準備されます:
    • 弱気構造フィルターが満たされている。
    • 最新のビッド価格が前日の中間点より上にある(Bid > middleYを反映)。
    • 前日の安値にセルストップ注文が配置される。
  • 両方の構造チェックが失敗した場合、次のセッションには注文が残りません。

出口ルール

  • ストップ注文がトリガーされると、反対のエントリー注文が即座にキャンセルされ、戦略がロングとショートの両方のエクスポージャーを同時に保持することを防ぎます。
  • ロングブレイクアウト約定後、2つの保護注文が登録されます:
    • 参照日の中間点のストップ注文がストップロスとして機能します。
    • 前日高値 + 前日レンジでのテイクプロフィット注文がMetaTraderのテイクプロフィット距離に一致します。
  • ショートブレイクアウト約定後、対称的な保護が適用されます:
    • 中間点のストップ注文(バイストップ)がストップロスをカバーします。
    • 前日安値 - 前日レンジでのテイクプロフィット注文が元のターゲットを反映します。
  • 保護注文は約定したポジションサイズが変わるたびに再設定され、ポジションがフラットに戻ると削除されます。

注文ライフサイクルと安全チェック

  • 保留注文は次の日足ローソク足が完了した後にのみ更新され、1取引日あたり1つのセットアップを強制します。
  • 戦略はすでにポジションを保持している場合、シグナル生成をスキップし、セットアップ間の重複を防ぎます。
  • 最新のビッド/アスクスナップショットはSubscribeLevel1()から保持されます。利用できない場合、最後の取引価格がフォールバックとして使用され、ブラインド注文の送信を回避します。
  • 丸め処理は計器の価格ステップで行われ、すべての注文が取引所のティックサイズと一致します。

パラメーター

名前 説明
Volume ストップエントリーの注文ボリューム。ゼロより大きくなければなりません。
CandleType 参照レンジを提供するローソク足タイプ(デフォルトは日足ローソク足)。

追加ノート

  • 戦略はすべての注文を高レベルAPIを通じて直接管理します。カスタムコレクションやインジケーターバッファへの依存はありません。
  • このパッケージにはC#実装のみが提供されています。この変換にはPythonバージョンは作成されていません。
  • テストは要求通り変更されていません。
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;

public class TwoDLimitsStrategy : 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;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public TwoDLimitsStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	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; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

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

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

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

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}