在 GitHub 上查看

权益倍数锁定策略

概述

  • 类别:风险控制 / 账户级自动化。
  • 原始来源:MQL5 专家顾问 “Close by Equity Percent” (#20880)。
  • 目标:比较当前账户权益与上一次无持仓时的余额,当权益达到指定倍数时立即平掉全部持仓以锁定利润。
  • 适用标的:同一投资组合下由其他策略或人工建立的任意合约。

思路解析

原版 MQL 程序实时比较 AccountEquityAccountBalance。余额仅在所有仓位关闭后才会更新,因此一旦满足 Balance * EquityPercentFromBalance,脚本会关闭所有仓位并实现收益。本移植版使用 StockSharp 的高层策略 API,完整保留这一收益保护逻辑。

运行流程

  1. 启动策略时,先记录当前组合的价值,作为“无仓余额”的快照。
  2. 通过参数 CandleType 订阅所选品种的 1 分钟 K 线。该行情流仅用于定期触发权益检查。
  3. 每当一根 K 线收盘:
    • 若当前没有持仓,则用最新的组合价值刷新余额快照。
    • 读取 Portfolio.CurrentValue 并与 balanceSnapshot * EquityPercentFromBalance 比较。
    • 一旦权益达到或超过阈值,对组合内每个持仓调用 ClosePosition(position.Security) 进行平仓。
  4. 所有仓位平掉后,余额快照更新,进入下一轮监控。

参数说明

名称 类型 默认值 说明
EquityPercentFromBalance decimal 1.20 触发平仓所需的权益倍数。例如 1.20 表示当权益达到最后一次无仓余额的 120% 时全部平仓。
CandleType DataType 1 分钟时间框 K 线 仅用于触发检查的行情数据流,可调整为更合适的频率。

实现细节

  • 逐个持仓调用 Strategy.ClosePosition(Security),对应 MQL 中遍历 PositionClose 的逻辑。
  • 仅在完全空仓时刷新余额快照,以模拟 MetaTrader 中 AccountBalance 的行为。
  • 策略本身不建仓,仅负责监控并在达标时清空投资组合中的所有仓位。
  • 启动前必须设置 PortfolioSecuritySecurity 只是用于订阅 K 线作为时间驱动。

使用建议

  1. 将策略附加到需要保护的投资组合上,并选择一个流动性高的品种作为定时器数据源。
  2. 根据风险/收益计划设置 EquityPercentFromBalance
  3. 启动策略后,当权益达到设定倍数,系统会自动平掉组合中所有持仓。
  4. 平仓完成后余额快照更新,下一轮将重新等待权益增长到新的阈值。

示例

  • 初始余额快照:10,000 美元。
  • EquityPercentFromBalance = 1.2 → 目标权益 = 12,000 美元。
  • 持仓盈利,权益涨至 12,050 美元。
  • 策略平掉所有仓位,新的余额为 12,000 美元。
  • 下一轮需权益达到 14,400 美元 (12,000 * 1.2) 才会再次触发平仓。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Equity percent lock strategy (simplified).
/// Trades using momentum and closes when profit target hit.
/// </summary>
public class EquityPercentLockStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _momentumLength;

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

	public int MomentumLength
	{
		get => _momentumLength.Value;
		set => _momentumLength.Value = value;
	}

	public EquityPercentLockStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candles", "General");

		_momentumLength = Param(nameof(MomentumLength), 10)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Length", "Momentum period", "Indicators");
	}

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

		var momentum = new Momentum { Length = MomentumLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(momentum, (ICandleMessage candle, decimal momValue) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				if (momValue > 0 && Position <= 0)
				{
					BuyMarket();
				}
				else if (momValue < 0 && Position >= 0)
				{
					SellMarket();
				}
			})
			.Start();

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