Ver en GitHub

Estrategia Zakryvator

La estrategia Zakryvator es un módulo de gestión de riesgo que monitorea la posición abierta actual y la cierra cuando la pérdida no realizada supera un umbral predefinido. La pérdida permitida depende del volumen de la posición, replicando la lógica del script MQL original donde diferentes tamaños de lote corresponden a diferentes drawdowns máximos.

Esta estrategia no genera entradas por sí misma. Se espera que las posiciones sean abiertas manualmente o por otra estrategia. Zakryvator simplemente protege la cuenta saliendo automáticamente de operaciones con pérdidas.

Detalles

  • Criterios de entrada: Ninguno. La estrategia solo gestiona posiciones existentes.
  • Criterios de salida: Cierra la posición actual una vez que la pérdida alcanza el umbral configurado para su volumen.
  • Largo/Corto: Ambas direcciones son compatibles.
  • Stops: Utiliza límites de pérdida monetaria fijos que varían con el tamaño de la posición.
  • Filtros: Sin filtros adicionales.

Parámetros

Parámetro Descripción
Min001002 Pérdida máxima para posiciones con volumen ≤ 0.02 lotes.
Min002005 Pérdida máxima para posiciones con volumen entre 0.02 y 0.05 lotes.
Min00501 Pérdida máxima para posiciones con volumen entre 0.05 y 0.10 lotes.
Min0103 Pérdida máxima para posiciones con volumen entre 0.10 y 0.30 lotes.
Min0305 Pérdida máxima para posiciones con volumen entre 0.30 y 0.50 lotes.
Min051 Pérdida máxima para posiciones con volumen entre 0.50 y 1 lote.
MinFrom1 Pérdida máxima para posiciones con volumen mayor a 1 lote.

Comportamiento

  1. La estrategia se suscribe a ticks de operaciones para rastrear precios en tiempo real.
  2. En cada tick calcula el PnL no realizado usando el precio actual y el precio de entrada promedio.
  3. Si la pérdida supera el umbral correspondiente al volumen de la posición actual, la posición se cierra a mercado.

Esto convierte a Zakryvator en una herramienta simple pero efectiva para limitar los drawdowns según el tamaño de la operación.

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();
	}
}