GitHub で見る

伏撃戦略

伏撃戦略は、バイストップとセルストップ注文のペアで市場を継続的に取り囲みます。待機注文は最良アスクを上回り最良ビッドを下回る設定可能な インデンテーションに配置され、現在のスプレッドに基づく最小距離を強制する動的な上書きがあります。いずれかの側がトリガーされると、 戦略は直ちに両方の注文を再構築し、市場が両方向から「伏撃」された状態を維持します。シンプルなエクイティベースの回路遮断器は、 日次の利益目標または損失限度に達すると、ポジションをフラット化できます。

このC#実装は、ZuzabushによるMT5エキスパートアドバイザーの元の動作を再現します。純粋にレベル1の気配値で動作し、ローソク足や インジケーターを必要としません。すべての決定はリアルタイムのbid/ask変化によって駆動されるため、この戦略はタイトなスプレッドを 持つ流動性の高いインストゥルメントに最適です。

トレーディングロジック

  1. 市場データの取り込み
    • 戦略はレベル1の更新をサブスクライブし、最新の最良ビッドとベストアスクを追跡します。
    • 注文帳の両サイドが利用可能で戦略が取引を許可されるまで、計算は停止します。
  2. エクイティ保護
    • 実現PnL (PnL) と現在のbid/askと PositionPrice から導出された未実現コンポーネントが合算されます。
    • 合算されたエクイティが EquityTakeProfit を超えるか、-EquityStopLoss を下回ると、現在のネットポジションが 成行注文でフラット化されます。待機注文はそのまま維持され、元のエキスパートの動作と一致します。
  3. 待機注文の配置
    • 価格単位でのスプレッドが MaxSpreadPoints と比較されます。スプレッドが広すぎる場合は新しい注文は配置されません。
    • そうでなければ距離が max(IndentationPoints * step, spread * 3) として計算されます。その値は、ユーザーのインデンテーションを 尊重するか、ブローカーの StopsLevel がゼロのときに3スプレッドを強制するMT5ロジックを複製します。
    • バイストップ注文は ask + 距離 に、セルストップは bid - 距離 に配置されます。価格は最も近いティックに正規化されます。 サイドごとに1つのアクティブな注文のみ許可されます。古い注文は状態が DoneFailed、または Canceled に遷移するとクリーンアップされます。
  4. 待機注文のトレーリング
    • TrailingStopPoints がゼロより大きい場合、戦略は定期的に (Pause より頻繁でなく) max((TrailingStopPoints + TrailingStepPoints) * step, spread * 3) を使用してストップ距離を再計算し、変化が半ティックを超えると注文を再登録します。
    • トレーリングは早期のトリガーを避ける最小距離を尊重しながら、注文を市場に近く保ちます。

最終結果は、価格がどちらの方向にも決定的に動くのを常に待っているグリッド型ブレイクアウトエンジンです。

パラメーター

パラメーター 説明
IndentationPoints 市場と各待機ストップ注文間のポイントでの基本距離。
MaxSpreadPoints 最大許容スプレッド (ポイント単位)。スプレッドが広い間は注文が停止されます。
TrailingStopPoints 既存の待機注文に適用するポイントでの基本トレーリング距離。トレーリングを無効にするにはゼロに設定。
TrailingStepPoints トレーリング基本距離の上に追加される追加バッファ。
Pause 2回のトレーリング再計算間の最小時間。デフォルトはMT5エキスパートの1秒停止を反映します。
EquityTakeProfit 即時ポジションフラット化をトリガーする口座通貨でのエクイティ利益。
EquityStopLoss オープンポジションが閉じられる前の許容エクイティドローダウン。
Volume 基底クラス Strategy から継承した注文サイズ。MT5のデフォルトを模倣するにはブローカーの最小値を使用。

すべての価格オフセットは Security.PriceStep を使用してポイントから実際の価格単位に変換されます。インストゥルメントが価格ステップを 公開していない場合、フォールバック値として1が使用されます。

実践的なメモ

  • 戦略はストップ注文のみで動作するため、ローソク足やインジケーターは不要です。レベル1データが利用可能であれば、 過去のローソク足を提供しないバックテスト中にも実行できます。
  • 非ゼロの StopsLevel を強制するブローカーは、結果として生じる価格差が取引所ルールを満たすように IndentationPoints を設定する 必要があります。トリプルスプレッドのセーフティネットは二次的なガードとして機能します。
  • エクイティフィルターは意図的に軽いタッチで、待機注文をキャンセルしません。これは元の伏撃の動作を反映し、手動の介入なしに フラット化イベントの後に新しいトレードを許可します。
  • スリッページと注文フィル許容範囲は接続されたブローカーまたはシミュレーターによって制御されます。インストゥルメントの ボラティリティに合わせて Volume とパラメーター値を調整してください。

この文書は意図的に最大レベルの詳細を提供しているため、裁量トレーダーとアルゴリズムトレーダーの両方が変換を理解し、 自分の執行会場に合わせて戦略をカスタマイズできます。

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>
/// Breakout strategy converted from the Ambush MQL5 expert.
/// Enters on breakouts above/below previous candle range with trailing stop management.
/// </summary>
public class AmbushStrategy : Strategy
{
	private readonly StrategyParam<decimal> _indentationPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<decimal> _trailingStepPoints;
	private readonly StrategyParam<decimal> _equityTakeProfit;
	private readonly StrategyParam<decimal> _equityStopLoss;
	private readonly StrategyParam<DataType> _candleType;

