GitHub で見る

E-News Lucky戦略

概要

E-News Lucky戦略は、MetaTraderのエキスパートアドバイザーe-News-LuckyのStockSharpポートです。このシステムは古典的なニュースブレイクアウトアプローチを自動化します:

  • 設定可能なPlacementTimeで、現在価格周辺にDistancePipsだけオフセットした買いストップと売りストップの両方の注文を送信します。
  • いずれかの待機注文が執行されると、反対の注文は即座にキャンセルされます。設定されたpipオフセットに従って初期保護のストップロスとテイクプロフィットレベルが付加されます。
  • TrailingStopPipsTrailingStepPipsを介してトレーリングストップを有効にし、取引が有利な方向に動くにつれて利益を確保できます。
  • 設定されたCancelTimeに、残っているすべての待機注文が削除され、オープンポジションは取引ウィンドウ外でのリスク保有を避けるために閉じられます。

戦略は予定された時間を追跡しトレーリングストップを更新するためだけにキャンドルデータ(CandleType、デフォルト1分足)を使用します。インジケーターの計算には依存しません。

パラメーター

名前 説明
Volume 各待機エントリーの注文量。戦略はこのボリュームで対称的な買いストップと売りストップ注文を送信します。
StopLossPips エントリー価格と保護ストップロスの間の距離(pips単位)。ストップを無効にするにはゼロに設定。
TakeProfitPips エントリー価格と利益目標の間の距離(pips単位)。目標を無効にするにはゼロに設定。
TrailingStopPips pips単位のトレーリングストップ距離。この値がゼロより大きい場合のみトレーリングエンジンがアクティブになります。
TrailingStepPips トレーリングストップが再び移動する前に必要な最小pip利益。レンジ相場でのストップ更新過多を防ぎます。
DistancePips ストップ注文を配置するために使用される現在価格からのオフセット(pips単位)。
PlacementTime 待機注文が配置されるブローカー/サーバー時間の時刻。デフォルト:10:30。
CancelTime 待機注文がキャンセルされ、オープンポジションが閉じられる時刻。デフォルト:22:30。
CandleType スケジューリングとトレーリングに使用するキャンドルシリーズ。デフォルト:1分足。

実装上の注意

  • pip サイズはMetaTraderのロジックに従います:シンボルが3桁または5桁を持つ場合、戦略はpip単位で作業するために価格ステップに10を掛けます。
  • すべての価格は注文送信前にインストゥルメントの価格ステップに正規化されます。
  • トレーリングストップは最後のクローズをPositionPriceと比較し、利益がTrailingStopPipsTrailingStepPipsの両方を超えたときのみ保護ストップを移動します。
  • 待機注文は配置時間に達したとき、毎取引日に再作成されます。キャンセル時間のチェックにより、ウィンドウの終了時にすべての建玉がフラットになることが保証されます。

