GitHub で見る

Zakryvator戦略

Zakryvator戦略は、現在のオープンポジションを監視し、未実現損失が事前定義された閾値を超えたときにポジションを閉じるリスク管理モジュールです。許容損失はポジションのボリュームに依存し、異なるロットサイズが異なる最大ドローダウンに対応するオリジナルMQLスクリプトのロジックを再現しています。

この戦略自体はエントリーを生成しません。ポジションは手動または別の戦略によって開かれることが想定されています。Zakryvatorは損失のある取引を自動的に終了させることで、口座を保護するだけです。

詳細

  • エントリー条件: なし。この戦略は既存のポジションのみを管理します。
  • エグジット条件: 損失がボリュームに対して設定された閾値に達したとき、現在のポジションを閉じます。
  • ロング/ショート: 両方向がサポートされています。
  • ストップ: ポジションサイズによって変動する固定の金銭的損失限度を使用します。
  • フィルター: 追加フィルターなし。

パラメーター

パラメーター 説明
Min001002 ボリューム ≤ 0.02 ロットのポジションの最大損失。
Min002005 ボリューム 0.02〜0.05 ロットのポジションの最大損失。
Min00501 ボリューム 0.05〜0.10 ロットのポジションの最大損失。
Min0103 ボリューム 0.10〜0.30 ロットのポジションの最大損失。
Min0305 ボリューム 0.30〜0.50 ロットのポジションの最大損失。
Min051 ボリューム 0.50〜1 ロットのポジションの最大損失。
MinFrom1 ボリューム 1 ロット超のポジションの最大損失。

動作

  1. 戦略はリアルタイム価格を追跡するためにトレードティックをサブスクライブします。
  2. 各ティックで現在価格と平均エントリー価格を使用して未実現PnLを計算します。
  3. 損失が現在のポジションボリュームに対応する閾値を超えると、ポジションは成行で閉じられます。

これにより、Zakryvatorは取引サイズに基づいてドローダウンを制限するためのシンプルながら効果的なツールとなっています。

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>
/// Strategy that opens positions using SMA crossover and closes them
/// when unrealized loss exceeds a volume-based threshold ("Zakryvator" = position closer on loss).
/// </summary>
public class ZakryvatorStrategy : Strategy
{
	private decimal _entryPrice;
	private decimal _lastPrice;
	private bool _prevShortAboveLong;

	private readonly SimpleMovingAverage _smaShort = new() { Length = 50 };
	private readonly SimpleMovingAverage _smaLong = new() { Length = 150 };

	private readonly StrategyParam<int> _shortPeriod;
	private readonly StrategyParam<int> _longPeriod;
	private readonly StrategyParam<decimal> _lossThreshold;

	/// <summary>Short SMA period.</summary>
	public int ShortPeriod { get => _shortPeriod.Value; set => _shortPeriod.Value = value; }

	/// <summary>Long SMA period.</summary>
	public int LongPeriod { get => _longPeriod.Value; set => _longPeriod.Value = value; }

	/// <summary>Maximum unrealized loss before closing position.</summary>
	public decimal LossThreshold { get => _lossThreshold.Value; set => _lossThreshold.Value = value; }

	/// <summary>Constructor.</summary>
	public ZakryvatorStrategy()
	{
		_shortPeriod = Param(nameof(ShortPeriod), 50)
			.SetDisplay("Short SMA", "Short SMA period for entry signal", "Entry");
		_longPeriod = Param(nameof(LongPeriod), 150)
			.SetDisplay("Long SMA", "Long SMA period for entry signal", "Entry");
		_lossThreshold = Param(nameof(LossThreshold), 500m)
			.SetDisplay("Loss Threshold", "Max unrealized loss before closing position", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, TimeSpan.FromMinutes(5).TimeFrame())];

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

		_smaShort.Length = ShortPeriod;
		_smaLong.Length = LongPeriod;

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());

		subscription
			.Bind(_smaShort, _smaLong, ProcessCandle)
			.Start();
	}

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

		if (!_smaShort.IsFormed || !_smaLong.IsFormed)
			return;

		_lastPrice = candle.ClosePrice;

		var shortAboveLong = shortSma > longSma;

		// Check loss threshold for open position
		if (Position != 0 && _entryPrice != 0m)
		{
			var openPnL = Position * (_lastPrice - _entryPrice);

			if (openPnL <= -LossThreshold)
			{
				// Close on loss
				if (Position > 0)
					SellMarket();
				else
					BuyMarket();

				_entryPrice = 0m;
				_prevShortAboveLong = shortAboveLong;
				return;
			}
		}

		// SMA crossover entry/exit logic
		var crossUp = shortAboveLong && !_prevShortAboveLong;
		var crossDown = !shortAboveLong && _prevShortAboveLong;

		if (crossUp)
		{
			if (Position < 0)
			{
				BuyMarket();
				_entryPrice = 0m;
			}

			if (Position == 0)
			{
				BuyMarket();
				_entryPrice = _lastPrice;
			}
		}
		else if (crossDown)
		{
			if (Position > 0)
			{
				SellMarket();
				_entryPrice = 0m;
			}

			if (Position == 0)
			{
				SellMarket();
				_entryPrice = _lastPrice;
			}
		}

		_prevShortAboveLong = shortAboveLong;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_entryPrice = 0m;
		_lastPrice = 0m;
		_prevShortAboveLong = false;

		_smaShort.Reset();
		_smaLong.Reset();
	}
}