Ver no GitHub

Estratégia Zakryvator

A estratégia Zakryvator é um módulo de gerenciamento de risco que monitora a posição aberta atual e a fecha quando a perda não realizada excede um limiar predefinido. A perda permitida depende do volume da posição, replicando a lógica do script MQL original onde diferentes tamanhos de lote correspondem a diferentes drawdowns máximos.

Esta estratégia não gera entradas por si mesma. Espera-se que as posições sejam abertas manualmente ou por outra estratégia. Zakryvator simplesmente protege a conta saindo automaticamente de operações com prejuízo.

Detalhes

  • Critérios de entrada: Nenhum. A estratégia apenas gerencia posições existentes.
  • Critérios de saída: Fecha a posição atual quando a perda atinge o limiar configurado para seu volume.
  • Comprado/Vendido: Ambas as direções são suportadas.
  • Stops: Utiliza limites de perda monetária fixos que variam com o tamanho da posição.
  • Filtros: Sem filtros adicionais.

Parâmetros

Parâmetro Descrição
Min001002 Perda máxima para posições com volume ≤ 0.02 lotes.
Min002005 Perda máxima para posições com volume entre 0.02 e 0.05 lotes.
Min00501 Perda máxima para posições com volume entre 0.05 e 0.10 lotes.
Min0103 Perda máxima para posições com volume entre 0.10 e 0.30 lotes.
Min0305 Perda máxima para posições com volume entre 0.30 e 0.50 lotes.
Min051 Perda máxima para posições com volume entre 0.50 e 1 lote.
MinFrom1 Perda máxima para posições com volume maior que 1 lote.

Comportamento

  1. A estratégia subscreve ticks de operações para rastrear preços em tempo real.
  2. Em cada tick calcula o PnL não realizado usando o preço atual e o preço médio de entrada.
  3. Se a perda exceder o limiar correspondente ao volume da posição atual, a posição é fechada a mercado.

Isso torna o Zakryvator uma ferramenta simples, mas eficaz, para limitar drawdowns com base no tamanho da operação.

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