Открыть на GitHub

Стратегия Ang Zad C Time MM Recovery

Обзор

Ang Zad C Time MM Recovery — это перенос на C# советника MetaTrader 5 Exp_Ang_Zad_C_Tm_MMRec. Стратегия объединяет пользовательский индикатор Ang_Zad_C, фильтр торговых сессий и адаптивный манименеджмент, который снижает объём после заданного количества убыточных сделок.

Логика индикатора

Индикатор Ang_Zad_C строит вокруг цены две адаптивные линии. Для каждой завершённой свечи выбранный тип цены (параметр Applied Price) сравнивается с предыдущей свечой и, если цена ушла дальше, линия смещается к новому значению с коэффициентом сглаживания Ki. Для исключения незакрытых свечей линии анализируются на смещении, заданном параметром Signal Bar.

Торговые правила

  • Открытие длинной позиции — если на предыдущей опорной свече верхняя линия была выше нижней, а на текущей опорной свече пересекла её сверху вниз или коснулась, при разрешённых лонгах закрывается шорт и открывается новый лонг.
  • Открытие короткой позиции — если на предыдущей опорной свече верхняя линия была ниже нижней, а на текущей опорной свече пересекла её снизу вверх или коснулась, при разрешённых шортах закрывается лонг и открывается новый шорт.
  • Закрытие длинной позиции — когда верхняя линия расположена ниже нижней на предыдущей опорной свече (параметр Enable Long Exit).
  • Закрытие короткой позиции — когда верхняя линия расположена выше нижней на предыдущей опорной свече (параметр Enable Short Exit).

Манименеджмент и защита

  • Если включён параметр Use Time Filter, сделки совершаются только внутри заданного торгового окна (Trade Start и Trade End). При выходе за пределы окна открытая позиция закрывается.
  • Для каждой стороны ведётся счётчик убыточных сделок. После Buy Loss Trigger убыточных лонгов (или Sell Loss Trigger убыточных шортов) стратегия переходит на объём Small Volume до появления прибыльной сделки.
  • Дополнительно можно зарегистрировать стоп-лосс и тейк-профит с помощью параметров Stop Loss Steps и Take Profit Steps, выраженных в шагах цены инструмента.

Параметры

Параметр Описание
Candle Type Таймфрейм свечей, по которым работают индикатор и сигналы.
Ki Коэффициент сглаживания линий Ang_Zad_C.
Applied Price Тип цены, подаваемой на вход индикатора.
Signal Bar Смещение по истории для оценки сигналов (1 = предыдущая завершённая свеча).
Use Time Filter / Trade Start / Trade End Включение торгового окна и его границы.
Enable Long/Short Entry Разрешение на открытие длинных / коротких позиций.
Enable Long/Short Exit Разрешение на закрытие позиций по сигналу.
Buy/Sell Loss Trigger Количество убыточных сделок до перехода на меньший объём.
Small Volume / Normal Volume Объёмы сделки после серии убытков и в нормальном режиме.
Stop Loss Steps / Take Profit Steps Расстояние стоп-лосса и тейк-профита в шагах цены.

Особенности портирования

  • Логика пересечений и проверка торгового окна повторяют оригинальный код MQL5.
  • Учёт серий убытков реализован через отслеживание реализованного PnL по каждому направлению и переключение объёма после достижении порога.
  • Индикатор рассчитывается на закрытых свечах через высокоуровневый API StockSharp без прямого доступа к буферам индикатора.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Ang_Zad_C adaptive channel strategy.
/// Trades when the upper/lower lines cross, indicating trend reversal.
/// </summary>
public class AngZadCTimeMMRecoveryStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _ki;

	private bool _hasState;
	private decimal _upperLine;
	private decimal _lowerLine;
	private decimal _previousPrice;
	private decimal? _prevUp;
	private decimal? _prevDn;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public decimal Ki
	{
		get => _ki.Value;
		set => _ki.Value = value;
	}

	public AngZadCTimeMMRecoveryStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_ki = Param(nameof(Ki), 4.000001m)
			.SetDisplay("Ki", "Smoothing coefficient", "Indicator")
			.SetGreaterThanZero();
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

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

		_hasState = false;
		_upperLine = 0m;
		_lowerLine = 0m;
		_previousPrice = 0m;
		_prevUp = null;
		_prevDn = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_hasState = false;
		_prevUp = null;
		_prevDn = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var price = candle.ClosePrice;
		var (upper, lower) = UpdateIndicator(price);

		if (_prevUp == null || _prevDn == null)
		{
			_prevUp = upper;
			_prevDn = lower;
			return;
		}

		var prevUp = _prevUp.Value;
		var prevDn = _prevDn.Value;

		// Buy signal: previous upper was below lower, now crossing above
		if (prevUp <= prevDn && upper > lower)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		// Sell signal: previous upper was above lower, now crossing below
		else if (prevUp >= prevDn && upper < lower)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}

		_prevUp = upper;
		_prevDn = lower;
	}

	private (decimal Up, decimal Down) UpdateIndicator(decimal price)
	{
		if (!_hasState)
		{
			_upperLine = price;
			_lowerLine = price;
			_previousPrice = price;
			_hasState = true;
			return (_upperLine, _lowerLine);
		}

		var ki = Ki;

		if (price > _upperLine && price > _previousPrice)
			_upperLine += (price - _upperLine) / ki;

		if (price < _upperLine && price < _previousPrice)
			_upperLine += (price - _upperLine) / ki;

		if (price > _lowerLine && price < _previousPrice)
			_lowerLine += (price - _lowerLine) / ki;

		if (price < _lowerLine && price > _previousPrice)
			_lowerLine += (price - _lowerLine) / ki;

		_previousPrice = price;

		return (_upperLine, _lowerLine);
	}
}