GitHub で見る

口座通貨での損益のクローズ

この戦略は、MetaTrader エキスパート アドバイザー Close_on_PROFIT_or_LOSS_inAccont_Currency を再現します。戦略が関連付けられているポートフォリオの資産を継続的に監視し、設定された利益目標またはドローダウンの下限に達すると、すべてのオープンポジションを清算し、戦略によって管理されているすべての未決注文をキャンセルします。このクラスは、StockSharp の高レベルの API に依存しています。つまり、ローソク足のサブスクリプションがハートビートを提供し、CancelActiveOrders() が約定待ち注文を削除し、ClosePosition() が成行注文を通じてエクスポージャーを平坦化します。

仕組み

  1. この戦略は、ハートビート ローソク足が閉じるたびに現在の株式 (Portfolio.CurrentValue) をポーリングし続けます。
  2. 資本が ポジティブ クロージャ 以上の場合、ストラテジーは完全クローズ リクエストを送信します。
  3. 資本が ネガティブ クロージャー 以下の場合、損失を制限するために同じ清算ルーチンが実行されます。
  4. 清算中、ストラテジーはすべての未決注文をキャンセルし、成行注文を送信してすべてのアクティブなポジションを閉じ、最後に戦略自体を停止します (元の EA からの ExpertRemove() 呼び出しをミラーリングします)。

重要: しきい値はアカウント通貨で設定してください。元の動作をエミュレートするには、現在の株式よりも上の ポジティブ クロージャー 値を選択し、それより下の ネガティブ クロージャー 値を選択します。それ以外の場合は、開始時にすぐに終了がトリガーされます。

パラメーター

名前 説明 デフォルト
PositiveClosureInAccountCurrency これを超えると完全清算が開始される資本レベル。 0
NegativeClosureInAccountCurrency 到達すると強制的に清算される株価下限。 0
CandleType 株式チェックを行うハートビート ローソク足に使用されるタイムフレーム。反応を速くするにはこれを減らします。 1 minute

注意事項

  • StartProtection() は開始時にアクティブ化され、元の安全動作がコピーされます。
  • この戦略は、管理するポジションと注文のみと対話します。保護したい取引を保持するポートフォリオにそれを添付します。
  • StockSharp 成行注文はコネクタ固有の執行コストをすでに考慮しているため、個別のスプレッド/スリッページ入力はありません。
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;

using StockSharp.Algo.Candles;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Closes all positions when portfolio equity reaches configured profit or loss thresholds.
/// Pending orders are cancelled before liquidating the exposure.
/// </summary>
public class CloseOnProfitOrLossInAccountCurrencyStrategy : Strategy
{
	private readonly StrategyParam<decimal> _positiveClosure;
	private readonly StrategyParam<decimal> _negativeClosure;
	private readonly StrategyParam<DataType> _candleType;

	private bool _closeRequested;
	private SimpleMovingAverage _smaFast;
	private SimpleMovingAverage _smaSlow;

	/// <summary>
	/// Equity level in account currency that triggers closing all positions when exceeded.
	/// </summary>
	public decimal PositiveClosureInAccountCurrency
	{
		get => _positiveClosure.Value;
		set => _positiveClosure.Value = value;
	}

	/// <summary>
	/// Equity level in account currency that triggers closing all positions when reached on drawdown.
	/// </summary>
	public decimal NegativeClosureInAccountCurrency
	{
		get => _negativeClosure.Value;
		set => _negativeClosure.Value = value;
	}

	/// <summary>
	/// Candle type used as a heartbeat to evaluate portfolio equity.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="CloseOnProfitOrLossInAccountCurrencyStrategy"/> class.
	/// </summary>
	public CloseOnProfitOrLossInAccountCurrencyStrategy()
	{
		_positiveClosure = Param(nameof(PositiveClosureInAccountCurrency), 0m)
			.SetDisplay("Positive Closure", "Equity level that triggers full liquidation", "Risk");

		_negativeClosure = Param(nameof(NegativeClosureInAccountCurrency), 0m)
			.SetDisplay("Negative Closure", "Equity floor that forces liquidation", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Heartbeat Candle", "Candle type that triggers equity checks", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		if (Security == null)
			return Array.Empty<(Security, DataType)>();

		return new (Security, DataType)[] { (Security, CandleType) };
	}

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

		if (Portfolio == null)
			throw new InvalidOperationException("Portfolio cannot be null.");

		if (Security == null)
			throw new InvalidOperationException("Security must be set to subscribe for candles.");

		_smaFast = new SimpleMovingAverage { Length = 10 };
		_smaSlow = new SimpleMovingAverage { Length = 30 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_smaFast, _smaSlow, ProcessCandleWithIndicators)
			.Start();
	}

	private void ProcessCandleWithIndicators(ICandleMessage candle, decimal fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (fast > slow && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		else if (fast < slow && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
	}

	private void RequestCloseAll(string reason)
	{
		if (_closeRequested)
			return;

		_closeRequested = true;

		LogInfo(reason);

		// Cancel any pending orders to avoid unexpected executions during liquidation.
		CancelActiveOrders();

		foreach (var position in Positions.ToArray())
		{
			var value = GetPositionValue(position.Security, Portfolio) ?? 0m;

			if (value == 0m)
				continue;

			// Submit a market order opposite to the current exposure.
			ClosePosition(position.Security);
		}

		// Stop the strategy after sending exit orders, mirroring ExpertRemove behavior.
		Stop();
	}
}