GitHub で見る

トレードパネル自動操縦戦略

概要

この戦略は、オリジナルのMQL4「trade panel with autopilot」エキスパートのコアロジックを再現しています。複数の時間軸にわたって価格方向を集計し、支配的な市場センチメントに従って単一のポジションを開いたり閉じたりします。

戦略は8つの異なる時間軸(M1、M5、M15、M30、H1、H4、D1、W1)で最新の2本のローソク足を監視します。各時間軸で、最新の2本のローソク足間の複数の価格コンポーネントを比較します:

  • Open
  • High
  • Low
  • (High + Low) / 2
  • Close
  • (High + Low + Close) / 3
  • (High + Low + Close + Close) / 4

各比較は買いまたは売りスコアに貢献します。すべての時間軸からのスコアが合計され、パーセンテージに変換されます。買いまたは売りのパーセンテージが設定された閾値を超えると、戦略はポジションに入ります。反対方向のパーセンテージが決済閾値を下回ると、既存ポジションが決済されます。

パラメーター

  • Autopilot — 自動売買を有効または無効にします。
  • OpenThreshold — 新しいポジションを開くために必要なパーセンテージレベル。デフォルト: 85。
  • CloseThreshold — 既存ポジションを閉じるためのパーセンテージレベル。デフォルト: 55。
  • LotFixedUseFixedLot が有効な場合の固定注文ボリューム。
  • LotPercentUseFixedLot が無効な場合の、ポートフォリオ価値に対するボリュームの割合。
  • UseFixedLot — 固定ボリュームとパーセンテージボリュームを切り替えます。
  • UseStopLoss — 有効にするとポジション保護を開始します。

トレードロジック

  1. 設定されたすべての時間軸でローソク足を購読します。
  2. 新しい完成したローソク足ごとに買い/売りスコアを計算します。
  3. 時間軸全体のスコアを合計し、買い/売りのパーセンテージを計算します。
  4. Autopilot が無効な場合、戦略はスコアの追跡のみを行います。
  5. ポジションが開いておらず、買いのパーセンテージが OpenThreshold を超えた場合、ロングポジションに入ります。売りのパーセンテージが閾値を超えた場合、ショートポジションに入ります。
  6. ロングポジションが存在し、買いのパーセンテージが CloseThreshold を下回った場合、ポジションを決済します。ショートポジションには売りのパーセンテージを使用して同じロジックが適用されます。

注意事項

  • 戦略は常に最大1つのオープンポジションを維持します。
  • UseStopLoss が真の場合、StartProtection() を通じてオプションのストップロス管理が有効化されます。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trade panel autopilot strategy v2.
/// Aggregates 7 price comparison metrics over rolling window.
/// Buys when buy percentage exceeds open threshold, sells on opposite.
/// </summary>
public class TradePanelAutopilotStrategy : Strategy
{
	private readonly StrategyParam<decimal> _openThreshold;
	private readonly StrategyParam<decimal> _closeThreshold;
	private readonly StrategyParam<int> _windowSize;
	private readonly StrategyParam<DataType> _candleType;

	private readonly Queue<(int buy, int sell)> _signalWindow = new();
	private ICandleMessage _prevCandle;

	public decimal OpenThreshold { get => _openThreshold.Value; set => _openThreshold.Value = value; }
	public decimal CloseThreshold { get => _closeThreshold.Value; set => _closeThreshold.Value = value; }
	public int WindowSize { get => _windowSize.Value; set => _windowSize.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public TradePanelAutopilotStrategy()
	{
		_openThreshold = Param(nameof(OpenThreshold), 65m)
			.SetDisplay("Open %", "Threshold for new position", "General");

		_closeThreshold = Param(nameof(CloseThreshold), 40m)
			.SetDisplay("Close %", "Threshold for closing", "General");

		_windowSize = Param(nameof(WindowSize), 8)
			.SetGreaterThanZero()
			.SetDisplay("Window Size", "Number of candles for signal aggregation", "General");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevCandle = null;
		_signalWindow.Clear();
	}

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

		_prevCandle = null;
		_signalWindow.Clear();

		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;

		if (_prevCandle == null)
		{
			_prevCandle = candle;
			return;
		}

		int buy = 0, sell = 0;

		if (candle.OpenPrice > _prevCandle.OpenPrice) buy++; else sell++;
		if (candle.HighPrice > _prevCandle.HighPrice) buy++; else sell++;
		if (candle.LowPrice > _prevCandle.LowPrice) buy++; else sell++;
		if (candle.ClosePrice > _prevCandle.ClosePrice) buy++; else sell++;

		var hlCurr = (candle.HighPrice + candle.LowPrice) / 2m;
		var hlPrev = (_prevCandle.HighPrice + _prevCandle.LowPrice) / 2m;
		if (hlCurr > hlPrev) buy++; else sell++;

		var hlcCurr = (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m;
		var hlcPrev = (_prevCandle.HighPrice + _prevCandle.LowPrice + _prevCandle.ClosePrice) / 3m;
		if (hlcCurr > hlcPrev) buy++; else sell++;

		var hlccCurr = (candle.HighPrice + candle.LowPrice + 2m * candle.ClosePrice) / 4m;
		var hlccPrev = (_prevCandle.HighPrice + _prevCandle.LowPrice + 2m * _prevCandle.ClosePrice) / 4m;
		if (hlccCurr > hlccPrev) buy++; else sell++;

		_signalWindow.Enqueue((buy, sell));

		while (_signalWindow.Count > WindowSize)
			_signalWindow.Dequeue();

		_prevCandle = candle;

		if (_signalWindow.Count < WindowSize)
			return;

		int totalBuy = 0, totalSell = 0;
		foreach (var (b, s) in _signalWindow)
		{
			totalBuy += b;
			totalSell += s;
		}

		var total = totalBuy + totalSell;
		if (total == 0) return;

		var buyPct = (decimal)totalBuy / total * 100m;
		var sellPct = (decimal)totalSell / total * 100m;

		if (Position > 0 && buyPct < CloseThreshold)
			SellMarket();
		else if (Position < 0 && sellPct < CloseThreshold)
			BuyMarket();

		if (Position == 0)
		{
			if (buyPct >= OpenThreshold)
				BuyMarket();
			else if (sellPct >= OpenThreshold)
				SellMarket();
		}
	}
}