在 GitHub 上查看

CloseProfit V2 策略

概述

CloseProfit V2 复刻了原始 MetaTrader 工具的功能:当未平仓浮动盈亏到达预设的利润或亏损阈值时,强制平掉所有仓位。移植到 StockSharp 后,它扮演账户保护模块的角色——在每根完成的K线上评估浮动盈亏,一旦超出限制,就会撤销所有挂单并平仓。该策略不会自行开仓,可与其他依赖同一资金账户的手动或自动策略并行运行。

与产生交易信号的策略不同,CloseProfit V2 只负责监控实时盈亏,从而自动化 MQL 版本中的“紧急平仓按钮”。监控频率由烛图订阅控制,因此该组件既能用于历史回测,也适用于实盘环境。

工作原理

  1. 启动时记录当前投资组合的市值作为最近一次“空仓权益”基准,并创建指定的K线订阅。
  2. 每当一根K线收盘时,策略会保存收盘价并计算浮动盈亏:
    • AllSymbols = false 时,仅跟踪主交易标的。浮动盈亏计算为 Position * (lastClose - averagePrice),仅包含未平仓利润,与原始 MQL 逻辑一致。
    • AllSymbols = true 时,将当前投资组合市值与空仓权益基准比较,得到策略所持全部标的的综合浮动盈亏。
  3. 当浮动盈亏大于等于 ProfitClose 或小于等于 -LossClose 时,策略立即发起清算:先撤销所有挂单,再对每个持仓标的发送市价单以平仓。
  4. 待所有仓位归零后,刷新空仓权益基准,确保下一轮监控从新的账户余额开始,避免因为已实现盈利而重复触发。

该实现严格遵循原版 EA 的思路:只关注未平仓盈亏,不考虑历史已实现盈亏。同时通过内部的 _closeAllRequested 标志防止在一次信号内重复发送撤单指令。

参数

  • ProfitClose(默认 10) – 账户货币计价的浮动盈利阈值。当未平仓收益达到该数值时,策略立即平掉所有监控的仓位。
  • LossClose(默认 1000) – 浮动亏损阈值。当未平仓亏损绝对值超过该数值时,触发清仓以阻止进一步亏损。
  • AllSymbols(默认 false)false 时仅监控主 Securitytrue 时汇总策略管理的所有标的,一次性平掉全部仓位。
  • CandleType(默认 1 分钟) – 用于评估的K线类型。AllSymbols = false 时,K线收盘价用于计算浮动盈亏。时间框架越短,响应越快;越长则降低回测开销。

使用建议

  • 将该策略与其他交易策略一同启动,只要触发阈值,CloseProfit V2 就会撤销它们的挂单并平仓。
  • 由于 StockSharp 高级接口暂不提供佣金和隔夜利息数据,浮动盈亏只基于价格差。若需要覆盖这些成本,可适当放宽阈值。
  • 平仓采用市价单,请确保目标市场具有足够流动性或预留滑点空间。
  • K线订阅同样适用于回测,可在实盘中选择更短的周期以获得更快的响应。
  • 启动时调用 StartProtection(),保持 StockSharp 内置的保护措施(如重连处理)处于激活状态。

与原始 MQL 实现的差异

  • MetaTrader 的 “magic number” 过滤在 StockSharp 中不再需要:订单按策略隔离,因此 AllSymbols 直接作用于该策略实例管理的全部标的。
  • MQL 版本通过图表标签展示余额、权益和订单统计;C# 版本改用日志输出,便于在自动化环境运行。
  • 移除了 MQL 中用于调试/测试的自动开仓代码,保留纯粹的监控和平仓功能。

适用场景

当需要对浮动盈亏设置硬性止盈/止损规则时即可启用 CloseProfit V2,例如保护资金账户、执行资金管理制度或实现日内盈利目标。可根据自身交易节奏调整 K 线周期以匹配所需的响应速度。

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>
/// CloseProfit v2 strategy (simplified).
/// Uses EMA crossover for entries with profit/loss exit thresholds.
/// </summary>
public class CloseProfitV2Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;

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

	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

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

		_fastLength = Param(nameof(FastLength), 8)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowLength = Param(nameof(SlowLength), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
	}

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

		var fastEma = new ExponentialMovingAverage { Length = FastLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowLength };
		decimal prevFast = 0m;
		decimal prevSlow = 0m;
		var hasPrev = false;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, (ICandleMessage candle, decimal fastVal, decimal slowVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!hasPrev)
				{
					prevFast = fastVal;
					prevSlow = slowVal;
					hasPrev = true;
					return;
				}

				if (!IsFormedAndOnlineAndAllowTrading())
				{
					prevFast = fastVal;
					prevSlow = slowVal;
					return;
				}

				if (prevFast <= prevSlow && fastVal > slowVal && Position <= 0)
					BuyMarket();
				else if (prevFast >= prevSlow && fastVal < slowVal && Position >= 0)
					SellMarket();

				prevFast = fastVal;
				prevSlow = slowVal;
			})
			.Start();

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