在 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.CurrentValue、Portfolio.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();
}
}
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.Indicators import CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class layered_risk_protector_strategy(Strategy):
def __init__(self):
super(layered_risk_protector_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._cci_length = self.Param("CciLength", 100)
self._cci_level = self.Param("CciLevel", 150.0)
self._prev_cci = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def CciLength(self):
return self._cci_length.Value
@CciLength.setter
def CciLength(self, value):
self._cci_length.Value = value
@property
def CciLevel(self):
return self._cci_level.Value
@CciLevel.setter
def CciLevel(self, value):
self._cci_level.Value = value
def OnReseted(self):
super(layered_risk_protector_strategy, self).OnReseted()
self._prev_cci = None
def OnStarted2(self, time):
super(layered_risk_protector_strategy, self).OnStarted2(time)
self._prev_cci = None
cci = CommodityChannelIndex()
cci.Length = self.CciLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(cci, self._process_candle).Start()
def _process_candle(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
cci_val = float(cci_value)
if self._prev_cci is None:
self._prev_cci = cci_val
return
cci_level = float(self.CciLevel)
# CCI crosses below -level -> buy
buy_cross = self._prev_cci >= -cci_level and cci_val < -cci_level
# CCI crosses above +level -> sell
sell_cross = self._prev_cci <= cci_level and cci_val > cci_level
if buy_cross:
if self.Position <= 0:
self.BuyMarket()
elif sell_cross:
if self.Position >= 0:
self.SellMarket()
self._prev_cci = cci_val
def CreateClone(self):
return layered_risk_protector_strategy()