GitHub で見る

ブル対メドベドのウィンドウ戦略

概要

ブル対メドベド戦略は、MetaTrader 4 エキスパート Bull_vs_Medved.mq4 を StockSharp に変換したものです。システムは次のことを試みます あらかじめ定義された5分間に6回の保留中の指値注文を発注することで、強い強気または弱気の衝動の中で引き戻しに入る ウィンドウは取引日全体に広がります。 StockSharp バージョンでは、ウィンドウごとに 1 回だけ取引するという考えが維持され、古いものはキャンセルされます 保留中の注文を処理し、シグナルローソクの実体サイズを使用して動的なストップロスとテイクプロフィットの距離を導き出します。

取引ロジック

  1. CandleType によって定義されたキャンドル ストリームをサブスクライブし、完成したキャンドルのみを処理します。
  2. 最後の 2 つの完了したローソク足を維持します。つまり、現在のローソク足 (shift1)、前のローソク足 (shift2)、そしてローソク足 その前に (shift3) は、MetaTrader で使用される Close[1..3] 参照を複製します。
  3. 各取引ウィンドウ (StartTime0..5 から始まる EntryWindowMinutes 分) 中に、次のパターンを確認してください。
    • 強気: shift3shift2 の始値を上回って終了します。shift2 の本体は少なくとも 10 ブローカー ポイントであり、本体は shift1 は少なくとも CandleSizePoints ポイントです。 IsBadBull が false (ロングボディが 3 つ連続) の場合、購入制限を設定します。
    • クールブル: shift2 は最低 20 ポイントのプルバックであり、shift1 の始値を下回って終了し、その後、shift1 を上回って終了します。 shift2 は、しきい値の少なくとも 40% の本文で開きます。購入制限を設けます。
    • 弱気: shift1 の本体は少なくとも CandleSizePoints ポイントですが、弱気です。売り指値を設定します。
  4. 買い指値は ask - BuyIndentPoints * PriceStep、売り指値は bid + SellIndentPoints * PriceStep に設定されます。一つだけ 未決注文またはポジションが一度に存在する可能性があるため、取引がすでにアクティブである場合、戦略は新しいシグナルをスキップします。 窓。
  5. 戦略の中にストップとターゲットが隠されています。エントリー注文が約定すると、shift1 のローソク足本体に が乗算されます。 StopLossMultiplierTakeProfitMultiplier は、PriceStep に正規化され、出口価格として保存されます。
  6. 終了したローソクごとに、戦略は高値/安値が保存されているストップまたはターゲットを突破したかどうかを評価します。レベルに達する 成行注文でオープンポジションをクローズし、保護フラグをクリアします。
  7. 230 分を超えた保留中の注文は、MetaTrader のクリーンアップ ルーチンを模倣するためにキャンセルされ、_orderPlacedInWindow は 価格が取引ウィンドウを離れるとリセットされます。

パラメーター

名前 種類 デフォルト 説明
OrderVolume decimal 0.1 各指値注文に使用されるボリューム。
CandleSizePoints decimal 75 シグナルローソク足の最小強気/弱気本体サイズ (ブローカーポイント単位)。
StopLossMultiplier decimal 0.8 停止距離を構築するためにシグナルキャンドル本体に適用される乗数。
TakeProfitMultiplier decimal 0.8 ターゲット距離を構築するためにシグナルキャンドル本体に適用される乗数。
BuyIndentPoints decimal 16 買い指値を設定するときにアスクから差し引かれるブローカー ポイントの数。
SellIndentPoints decimal 20 売り指値を設定するときに入札に追加されるブローカー ポイントの数。
EntryWindowMinutes int 5 各セッションの所要時間 (分単位)。
CandleType DataType 5分キャンドル 戦略的に加工されたキャンドルシリーズ。
StartTime0..5 TimeSpan 00:05, 04:05, 08:05, 12:05, 16:05, 20:05 各取引ウィンドウの開始時間。

元の専門家との違い

  • MetaTrader エキスパートは、未決注文自体にストップロスとテイクプロフィットを割り当てます。 StockSharp ポートはそれをシミュレートします 隠れレベルを保存し、ローソク足がそれをブレイクしたときに成行注文でネットポジションを閉じることによって動作します。
  • 価格しきい値には Security.PriceStep が使用されるため、追加の追加なしで 4 桁と 5 桁の両方の外国為替相場で変換が機能します。 パラメータ。
  • 終了したローソク足のみがストップ/ターゲット ルールの評価に使用されますが、MetaTrader ストップはバー内でトリガーできます。 トレードサーバー。
  • 元の EA のサウンド アラートとコメント フィールドは省略されています。代わりに、StockSharp ログがフィードバックを提供します。

