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);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class ang_zad_c_time_mm_recovery_strategy(Strategy):
def __init__(self):
super(ang_zad_c_time_mm_recovery_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._ki = self.Param("Ki", 4.000001) \
.SetDisplay("Ki", "Smoothing coefficient", "Indicator")
self._has_state = False
self._upper_line = 0.0
self._lower_line = 0.0
self._previous_price = 0.0
self._prev_up = None
self._prev_dn = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def Ki(self):
return self._ki.Value
def OnReseted(self):
super(ang_zad_c_time_mm_recovery_strategy, self).OnReseted()
self._has_state = False
self._upper_line = 0.0
self._lower_line = 0.0
self._previous_price = 0.0
self._prev_up = None
self._prev_dn = None
def OnStarted2(self, time):
super(ang_zad_c_time_mm_recovery_strategy, self).OnStarted2(time)
self._has_state = False
self._prev_up = None
self._prev_dn = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _update_indicator(self, price):
if not self._has_state:
self._upper_line = price
self._lower_line = price
self._previous_price = price
self._has_state = True
return (self._upper_line, self._lower_line)
ki = float(self.Ki)
if price > self._upper_line and price > self._previous_price:
self._upper_line += (price - self._upper_line) / ki
if price < self._upper_line and price < self._previous_price:
self._upper_line += (price - self._upper_line) / ki
if price > self._lower_line and price < self._previous_price:
self._lower_line += (price - self._lower_line) / ki
if price < self._lower_line and price > self._previous_price:
self._lower_line += (price - self._lower_line) / ki
self._previous_price = price
return (self._upper_line, self._lower_line)
def _on_process(self, candle):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
upper, lower = self._update_indicator(price)
if self._prev_up is None or self._prev_dn is None:
self._prev_up = upper
self._prev_dn = lower
return
prev_up = self._prev_up
prev_dn = self._prev_dn
# Buy signal: previous upper was below lower, now crossing above
if prev_up <= prev_dn and upper > lower:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
# Sell signal: previous upper was above lower, now crossing below
elif prev_up >= prev_dn and upper < lower:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
self._prev_up = upper
self._prev_dn = lower
def CreateClone(self):
return ang_zad_c_time_mm_recovery_strategy()