在 GitHub 上查看

Ang Zad C 时间管理回撤策略

概览

Ang Zad C 时间管理回撤策略是 MetaTrader 5 智能交易系统 Exp_Ang_Zad_C_Tm_MMRec 的 C# 移植版。策略将自定义的 Ang_Zad_C 通道指标、可配置的交易时段过滤以及在连续亏损后降低仓位的资金管理规则结合在一起。

指标逻辑

Ang_Zad_C 指标围绕价格构建两个自适应包络线。对于每根收盘 K 线,指标比较所选价格类型(由 Applied Price 决定)与前一根 K 线的价格,如果出现突破则按 Ki 平滑系数向新价格移动。为了避免使用未完成的柱线,策略按照 Signal Bar 指定的历史偏移位置读取上、下包络线的数值。

交易规则

  • 做多开仓:在参考的上一根柱线中上轨高于下轨,而在最新参考柱线中上轨向下穿越或接触下轨(若允许做多)。触发信号时会先平掉已有空头仓位,然后再开新多单。
  • 做空开仓:在参考的上一根柱线中上轨低于下轨,而在最新参考柱线中上轨向上穿越或接触下轨(若允许做空)。触发信号时会先平掉已有多头仓位,再开空单。
  • 做多平仓:当上一根参考柱线中上轨位于下轨之下时触发(可通过 Enable Long Exit 开关)。
  • 做空平仓:当上一根参考柱线中上轨位于下轨之上时触发(可通过 Enable Short Exit 开关)。

资金管理与风控

  • Use Time Filter 为真时,仅在设定的交易时段内建仓;若到了时段外仍持仓,则立即平仓退出。
  • 策略分别统计多头与空头的亏损次数。多头亏损达到 Buy Loss Trigger 次(或空头亏损达到 Sell Loss Trigger 次)后,改用 Small Volume 指定的较小仓位,直到出现盈利交易重新归零。
  • 可选的止损与止盈根据 Stop Loss StepsTake Profit Steps(以价格步长为单位)在建仓后自动挂单。

参数说明

参数 描述
Candle Type 指标与信号所使用的 K 线周期。
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 代码,并按照参考柱线判断交叉。
  • 资金管理通过实时跟踪每个方向的已实现盈亏来实现,达到设置的亏损次数后自动切换到小仓位。
  • 指标计算使用 StockSharp 的高级订阅接口,在每根完成的 K 线上更新,而不需要直接访问指标缓冲区。
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);
	}
}