使い方のヒント

  • この戦略は、端数ピップ価格設定を使用する外国為替シンボル向けに設計されています。 PriceStep を確認してポイントベースであることを確認してください フィルターは意図したピップ距離と一致します。
  • ストップとテイクプロフィットは隠されているため、専用環境で戦略を実行するか、戦略を保護することを検討してください。 接続が切断された場合のブローカー側のリスク モジュール。
  • ブローカー セッションが元の GMT ベースのスケジュールと異なる場合は、StartTime の値を調整します。各ウィンドウは次の方法で無効にできます。 取引日以外の開始時刻を設定する。
  • 戦略をチャートに添付して指値注文を視覚化し、各ウィンドウで 1 つのエントリーのみが試行されることを確認します。
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>
/// Bull vs Medved strategy converted from MetaTrader 4.
/// Enters market orders during predefined intraday windows when multi-candle patterns appear.
/// Exits on candle-based stop-loss / take-profit levels.
/// </summary>
public class BullVsMedvedWindowStrategy : Strategy
{
	private readonly StrategyParam<decimal> _candleSizePoints;
	private readonly StrategyParam<decimal> _stopLossMultiplier;
	private readonly StrategyParam<decimal> _takeProfitMultiplier;
	private readonly StrategyParam<int> _entryWindowMinutes;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<TimeSpan> _startTime0;
	private readonly StrategyParam<TimeSpan> _startTime1;
	private readonly StrategyParam<TimeSpan> _startTime2;
	private readonly StrategyParam<TimeSpan> _startTime3;
	private readonly StrategyParam<TimeSpan> _startTime4;
	private readonly StrategyParam<TimeSpan> _startTime5;

	private decimal _pointValue;
	private decimal _candleSizeThreshold;
	private decimal _bodyMinSize;
	private decimal _pullbackSize;

	private ICandleMessage _previousCandle1;
	private ICandleMessage _previousCandle2;

	private TimeSpan[] _entryTimes = Array.Empty<TimeSpan>();
	private TimeSpan _entryWindow = TimeSpan.Zero;
	private bool _orderPlacedInWindow;

	private decimal _entryPrice;

	private decimal? _longStopPrice;
	private decimal? _longTakePrice;
	private decimal? _shortStopPrice;
	private decimal? _shortTakePrice;
	private bool _exitRequested;

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public BullVsMedvedWindowStrategy()
	{
		_candleSizePoints = Param(nameof(CandleSizePoints), 75m)
			.SetDisplay("Body Size (points)", "Minimum body size for the latest candle", "Filters")
			.SetGreaterThanZero();

		_stopLossMultiplier = Param(nameof(StopLossMultiplier), 0.8m)
			.SetDisplay("Stop Multiplier", "Coefficient applied to the candle body for stop-loss", "Risk")
			.SetGreaterThanZero();

		_takeProfitMultiplier = Param(nameof(TakeProfitMultiplier), 0.8m)
			.SetDisplay("Take Profit Multiplier", "Coefficient applied to the candle body for take-profit", "Risk")
			.SetGreaterThanZero();

		_entryWindowMinutes = Param(nameof(EntryWindowMinutes), 10)
			.SetDisplay("Entry Window", "Duration of each trading window in minutes", "Timing")
			.SetGreaterThanZero();

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe for pattern detection", "Data");

		_startTime0 = Param(nameof(StartTime0), new TimeSpan(0, 5, 0))
			.SetDisplay("Start Time #1", "First trading window start", "Timing");

		_startTime1 = Param(nameof(StartTime1), new TimeSpan(4, 5, 0))
			.SetDisplay("Start Time #2", "Second trading window start", "Timing");

		_startTime2 = Param(nameof(StartTime2), new TimeSpan(8, 5, 0))
			.SetDisplay("Start Time #3", "Third trading window start", "Timing");

		_startTime3 = Param(nameof(StartTime3), new TimeSpan(12, 5, 0))
			.SetDisplay("Start Time #4", "Fourth trading window start", "Timing");

		_startTime4 = Param(nameof(StartTime4), new TimeSpan(16, 5, 0))
			.SetDisplay("Start Time #5", "Fifth trading window start", "Timing");

		_startTime5 = Param(nameof(StartTime5), new TimeSpan(20, 5, 0))
			.SetDisplay("Start Time #6", "Sixth trading window start", "Timing");
	}

	/// <summary>
	/// Minimum bullish or bearish body size in broker points.
	/// </summary>
	public decimal CandleSizePoints
	{
		get => _candleSizePoints.Value;
		set => _candleSizePoints.Value = value;
	}

