在 GitHub 上查看
CloseProfit V2 策略
概述
CloseProfit V2 复刻了原始 MetaTrader 工具的功能:当未平仓浮动盈亏到达预设的利润或亏损阈值时,强制平掉所有仓位。移植到 StockSharp 后,它扮演账户保护模块的角色——在每根完成的K线上评估浮动盈亏,一旦超出限制,就会撤销所有挂单并平仓。该策略不会自行开仓,可与其他依赖同一资金账户的手动或自动策略并行运行。
与产生交易信号的策略不同,CloseProfit V2 只负责监控实时盈亏,从而自动化 MQL 版本中的“紧急平仓按钮”。监控频率由烛图订阅控制,因此该组件既能用于历史回测,也适用于实盘环境。
工作原理
- 启动时记录当前投资组合的市值作为最近一次“空仓权益”基准,并创建指定的K线订阅。
- 每当一根K线收盘时,策略会保存收盘价并计算浮动盈亏:
AllSymbols = false 时,仅跟踪主交易标的。浮动盈亏计算为 Position * (lastClose - averagePrice),仅包含未平仓利润,与原始 MQL 逻辑一致。
AllSymbols = true 时,将当前投资组合市值与空仓权益基准比较,得到策略所持全部标的的综合浮动盈亏。
- 当浮动盈亏大于等于
ProfitClose 或小于等于 -LossClose 时,策略立即发起清算:先撤销所有挂单,再对每个持仓标的发送市价单以平仓。
- 待所有仓位归零后,刷新空仓权益基准,确保下一轮监控从新的账户余额开始,避免因为已实现盈利而重复触发。
该实现严格遵循原版 EA 的思路:只关注未平仓盈亏,不考虑历史已实现盈亏。同时通过内部的 _closeAllRequested 标志防止在一次信号内重复发送撤单指令。
参数
- ProfitClose(默认 10) – 账户货币计价的浮动盈利阈值。当未平仓收益达到该数值时,策略立即平掉所有监控的仓位。
- LossClose(默认 1000) – 浮动亏损阈值。当未平仓亏损绝对值超过该数值时,触发清仓以阻止进一步亏损。
- AllSymbols(默认 false) –
false 时仅监控主 Security;true 时汇总策略管理的所有标的,一次性平掉全部仓位。
- 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);
}
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class close_profit_v2_strategy(Strategy):
def __init__(self):
super(close_profit_v2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candles", "General")
self._fast_length = self.Param("FastLength", 8) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_length = self.Param("SlowLength", 21) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastLength(self):
return self._fast_length.Value
@property
def SlowLength(self):
return self._slow_length.Value
def OnReseted(self):
super(close_profit_v2_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(close_profit_v2_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastLength
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ema, slow_ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if not self._has_prev:
self._prev_fast = fv
self._prev_slow = sv
self._has_prev = True
return
if self._prev_fast <= self._prev_slow and fv > sv and self.Position <= 0:
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and fv < sv and self.Position >= 0:
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def CreateClone(self):
return close_profit_v2_strategy()