在 GitHub 上查看

分层风险防护策略

概述

分层风险防护策略 是对 MetaTrader 专家顾问 “RiskManager” 的直接移植。该算法通过商品通道指数(CCI)、平均真实波幅(ATR)倍数以及分层仓位模型,持续监控投资组合权益并调整市场敞口。当风险指标低于可配置阈值时,策略会自动切换到对冲模式,在达到盈利或回撤条件时平仓,并可选择在盈亏平衡点强制清仓。

交易逻辑

  • 指标条件 – 策略订阅主 K 线序列(时间框架可配置),并计算:
    • 使用用户设定周期的 CCI。做多时要求 CCI 低于负阈值,做空时要求 CCI 高于正阈值。
    • 固定周期为 14 的 ATR,用于计算每一层仓位的波动率自适应止盈与止损距离。
    • 基于最近 50 根已完成 K 线的成交量移动平均。只有当滚动平均值高于上一根 K 线的成交量时,交易才会被启用,从而复制原策略中的 “Active” 过滤器。
  • 分层建仓 – 最大总仓位被分配到可配置数量的层。每次下单都会使用 MaxVolume / Layers 计算出的单层手数;当相对层使用率(订单数 / Layers * 100)高于当前系统健康度时,将禁止继续加仓。
  • 订单管理 – 每一层仓位都会记录开仓价及其 ATR 计算得出的止盈、止损水平。每根已完成的 K 线都会检查其最高价/最低价,以确定是否触发任意层的保护水平并执行平仓。
  • 对冲模式 – 当 MultiPairTrading 设为 false 且健康度低于 HedgeLevel 时,策略会记录当前订单数量,并按 HedgeRatio 的要求开立反向仓位,直至对冲目标满足。一旦健康度恢复到阈值以上,对冲模式自动关闭。
  • 权益控制 – 以下保护机制与原始 EA 保持一致:
    • RiskLimit 定义的硬性权益止损(初始资金减去风险限制)。
    • 以初始资金为基准的附加型盈利目标。
    • 动态 “Close Equity” 目标:每次成功清仓后,将 CloseProfitBuffer 加到当前余额上。
    • 可选的盈亏平衡退出:当权益达到记录的盈亏平衡资本时立即平仓。
    • 手动 “Hard Close” 开关,可立即强制平仓并暂停交易。

参数

  • AllowLong / AllowShort – 分别允许做多或做空。
  • MaxVolume – 在所有层之间分配的总仓位手数。
  • Layers – 可同时打开的最大层数。
  • CciLength / CciLevel – CCI 的周期和阈值。
  • StopLossMultiple / TakeProfitMultiple – 用于确定每层止损、止盈的 ATR 倍数。
  • CloseProfitBuffer – 在重置 Close Equity 目标时添加到余额的盈利缓冲,同时用于计算盈亏平衡资本。
  • ManualCapital – 重写用于风险计算的初始资金(设为 0 表示使用启动时投资组合的实时余额)。
  • RiskLimit – 允许的最大权益回撤。
  • ProfitTarget – 达到后暂停交易的附加盈利目标。
  • MultiPairTrading – 若为 true,即使健康度跌破阈值也不会启动内部对冲。
  • HedgeLevel / HedgeRatio – 启动对冲的健康度百分比以及对冲模式下需要增加的订单比例。
  • CloseAtBreakEven – 启用盈亏平衡退出逻辑。
  • HardClose – 强制立即平仓,并在该值为 true 时暂停交易。
  • CandleType – 用于指标计算和交易决策的 K 线类型。

说明

  • 策略假设市价单可立即成交。在历史回测中,实际执行模型依赖于 StockSharp 的回测配置。
  • 权益与余额数据来自所连接的投资组合(Portfolio.CurrentValuePortfolio.CurrentBalance)。请确保投资组合与交易标的同步。
  • 启动对冲时会在同一标的上开立额外的市场仓位,请确认经纪商或模拟环境允许持有反向仓位。
  • 盈亏平衡跟踪复用了 CloseProfitBuffer 数值,保持与原始 MetaTrader 参数 ClosePL 的行为一致。
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "Risk Manager Layered" MetaTrader expert.
/// Uses CCI crossover for entry with position management. CCI above level -> sell,
/// CCI below negative level -> buy, with volume-based confirmation.
/// </summary>
public class LayeredRiskProtectorStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cciLength;
	private readonly StrategyParam<decimal> _cciLevel;

	private CommodityChannelIndex _cci;
	private decimal? _prevCci;

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

	public int CciLength
	{
		get => _cciLength.Value;
		set => _cciLength.Value = value;
	}

	public decimal CciLevel
	{
		get => _cciLevel.Value;
		set => _cciLevel.Value = value;
	}

	public LayeredRiskProtectorStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle series", "General");

		_cciLength = Param(nameof(CciLength), 100)
			.SetGreaterThanZero()
			.SetDisplay("CCI Length", "CCI indicator period", "Indicators");

		_cciLevel = Param(nameof(CciLevel), 150m)
			.SetGreaterThanZero()
			.SetDisplay("CCI Level", "CCI threshold for entries", "Indicators");
	}

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

		_cci = new CommodityChannelIndex { Length = CciLength };
		_prevCci = null;

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

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

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

		if (!_cci.IsFormed)
		{
			_prevCci = cciValue;
			return;
		}

		if (_prevCci is null)
		{
			_prevCci = cciValue;
			return;
		}

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// CCI crosses below -level -> buy
		var buyCross = _prevCci.Value >= -CciLevel && cciValue < -CciLevel;
		// CCI crosses above +level -> sell
		var sellCross = _prevCci.Value <= CciLevel && cciValue > CciLevel;

		if (buyCross)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (sellCross)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevCci = cciValue;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_cci = null;
		_prevCci = null;

		base.OnReseted();
	}
}