	/// <summary>
	/// Multiplier applied to the signal candle body to calculate the stop-loss distance.
	/// </summary>
	public decimal StopLossMultiplier
	{
		get => _stopLossMultiplier.Value;
		set => _stopLossMultiplier.Value = value;
	}

	/// <summary>
	/// Multiplier applied to the signal candle body to calculate the take-profit distance.
	/// </summary>
	public decimal TakeProfitMultiplier
	{
		get => _takeProfitMultiplier.Value;
		set => _takeProfitMultiplier.Value = value;
	}

	/// <summary>
	/// Duration of each trading window in minutes.
	/// </summary>
	public int EntryWindowMinutes
	{
		get => _entryWindowMinutes.Value;
		set => _entryWindowMinutes.Value = value;
	}

	/// <summary>
	/// Candle type used to evaluate price patterns.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// First trading window start time.
	/// </summary>
	public TimeSpan StartTime0
	{
		get => _startTime0.Value;
		set => _startTime0.Value = value;
	}

	/// <summary>
	/// Second trading window start time.
	/// </summary>
	public TimeSpan StartTime1
	{
		get => _startTime1.Value;
		set => _startTime1.Value = value;
	}

	/// <summary>
	/// Third trading window start time.
	/// </summary>
	public TimeSpan StartTime2
	{
		get => _startTime2.Value;
		set => _startTime2.Value = value;
	}

	/// <summary>
	/// Fourth trading window start time.
	/// </summary>
	public TimeSpan StartTime3
	{
		get => _startTime3.Value;
		set => _startTime3.Value = value;
	}

	/// <summary>
	/// Fifth trading window start time.
	/// </summary>
	public TimeSpan StartTime4
	{
		get => _startTime4.Value;
		set => _startTime4.Value = value;
	}

	/// <summary>
	/// Sixth trading window start time.
	/// </summary>
	public TimeSpan StartTime5
	{
		get => _startTime5.Value;
		set => _startTime5.Value = value;
	}

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

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

		_pointValue = 0m;
		_candleSizeThreshold = 0m;
		_bodyMinSize = 0m;
		_pullbackSize = 0m;
		_entryWindow = TimeSpan.Zero;

		_previousCandle1 = null;
		_previousCandle2 = null;
		_entryTimes = Array.Empty<TimeSpan>();
		_orderPlacedInWindow = false;

		_entryPrice = 0m;