	private ICandleMessage _previousCandle;
	private decimal _entryPrice;
	private decimal? _stopPrice;
	private decimal _priceStep;

	/// <summary>
	/// Distance from the market price for breakout detection, in points.
	/// </summary>
	public decimal IndentationPoints
	{
		get => _indentationPoints.Value;
		set => _indentationPoints.Value = value;
	}

	/// <summary>
	/// Trailing distance for stop orders, in points.
	/// </summary>
	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	/// <summary>
	/// Trailing step added to the base trailing distance, in points.
	/// </summary>
	public decimal TrailingStepPoints
	{
		get => _trailingStepPoints.Value;
		set => _trailingStepPoints.Value = value;
	}

	/// <summary>
	/// Target equity profit that triggers position flattening.
	/// </summary>
	public decimal EquityTakeProfit
	{
		get => _equityTakeProfit.Value;
		set => _equityTakeProfit.Value = value;
	}

	/// <summary>
	/// Maximum equity drawdown allowed before flattening positions.
	/// </summary>
	public decimal EquityStopLoss
	{
		get => _equityStopLoss.Value;
		set => _equityStopLoss.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of the <see cref="AmbushStrategy"/> class.
	/// </summary>
	public AmbushStrategy()
	{
		_indentationPoints = Param(nameof(IndentationPoints), 10m)
			.SetNotNegative()
			.SetDisplay("Indentation (points)", "Distance from price for breakout", "Orders");

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 10m)
			.SetNotNegative()
			.SetDisplay("Trailing Stop (points)", "Base trailing distance", "Orders");

		_trailingStepPoints = Param(nameof(TrailingStepPoints), 1m)
			.SetNotNegative()
			.SetDisplay("Trailing Step (points)", "Additional trailing offset", "Orders");

		_equityTakeProfit = Param(nameof(EquityTakeProfit), 15m)
			.SetNotNegative()
			.SetDisplay("Equity Take Profit", "Flatten positions once this profit is reached", "Risk");

		_equityStopLoss = Param(nameof(EquityStopLoss), 5m)
			.SetNotNegative()
			.SetDisplay("Equity Stop Loss", "Flatten positions after this loss", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(6).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for breakout detection", "General");

		Volume = 1;
	}

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

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

		_previousCandle = null;
		_entryPrice = 0m;
		_stopPrice = null;
		_priceStep = 0m;
	}

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

		_priceStep = Security?.PriceStep ?? 1m;
		if (_priceStep <= 0m) _priceStep = 1m;

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

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

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

		// Check equity targets.
		var pnl = PnL;
		if (EquityTakeProfit > 0m && pnl >= EquityTakeProfit)
		{
			FlattenPosition();
			_previousCandle = candle;
			return;
		}
		if (EquityStopLoss > 0m && pnl <= -EquityStopLoss)
		{
			FlattenPosition();
			_previousCandle = candle;
			return;
		}

		// Check trailing stop.
		if (Position > 0 && _stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
		{
			SellMarket(Position);
			ResetTargets();
		}
		else if (Position < 0 && _stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
		{
			BuyMarket(Math.Abs(Position));
			ResetTargets();
		}

		// Update trailing stop.
		UpdateTrailing(candle);

		// Entry logic - breakout above/below previous candle range.
		if (Position == 0 && _previousCandle != null)
		{
			var indentation = IndentationPoints * _priceStep;
			var buyLevel = _previousCandle.HighPrice + indentation;
			var sellLevel = _previousCandle.LowPrice - indentation;

			if (candle.HighPrice >= buyLevel)
			{
				BuyMarket(Volume);
				_entryPrice = candle.ClosePrice;
				var trailDist = (TrailingStopPoints + TrailingStepPoints) * _priceStep;
				_stopPrice = trailDist > 0m ? _entryPrice - trailDist : null;
			}
			else if (candle.LowPrice <= sellLevel)
			{
				SellMarket(Volume);
				_entryPrice = candle.ClosePrice;
				var trailDist = (TrailingStopPoints + TrailingStepPoints) * _priceStep;
				_stopPrice = trailDist > 0m ? _entryPrice + trailDist : null;
			}
		}

		_previousCandle = candle;
	}

	private void UpdateTrailing(ICandleMessage candle)
	{
		if (TrailingStopPoints <= 0m)
			return;

		var trailDist = (TrailingStopPoints + TrailingStepPoints) * _priceStep;
		if (trailDist <= 0m)
			return;

		if (Position > 0)
		{
			var newStop = candle.ClosePrice - trailDist;
			if (!_stopPrice.HasValue || newStop > _stopPrice.Value)
				_stopPrice = newStop;
		}
		else if (Position < 0)
		{
			var newStop = candle.ClosePrice + trailDist;
			if (!_stopPrice.HasValue || newStop < _stopPrice.Value)
				_stopPrice = newStop;
		}
	}

	private void FlattenPosition()
	{
		if (Position > 0)
			SellMarket(Position);
		else if (Position < 0)
			BuyMarket(Math.Abs(Position));
		ResetTargets();
	}

	private void ResetTargets()
	{
		_entryPrice = 0m;
		_stopPrice = null;
	}
}