GitHub で見る

オープンクローズ戦略 (ID 3996)

概要

この戦略は、MetaTrader 4 エキスパート open_close.mq4 を再現しています。これは単一の金融商品で動作し、最新のローソク足の始値と終値を前のローソク足と比較します。アクティブなポジションがない場合、強い 1 バーの動き (ギャップ アンド リバーサル パターン) がフェードアウトします。取引中、パターンが反転したとき、または変動損失保護のしきい値に違反したときにポジションを閉じます。

取引ロジック

エントリールール

  • 前のローソク足が処理された場合にのみ取引されます(元の Volume[0] == 1 ガード)。
  • ロングエントリー: 現在のローソク足は前の始値より上で始まり**、**前の終値より下で閉じます。この戦略では、設定された量を市場で購入します。
  • ショートエントリー: 現在のローソク足は前の始値よりも下で始まり**、**前の終値よりも上で閉じます。この戦略は市場で空売りされる。

いつでもアクティブにできるポジションは 1 つだけです。オープンポジションがクローズされるまで、新しいシグナルは無視されます。

終了ルール

  1. リスク保護: 変動損益は平均エントリー価格から測定されます。未実現損失が MaximumRisk × Portfolio.CurrentValue を超える場合、ストラテジーは直ちにポジションをクローズします。元の MQL バージョンでは AccountMargin が使用されていましたが、ここでは利用可能な最良のポートフォリオ評価で近似しています。
  2. パターン反転:
    • 次のローソク足が下降を続けるとロングポジションが終了します (open < previous openclose < previous close)。
    • 次のローソク足が上昇を続けるとショートポジションは終了します(open > previous openclose > previous close)。

ポジションサイズ

  • デフォルトの注文サイズは MaximumRisk から導出されます。この戦略は、利用可能なアカウントの値に MaximumRisk を乗算し、その結果を 1000 で除算し、AccountFreeMargin * MaximumRisk / 1000 の MetaTrader の計算を模倣します。
  • アカウント情報が利用できない場合は、フォールバック InitialVolume パラメータが使用されます。
  • 複数回連続して取引に負けた後、ロットサイズは volume × losses / DecreaseFactor だけ減少し、終了した取引の履歴で MetaTrader ループが再現されます。
  • 数量を商品の取引量ステップと為替制限に合わせる前に、0.1 ロットの最小取引可能量が適用されます。

パラメーター

名前 種類 デフォルト 説明
InitialVolume decimal 0.1 株式情報が利用できない場合に使用されるフォールバック ロット サイズ。
MaximumRisk decimal 0.3 ポジションのサイジングと最大許容変動損失の両方を制御する口座価値の割合。
DecreaseFactor decimal 100 複数回連続で負けたトレード後に適用される減額係数。
CandleType DataType 15m 時間枠 パターンの評価に使用されるキャンドル シリーズ。

実装メモ

  • このストラテジーは、選択されたローソク足シリーズをサブスクライブし、終了したローソク足のみを処理し、元のエキスパートの Volume[0] > 1 条件と一致します。
  • StockSharp は MetaTrader の AccountProfit および AccountMargin 指標を公開していないため、変動損益はストラテジーの現在のポジションと最新の終値から推定されます。
  • 連続損失は約定取引を通じて追跡されるため、DecreaseFactor は取引履歴の元のループのように動作できます。
  • ボリューム調整では、交換要件との互換性を維持するために、Security.VolumeStepMinVolume、および MaxVolume が考慮されます。
  • チャート領域が視覚的なデバッグに使用できる場合、チャートにはローソク足と独自の取引が表示されます。

使用のヒント

  • 元のエキスパートを調整するときに、MetaTrader で使用されるローソク足の間隔と一致するローソク足の間隔を選択します。
  • MaximumRiskDecreaseFactor を調整して、ロットサイジング ルールの積極性を調整します。
  • この戦略は逆張りであるため、単一バーのオーバーエクステンションやスナップバックの動きを頻繁に示す商品で最も優れたパフォーマンスを発揮します。
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>
/// Contrarian pattern strategy converted from the MetaTrader expert "open_close".
/// Evaluates relationships between consecutive candle opens and closes.
/// Buys when a bearish candle opens above the previous open (fading the move),
/// and sells when a bullish candle opens below the previous open.
/// </summary>
public class OpenCloseStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _ema;

	private bool _hasPreviousCandle;
	private decimal _previousOpen;
	private decimal _previousClose;

	public OpenCloseStrategy()
	{
		_stopLossPoints = Param(nameof(StopLossPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop loss in absolute points", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take profit in absolute points", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Time-frame used to evaluate the open/close pattern.", "Data");

		Volume = 1;
	}

	/// <summary>
	/// Stop loss distance in absolute points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take profit distance in absolute points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Candle series used to evaluate the pattern.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_hasPreviousCandle = false;
		_previousOpen = 0m;
		_previousClose = 0m;
		_ema = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		_ema = new ExponentialMovingAverage { Length = 20 };

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

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

		var tp = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null;
		var sl = StopLossPoints > 0 ? new Unit(StopLossPoints, UnitTypes.Absolute) : null;
		if (tp != null || sl != null)
			StartProtection(tp, sl);

		base.OnStarted2(time);
	}

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

		var open = candle.OpenPrice;
		var close = candle.ClosePrice;

		if (!_hasPreviousCandle)
		{
			_previousOpen = open;
			_previousClose = close;
			_hasPreviousCandle = true;
			return;
		}

		// Exit logic
		if (Position > 0)
		{
			// Close long on bearish continuation
			if (open < _previousOpen && close < _previousClose)
				SellMarket(Position);
		}
		else if (Position < 0)
		{
			// Close short on bullish continuation
			if (open > _previousOpen && close > _previousClose)
				BuyMarket(Math.Abs(Position));
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_previousOpen = open;
			_previousClose = close;
			return;
		}

		// Entry logic
		if (Position == 0)
		{
			// Buy: fade a bearish candle that opened above the previous open
			if (open > _previousOpen && close < _previousClose)
			{
				BuyMarket(Volume);
			}
			// Sell: fade a bullish candle that opened below the previous open
			else if (open < _previousOpen && close > _previousClose)
			{
				SellMarket(Volume);
			}
		}

		_previousOpen = open;
		_previousClose = close;
	}
}