		_longStopPrice = null;
		_longTakePrice = null;
		_shortStopPrice = null;
		_shortTakePrice = null;
		_exitRequested = false;
	}

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

		_pointValue = Security?.PriceStep ?? 1m;
		_candleSizeThreshold = CandleSizePoints * _pointValue;
		_bodyMinSize = 10m * _pointValue;
		_pullbackSize = 20m * _pointValue;
		_entryWindow = TimeSpan.FromMinutes(EntryWindowMinutes);

		_entryTimes = new[]
		{
			StartTime0,
			StartTime1,
			StartTime2,
			StartTime3,
			StartTime4,
			StartTime5,
		};

		SubscribeCandles(CandleType)
			.Bind(ProcessCandle)
			.Start();
	}

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

		if (HandlePositionExits(candle))
		{
			ShiftHistory(candle);
			return;
		}

		var inWindow = IsWithinEntryWindow(candle.CloseTime);
		if (!inWindow)
		{
			_orderPlacedInWindow = false;
			ShiftHistory(candle);
			return;
		}

		if (_orderPlacedInWindow || Position != 0m)
		{
			ShiftHistory(candle);
			return;
		}

		if (_previousCandle1 is null || _previousCandle2 is null)
		{
			ShiftHistory(candle);
			return;
		}

		var shift1 = candle;
		var shift2 = _previousCandle1;
		var shift3 = _previousCandle2;

		var placedOrder = false;

		var isBull = IsBull(shift3, shift2, shift1);
		var isBadBull = IsBadBull(shift3, shift2, shift1);
		var isCoolBull = IsCoolBull(shift2, shift1);
		var isBear = IsBear(shift1);

		if (isBull && !isBadBull)
			placedOrder = TryBuyMarket(shift1);
		else if (isCoolBull)
			placedOrder = TryBuyMarket(shift1);
		else if (isBear)
			placedOrder = TrySellMarket(shift1);

		if (placedOrder)
			_orderPlacedInWindow = true;

		ShiftHistory(candle);
	}

	private bool HandlePositionExits(ICandleMessage candle)
	{
		if (Position > 0m)
		{
			if (!_exitRequested && _longStopPrice is decimal stop && candle.LowPrice <= stop)
			{
				_exitRequested = true;
				SellMarket();
				ResetProtectionLevels();
				return true;
			}

			if (!_exitRequested && _longTakePrice is decimal take && candle.HighPrice >= take)
			{
				_exitRequested = true;
				SellMarket();
				ResetProtectionLevels();
				return true;
			}
		}
		else if (Position < 0m)
		{
			if (!_exitRequested && _shortStopPrice is decimal stop && candle.HighPrice >= stop)
			{
				_exitRequested = true;
				BuyMarket();
				ResetProtectionLevels();
				return true;
			}

			if (!_exitRequested && _shortTakePrice is decimal take && candle.LowPrice <= take)
			{
				_exitRequested = true;
				BuyMarket();
				ResetProtectionLevels();
				return true;
			}
		}

		return false;
	}

	private bool TryBuyMarket(ICandleMessage referenceCandle)
	{
		var body = (referenceCandle.ClosePrice - referenceCandle.OpenPrice).Abs();
		var stopDistance = RoundToPoint(body * StopLossMultiplier);
		var takeDistance = RoundToPoint(body * TakeProfitMultiplier);

		var price = referenceCandle.ClosePrice;

		BuyMarket();

		_entryPrice = price;
		_longStopPrice = stopDistance > 0m ? NormalizePrice(price - stopDistance) : null;
		_longTakePrice = takeDistance > 0m ? NormalizePrice(price + takeDistance) : null;
		_shortStopPrice = null;
		_shortTakePrice = null;
		_exitRequested = false;

		return true;
	}

	private bool TrySellMarket(ICandleMessage referenceCandle)
	{
		var body = (referenceCandle.ClosePrice - referenceCandle.OpenPrice).Abs();
		var stopDistance = RoundToPoint(body * StopLossMultiplier);
		var takeDistance = RoundToPoint(body * TakeProfitMultiplier);

		var price = referenceCandle.ClosePrice;

		SellMarket();

		_entryPrice = price;
		_shortStopPrice = stopDistance > 0m ? NormalizePrice(price + stopDistance) : null;
		_shortTakePrice = takeDistance > 0m ? NormalizePrice(price - takeDistance) : null;
		_longStopPrice = null;
		_longTakePrice = null;
		_exitRequested = false;

		return true;
	}

	private bool IsWithinEntryWindow(DateTimeOffset time)
	{
		if (_entryWindow <= TimeSpan.Zero)
			return false;

		var tod = time.TimeOfDay;

		for (var i = 0; i < _entryTimes.Length; i++)
		{
			var start = _entryTimes[i];
			var end = start + _entryWindow;

			if (tod >= start && tod <= end)
				return true;
		}

		return false;
	}

	private void ShiftHistory(ICandleMessage candle)
	{
		_previousCandle2 = _previousCandle1;
		_previousCandle1 = candle;
	}

	private bool IsBull(ICandleMessage shift3, ICandleMessage shift2, ICandleMessage shift1)
	{
		return shift3.ClosePrice > shift2.OpenPrice &&
			(shift2.ClosePrice - shift2.OpenPrice) >= _bodyMinSize &&
			(shift1.ClosePrice - shift1.OpenPrice) >= _candleSizeThreshold;
	}

	private bool IsBadBull(ICandleMessage shift3, ICandleMessage shift2, ICandleMessage shift1)
	{
		return (shift3.ClosePrice - shift3.OpenPrice) >= _bodyMinSize &&
			(shift2.ClosePrice - shift2.OpenPrice) >= _bodyMinSize &&
			(shift1.ClosePrice - shift1.OpenPrice) >= _candleSizeThreshold;
	}

	private bool IsCoolBull(ICandleMessage shift2, ICandleMessage shift1)
	{
		return (shift2.OpenPrice - shift2.ClosePrice) >= _pullbackSize &&
			shift2.ClosePrice <= shift1.OpenPrice &&
			shift1.ClosePrice > shift2.OpenPrice &&
			(shift1.ClosePrice - shift1.OpenPrice) >= 0.4m * _candleSizeThreshold;
	}

	private bool IsBear(ICandleMessage shift1)
	{
		return (shift1.OpenPrice - shift1.ClosePrice) >= _candleSizeThreshold;
	}

	private decimal NormalizePrice(decimal price)
	{
		if (_pointValue <= 0m)
			return price;

		var steps = price / _pointValue;
		var roundedSteps = decimal.Round(steps, MidpointRounding.AwayFromZero);
		return roundedSteps * _pointValue;
	}

	private decimal RoundToPoint(decimal value)
	{
		if (_pointValue <= 0m)
			return value;

		var steps = value / _pointValue;
		var roundedSteps = decimal.Round(steps, MidpointRounding.AwayFromZero);
		return roundedSteps * _pointValue;
	}

	private void ResetProtectionLevels()
	{
		_longStopPrice = null;
		_longTakePrice = null;
		_shortStopPrice = null;
		_shortTakePrice = null;
	}
}