GitHub で見る

TwoPerBar ロン戦略

概要

Ron Thompson によるオリジナルの MetaTrader エキスパート「TwoPerBar」は、すべての新しいバーの開始時に 2 つの成行注文 (ロングとショートの 1 つ) を開きます。レッグが固定キャッシュターゲット (MQL コードの ProfitMade * Point) に達すると必ずレッグは閉じられ、次の足の開始時に、新しいヘッジペアが作成される前に残りのエクスポージャが清算されます。前のバーがオープンポジションで終了した場合、ロットサイズは安全キャップ (LotLimit) まで 2 倍になります。 StockSharp ポートは、高レベルの戦略 API、買値/売値モニタリング用のレベル 1 相場、および 2 つのヘッジ レッグの明示的な追跡を使用して、この動作を再現します。

取引ワークフロー

  1. バー検出SubscribeCandles(CandleType) は、設定されたローソク足シリーズが終了すると戦略に通知します。完了したローソク足は、MetaTrader の Time[0] の変化と同じように、新しいバーの始まりを示します。
  2. 利益検査 – レベル 1 のスナップショット (買い/売り) が継続的に監視されます。最高の買値または売値が記録されたエントリー価格から大きく離れるとすぐに、一致するレッグは SellMarket または BuyMarket でクローズされます。
  3. 強制清算 – 新しいバーの開始時に、残っているレッグは市場で閉じられます。これは、MQL スクリプトの OrderClose ループを反映しています。
  4. ボリュームスケーリング – 前のサイクルにアクティブな取引があった場合、ロットサイズは VolumeMultiplier 倍されます (デフォルトは 2)。それ以外の場合は、BaseVolume にリセットされます。値は楽器の音量ステップに対して正規化され、MaxVolume と交換 Security.MaxVolume によってクランプされます。
  5. ヘッジ作成 – 2 つの成行注文が BuyMarketSellMarket 経由で送信されます。各レッグはターゲットボリューム、実際の約定サイズ、加重平均約定価格を記憶しているため、利益チェックは正確な情報に基づいて行われます。

リスクと資金の管理

  • Martingale スタイルのスケーリング – 未完了のサイクルの後にロットを 2 倍にすることで、元のマーチンゲールのようなサイジングを模倣します。バーの間に両レッグが閉じられると、シーケンスは基準ロットにリセットされます。
  • レッグごとの利益目標ProfitTargetPoints は、MetaTrader ProfitMade 入力を変換します。この値に商品ポイントサイズが乗算され、買値/買値と比較されて、いつレッグを終了するかを決定します。
  • 取引所のコンプライアンスNormalizeVolume は、生成されたロットが商品 VolumeStep および MinVolume に準拠していることを保証します。値が大きすぎると、取引可能な数量にリセットされます。
  • ヘッジ会計 – StockSharp ポートフォリオは通常、ネット ポジションのみを公開するため、戦略は独自のレッグ リストを維持します。これにより、ヘッジ口座をサポートする環境でも同じ動作が可能になります。

パラメーター

名前 種類 デフォルト 説明
CandleType DataType 1分キャンドル 新しいバーが開始されたことを知らせるプライマリ タイムフレーム。
BaseVolume decimal 0.1 新品サイクルの初期ロットサイズ。
VolumeMultiplier decimal 2 オープンポジションでバーが終了した後に適用される乗数。
MaxVolume decimal 12.8 マーチンゲールのロットサイズに対するハードシーリング。
ProfitTargetPoints decimal 19 利益目標はポイントで表現されます。金融商品のポイントサイズを乗算し、買値/売値と比較します。

MQL バージョンとの違い

  • ティックごとの Bid/Ask グローバルの代わりに SubscribeLevel1() を使用しますが、最良の引用符に基づいて同じロジックを維持します。
  • 注文は StockSharp ヘルパー メソッド (BuyMarketSellMarket) を通じて送信されるため、為替固有の丸めはすべて自動的に行われます。
  • ボリューム処理では VolumeStepMinVolume、および MaxVolume が尊重されますが、元のスクリプトは生の double 値で動作しました。
  • StockSharp ポートは内部にレッグ情報を保存します。ネッティング モードで実行されているコネクタは依然としてヘッジをフラット化する可能性があるため、ブローカーが反対のポジションをサポートしていることを確認してください。

使い方のヒント

  • BaseVolume を選択した商品の有効なロットサイズと一致させます。そうしないと、正規化ステップで取引がスキップされます。
  • ProfitTargetPoints をシンボルのポイント サイズに合わせてください。単一のバー内で過度に大きな値がヒットすることはほとんどありません。
  • この戦略は反対の成行注文を送信するため、運用環境に移行する前にデモ データ ソースまたはヘッジ口座で実行してください。
  • 戦略をチャートに添付します: OnStarted は、監視を容易にするために、ローソク足と実行された取引を視覚的なチャートに追加します。
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Two Per Bar Ron strategy - momentum-based direction with EMA confirmation.
/// Buys when momentum crosses above zero and close is above EMA.
/// Sells when momentum crosses below zero and close is below EMA.
/// </summary>
public class TwoPerBarRonStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevMom;
	private bool _hasPrev;

	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public TwoPerBarRonStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 20)
			.SetDisplay("EMA Period", "EMA trend filter", "Indicators");

		_momentumPeriod = Param(nameof(MomentumPeriod), 10)
			.SetDisplay("Momentum Period", "Momentum lookback", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); _prevMom = 0m; _hasPrev = false; }

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

		_hasPrev = false;

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var mom = new Momentum { Length = MomentumPeriod };

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

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

		var close = candle.ClosePrice;

		if (!_hasPrev)
		{
			_prevMom = mom;
			_hasPrev = true;
			return;
		}

		if (_prevMom <= 0 && mom > 0 && close > ema && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		else if (_prevMom >= 0 && mom < 0 && close < ema && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevMom = mom;
	}
}