GitHub で見る

Expert NEWSエキスパート戦略

概要

Expert NEWS戦略はMQL5ロボット「Expert_NEWS」の直接移植版です。この戦略は現在の市場価格の上下に対称的なストップ注文を継続的に配置し、ブレイクイーブン保護、トレーリングストップ、および保留注文のスケジュール更新によって結果ポジションを管理します。実装はLevel1クォートに依存し、デフォルト取引量は0.1ロットに設定されています。

取引ロジック

  1. クォート購読 – 戦略は最良のビッド/アスク更新を監視し、最新値からオーダー価格を計算します。
  2. 初期ストップ注文 – ロングポジションまたはバイストップが存在しない場合、ask + EntryOffsetTicks * PriceStepに新しいバイストップを配置します。ショートポジションまたはセルストップが存在しない場合、bid - EntryOffsetTicks * PriceStepにセルストップを配置します。
  3. 注文更新OrderRefreshSecondsごとに、必要価格がTrailingStepTicksティック以上乖離した場合、戦略は保留中のストップをキャンセルして再作成します。
  4. ポジション保護 – 約定後、要求距離がMinimumStopTicks制約を満たす場合、戦略は保護的なストップとテイクプロフィット注文を開きます。
  5. ブレイクイーブン制御UseBreakEvenが有効な場合、市場が十分に動き、新しいストップが現在のクォートからの最小距離を尊重すれば、ストップはエントリー ± BreakEvenProfitTicksに引き寄せられます。
  6. トレーリングストップ – 価格がTrailingStartTicks進むと、ストップはTrailingStopTicksを距離として、TrailingStepTicksを最小改善ステップとして追従します。
  7. クリーンアップ – ポジションを閉じると残りの保護注文がすべてキャンセルされます。

パラメーター

パラメーター 説明
StopLossTicks 初期保護ストップ距離(ティック)。初期ストップ注文を無効にするにはゼロに設定します。
TakeProfitTicks 初期テイクプロフィット距離(ティック)。ターゲット注文を無効にするにはゼロに設定します。
TrailingStopTicks トレーリングストップの距離(ティック)。
TrailingStartTicks トレーリングロジックが有効になる前に必要な利益(ティック)。
TrailingStepTicks トレーリングストップまたは保留中のエントリー注文を更新する際の最小改善量。
UseBreakEven 十分な利益がある場合にストップのブレイクイーブンシフトを有効にします。
BreakEvenProfitTicks ストップをブレイクイーブンに移動する際の追加利益クッション。
EntryOffsetTicks 現在のクォートと各新規ストップエントリー注文の間の距離。
OrderRefreshSeconds 保留中のストップ注文の自動更新試行間隔。
MinimumStopTicks ブローカーのストップレベル要件の手動フォールバック。この距離より近いストップは送信されません。

ポジション管理

  • 保護注文は常にネットポジション量と一致します。部分約定はストップとテイクプロフィット注文を自動的にリサイズします。
  • ブレイクイーブンとトレーリングロジックは初期ストップが無効な場合でも機能します;ルールが満たされるとストップは動的に作成されます。
  • 戦略はトレーリング更新が単調な動作を維持するよう、最新のストップ価格をメモリに保持します。

使用上の注意

  • Security.PriceStepが設定されていることを確認してください;すべてのティック距離パラメーターはこの値で乗算されます。
  • デフォルトの取引量は元のロボットを反映した0.1です。別のサイズが必要な場合はVolumeプロパティを調整してください。
  • 取引会場がストップレベル要件を課す場合はMinimumStopTicksをその値に設定してください。最も厳しいストップを許可するにはゼロのままにしてください。
  • アルゴリズムは過去のバーに依存せず、ストリーミングクォートのみで動作できます。
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 MQL Expert NEWS robot.
/// Detects breakouts above/below a reference price range and enters positions.
/// Uses stop loss and take profit for position management.
/// </summary>
public class ExpertNewsStrategy : Strategy
{
	private readonly StrategyParam<decimal> _entryOffset;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<int> _lookbackPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();
	private decimal _entryPrice;
	private int _lastSignal;

	/// <summary>
	/// Entry offset from the high/low range.
	/// </summary>
	public decimal EntryOffset
	{
		get => _entryOffset.Value;
		set => _entryOffset.Value = value;
	}

	/// <summary>
	/// Stop loss distance in price units.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Take profit distance in price units.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Number of bars to determine high/low range.
	/// </summary>
	public int LookbackPeriod
	{
		get => _lookbackPeriod.Value;
		set => _lookbackPeriod.Value = value;
	}

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

	/// <summary>
	/// Constructor.
	/// </summary>
	public ExpertNewsStrategy()
	{
		_entryOffset = Param(nameof(EntryOffset), 200m)
			.SetGreaterThanZero()
			.SetDisplay("Entry Offset", "Offset from range high/low for entry", "Parameters");

		_stopLoss = Param(nameof(StopLoss), 1000m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");

		_takeProfit = Param(nameof(TakeProfit), 2000m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit", "Take profit in price units", "Risk");

		_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Lookback Period", "Bars for range calculation", "Parameters");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_highs.Clear();
		_lows.Clear();
		_entryPrice = 0m;
		_lastSignal = 0;
	}

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

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

		StartProtection(
			new Unit(TakeProfit, UnitTypes.Absolute),
			new Unit(StopLoss, UnitTypes.Absolute));
	}

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

		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);

		if (_highs.Count > LookbackPeriod + 1)
			_highs.RemoveAt(0);
		if (_lows.Count > LookbackPeriod + 1)
			_lows.RemoveAt(0);

		if (_highs.Count <= LookbackPeriod)
			return;

		// Compute range from prior bars (excluding current)
		var rangeHigh = decimal.MinValue;
		var rangeLow = decimal.MaxValue;
		for (int i = 0; i < _highs.Count - 1; i++)
		{
			if (_highs[i] > rangeHigh) rangeHigh = _highs[i];
			if (_lows[i] < rangeLow) rangeLow = _lows[i];
		}

		var close = candle.ClosePrice;
		var breakoutUp = close > rangeHigh + EntryOffset;
		var breakoutDown = close < rangeLow - EntryOffset;

		if (breakoutUp && _lastSignal != 1 && Position <= 0)
		{
			BuyMarket();
			_entryPrice = close;
			_lastSignal = 1;
		}
		else if (breakoutDown && _lastSignal != -1 && Position >= 0)
		{
			SellMarket();
			_entryPrice = close;
			_lastSignal = -1;
		}
		else if (!breakoutUp && !breakoutDown)
			_lastSignal = 0;
	}
}