使用のヒント

  1. 戦略を流動性の高いタイトなスプレッドのインストゥルメントに適用してください;ブレイクアウト距離はニュース的な価格動向を前提としています。
  2. 対象となる経済カレンダーに従ってPlacementTimeCancelTimeを設定してください。
  3. インストゥルメントのボラティリティに合わせてpip距離を調整してください。値が大きいほど誤ったトリガーの可能性が減り、小さな値はより早い動きを捉えられますがwhipsawリスクが増加します。
  4. 固定ストップが好ましい場合はTrailingStopPipsをゼロに保つことでトレーリングを無効にできます。
  5. 高インパクトのニュース時にスリッページとスプレッドを監視し、待機注文が期待通りに約定されることを確認してください。
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>
/// Scheduled breakout strategy that monitors price around a reference level and enters on breakout.
/// Converted from the original pending-order version to use market orders.
/// </summary>
public class ENewsLuckyStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _trailingStepPips;
	private readonly StrategyParam<decimal> _distancePips;
	private readonly StrategyParam<int> _placementHour;
	private readonly StrategyParam<int> _cancelHour;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _pipSize;
	private decimal? _buyLevel;
	private decimal? _sellLevel;
	private decimal _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;
	private bool _pendingActive;
	private bool _lastWasPlacementDay;

	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	public decimal TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	public decimal DistancePips
	{
		get => _distancePips.Value;
		set => _distancePips.Value = value;
	}

	public int PlacementHour
	{
		get => _placementHour.Value;
		set => _placementHour.Value = value;
	}

	public int CancelHour
	{
		get => _cancelHour.Value;
		set => _cancelHour.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public ENewsLuckyStrategy()
	{
		_stopLossPips = Param(nameof(StopLossPips), 50m)
			.SetDisplay("Stop Loss", "Stop loss in pips", "Trading");

		_takeProfitPips = Param(nameof(TakeProfitPips), 150m)
			.SetDisplay("Take Profit", "Take profit in pips", "Trading");

		_trailingStopPips = Param(nameof(TrailingStopPips), 5m)
			.SetDisplay("Trailing Stop", "Trailing distance in pips", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5m)
			.SetDisplay("Trailing Step", "Minimum trailing step in pips", "Risk");

		_distancePips = Param(nameof(DistancePips), 20m)
			.SetGreaterThanZero()
			.SetDisplay("Entry Distance", "Distance from market in pips", "Trading");

		_placementHour = Param(nameof(PlacementHour), 2)
			.SetDisplay("Placement Hour", "Hour to set breakout levels", "General");

		_cancelHour = Param(nameof(CancelHour), 22)
			.SetDisplay("Cancel Hour", "Hour to cancel and close", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Working candle timeframe", "General");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_pipSize = 0m;
		_buyLevel = null;
		_sellLevel = null;
		_entryPrice = 0m;
		_stopPrice = null;
		_takePrice = null;
		_pendingActive = false;
		_lastWasPlacementDay = false;
	}

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

		var step = Security?.PriceStep ?? 0m;
		_pipSize = step > 0 ? step : 1m;

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

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

		var hour = candle.CloseTime.Hour;
		var price = candle.ClosePrice;

		// Set breakout levels at placement hour
		if (hour == PlacementHour && !_lastWasPlacementDay && Position == 0)
		{
			var distance = DistancePips * _pipSize;
			_buyLevel = price + distance;
			_sellLevel = price - distance;
			_pendingActive = true;
			_lastWasPlacementDay = true;
		}

		if (hour != PlacementHour)
			_lastWasPlacementDay = false;

		// Cancel at cancel hour
		if (hour == CancelHour && _pendingActive)
		{
			_pendingActive = false;
			_buyLevel = null;
			_sellLevel = null;

			if (Position > 0)
				SellMarket(Position);
			else if (Position < 0)
				BuyMarket(-Position);

			_entryPrice = 0m;
			_stopPrice = null;
			_takePrice = null;
			return;
		}

		// Check breakout triggers
		if (_pendingActive && Position == 0)
		{
			if (_buyLevel.HasValue && candle.HighPrice >= _buyLevel.Value)
			{
				var buyLevel = _buyLevel.Value;
				BuyMarket(Volume);
				_entryPrice = buyLevel;
				_stopPrice = StopLossPips > 0 ? _entryPrice - StopLossPips * _pipSize : null;
				_takePrice = TakeProfitPips > 0 ? _entryPrice + TakeProfitPips * _pipSize : null;
				_pendingActive = false;
				_buyLevel = null;
				_sellLevel = null;
			}
			else if (_sellLevel.HasValue && candle.LowPrice <= _sellLevel.Value)
			{
				var sellLevel = _sellLevel.Value;
				SellMarket(Volume);
				_entryPrice = sellLevel;
				_stopPrice = StopLossPips > 0 ? _entryPrice + StopLossPips * _pipSize : null;
				_takePrice = TakeProfitPips > 0 ? _entryPrice - TakeProfitPips * _pipSize : null;
				_pendingActive = false;
				_buyLevel = null;
				_sellLevel = null;
			}
		}

		// Manage open position
		if (Position > 0)
		{
			// Trailing stop
			if (TrailingStopPips > 0 && _entryPrice > 0)
			{
				var trailDist = TrailingStopPips * _pipSize;
				var stepDist = TrailingStepPips * _pipSize;
				if (price - _entryPrice > trailDist + stepDist)
				{
					var newStop = price - trailDist;
					if (!_stopPrice.HasValue || newStop > _stopPrice.Value)
						_stopPrice = newStop;
				}
			}

			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				SellMarket(Position);
				ResetPosition();
				return;
			}

			if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
			{
				SellMarket(Position);
				ResetPosition();
			}
		}
		else if (Position < 0)
		{
			// Trailing stop
			if (TrailingStopPips > 0 && _entryPrice > 0)
			{
				var trailDist = TrailingStopPips * _pipSize;
				var stepDist = TrailingStepPips * _pipSize;
				if (_entryPrice - price > trailDist + stepDist)
				{
					var newStop = price + trailDist;
					if (!_stopPrice.HasValue || newStop < _stopPrice.Value)
						_stopPrice = newStop;
				}
			}

			if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
			{
				BuyMarket(-Position);
				ResetPosition();
				return;
			}

			if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
			{
				BuyMarket(-Position);
				ResetPosition();
			}
		}
	}

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