Открыть на GitHub

Стратегия ZeeZee Level

Обзор

Стратегия ZeeZee Level переносит логику оригинального эксперта MetaTrader «ZeeZee Level» на высокоуровневый API StockSharp. Алгоритм анализирует экстремумы индикатора ZigZag на выбранном таймфрейме и открывает позицию в сторону последнего подтверждённого экстремума. Уровни стоп-лосса, тейк-профита и трейлинг-стопа задаются в пунктах, а объём сделки увеличивается по принципу мартингейла после убыточных сделок.

Логика работы

  1. Подписка на свечи выполняется согласно параметру CandleType.
  2. Индикатор ZigZagIndicator с настраиваемыми параметрами глубины, отклонения и backstep отслеживает новые максимумы и минимумы.
  3. При отсутствии открытой позиции стратегия сравнивает давность последнего ZigZag-хая и лоу в пределах окна ZigZagIdInterval:
    • если последний максимум оказался свежее последнего минимума — открывается короткая позиция;
    • если последний минимум свежее последнего максимума — открывается длинная позиция.
  4. Одновременно удерживается только одна позиция, причём объём приводится к шагу объёма инструмента.
  5. После открытия сделки выставляются уровни стоп-лосса, тейк-профита и при необходимости трейлинг-стопа. Трейлинг-стоп смещается только по направлению прибыльного движения.
  6. Сделка закрывается при достижении стоп-лосса или тейк-профита. Если в одной свече достигаются оба уровня, выбирается тот, который ближе к цене входа.
  7. После закрытия прибыльной сделки объём возвращается к InitialVolume, а после убыточной умножается на MartingaleMultiplier.

Параметры

Параметр Описание
ZigZagDepth Количество свечей, используемых при поиске новых экстремумов ZigZag.
ZigZagDeviation Минимальное ценовое отклонение (в шагах цены) для подтверждения экстремума.
ZigZagBackstep Минимальное число баров, через которое ZigZag может сменить направление.
ZigZagIdInterval Максимальная глубина поиска последних ZigZag-хая и лоу.
StopLossPips Дистанция стоп-лосса в пунктах. Ноль отключает уровень.
TakeProfitPips Дистанция тейк-профита в пунктах. Ноль отключает уровень.
TrailingStopPips Дистанция трейлинг-стопа в пунктах. Ноль отключает сопровождение.
InitialVolume Базовый объём первой сделки в цикле мартингейла.
MartingaleMultiplier Множитель объёма для следующей сделки после убытка.
CandleType Тип и таймфрейм свечей, используемых в расчётах.

Управление капиталом

  • Объём округляется к шагу объёма и ограничивается биржевыми минимальными и максимальными значениями.
  • Прибыльная сделка сбрасывает объём к InitialVolume, убыточная — умножает объём на MartingaleMultiplier.

Управление рисками

  • На каждой закрытой свече пересчитываются уровни стоп-лосса, тейк-профита и трейлинг-стопа.
  • Трейлинг-стоп перемещается только в сторону прибыльного движения и никогда не отступает назад.
  • Новые сделки не открываются, пока нет подтверждённых ZigZag-экстремумов в пределах окна или уже есть открытая позиция.

Примечания

  • Стратегия работает только с закрытыми свечами, что позволяет повторить поведение исходного эксперта.
  • Конвертация пунктов опирается на параметр PriceStep. Перед запуском убедитесь, что данные об инструменте загружены.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class ZeeZeeLevelStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public ZeeZeeLevelStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}