在 GitHub 上查看
权益倍数锁定策略
概述
- 类别:风险控制 / 账户级自动化。
- 原始来源:MQL5 专家顾问 “Close by Equity Percent” (#20880)。
- 目标:比较当前账户权益与上一次无持仓时的余额,当权益达到指定倍数时立即平掉全部持仓以锁定利润。
- 适用标的:同一投资组合下由其他策略或人工建立的任意合约。
思路解析
原版 MQL 程序实时比较 AccountEquity 与 AccountBalance。余额仅在所有仓位关闭后才会更新,因此一旦满足 Balance * EquityPercentFromBalance,脚本会关闭所有仓位并实现收益。本移植版使用 StockSharp 的高层策略 API,完整保留这一收益保护逻辑。
运行流程
- 启动策略时,先记录当前组合的价值,作为“无仓余额”的快照。
- 通过参数
CandleType 订阅所选品种的 1 分钟 K 线。该行情流仅用于定期触发权益检查。
- 每当一根 K 线收盘:
- 若当前没有持仓,则用最新的组合价值刷新余额快照。
- 读取
Portfolio.CurrentValue 并与 balanceSnapshot * EquityPercentFromBalance 比较。
- 一旦权益达到或超过阈值,对组合内每个持仓调用
ClosePosition(position.Security) 进行平仓。
- 所有仓位平掉后,余额快照更新,进入下一轮监控。
参数说明
| 名称 |
类型 |
默认值 |
说明 |
EquityPercentFromBalance |
decimal |
1.20 |
触发平仓所需的权益倍数。例如 1.20 表示当权益达到最后一次无仓余额的 120% 时全部平仓。 |
CandleType |
DataType |
1 分钟时间框 K 线 |
仅用于触发检查的行情数据流,可调整为更合适的频率。 |
实现细节
- 逐个持仓调用
Strategy.ClosePosition(Security),对应 MQL 中遍历 PositionClose 的逻辑。
- 仅在完全空仓时刷新余额快照,以模拟 MetaTrader 中
AccountBalance 的行为。
- 策略本身不建仓,仅负责监控并在达标时清空投资组合中的所有仓位。
- 启动前必须设置
Portfolio 与 Security。Security 只是用于订阅 K 线作为时间驱动。
使用建议
- 将策略附加到需要保护的投资组合上,并选择一个流动性高的品种作为定时器数据源。
- 根据风险/收益计划设置
EquityPercentFromBalance。
- 启动策略后,当权益达到设定倍数,系统会自动平掉组合中所有持仓。
- 平仓完成后余额快照更新,下一轮将重新等待权益增长到新的阈值。
示例
- 初始余额快照: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);
}
}
}
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 Momentum
from StockSharp.Algo.Strategies import Strategy
class equity_percent_lock_strategy(Strategy):
def __init__(self):
super(equity_percent_lock_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candles", "General")
self._momentum_length = self.Param("MomentumLength", 10) \
.SetDisplay("Momentum Length", "Momentum period", "Indicators")
@property
def CandleType(self):
return self._candle_type.Value
@property
def MomentumLength(self):
return self._momentum_length.Value
def OnStarted2(self, time):
super(equity_percent_lock_strategy, self).OnStarted2(time)
momentum = Momentum()
momentum.Length = self.MomentumLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(momentum, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle, mom_value):
if candle.State != CandleStates.Finished:
return
mv = float(mom_value)
if mv > 0 and self.Position <= 0:
self.BuyMarket()
elif mv < 0 and self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return equity_percent_lock_strategy()