在 GitHub 上查看

全局止损计时策略

全局止损计时策略来源于 MetaTrader 专家顾问 Exp_GStop_Tm,用于账户级风险控制。 策略在每根订阅蜡烛收盘时读取投资组合当前价值,并在达到预设的总盈利目标或总亏损限额后 停止交易。同时可以限定交易时间窗口,只要时间超出窗口就会强制平仓。

工作流程

  • 启动时记录投资组合的初始价值,作为后续盈亏计算的基准。
  • 每当订阅的蜡烛收盘,计算当前投资组合价值与初始价值的差额。
  • 根据 StopCalculationMode 的设置,将差额转换为百分比或保留为账户货币金额。
  • 当亏损超过 StopLoss 或盈利超过 TakeProfit 时,策略进入停止状态,写入日志,并发送市价单 平掉所有剩余仓位。
  • 如果启用时间窗口,当当前时间离开窗口时也会尝试平仓;当仓位归零后,停止标记被清除,下一次 进入有效窗口时可以继续交易。

该策略不会自行开仓,适合与其他交易策略或人工交易配合使用,用于限制账户回撤或锁定整体收益。

时间窗口逻辑

  • 当开始小时小于结束小时时,允许交易的时间为当日开始分钟(含)到结束分钟(不含)。
  • 当开始小时等于结束小时,只有在当前分钟介于 StartMinute(含)与 EndMinute(不含)之间时 才能交易。
  • 当开始小时大于结束小时,窗口跨越午夜:从开始时间到午夜允许交易,午夜后直到结束时间再次 允许交易。

参数说明

  • StopCalculationMode – 选择使用百分比还是货币金额来计算全局止损/止盈。
  • StopLoss – 总体亏损阈值;在百分比模式下视为百分比,否则视为账户货币金额。
  • TakeProfit – 总体盈利目标;与 StopLoss 使用相同的单位。
  • UseTradingWindow – 是否启用交易时间窗口。
  • StartHour / StartMinute – 交易窗口的开始时间。
  • EndHour / EndMinute – 交易窗口的结束时间。
  • CandleType – 用于定期评估账户状态的蜡烛类型。

使用提示

  • 若需要更快反应,可选用一分钟等较短周期的蜡烛以提高检查频率。
  • 策略仅平掉该实例管理的仓位,如需管理多个品种,请分别运行多个实例。
  • 可将该策略作为父策略或在相同标的上并行运行,为其他交易策略提供全局风控保护。
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>
/// Global Stop Timer strategy (simplified). Trades momentum with
/// time-based position management using EMA trend filter.
/// </summary>
public class GlobalStopTimerStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _momentumLength;
	private readonly StrategyParam<int> _emaLength;

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

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

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

	public GlobalStopTimerStrategy()
	{
		_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");

		_emaLength = Param(nameof(EmaLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("EMA Length", "EMA period", "Indicators");
	}

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

		var momentum = new Momentum { Length = MomentumLength };
		var ema = new ExponentialMovingAverage { Length = EmaLength };
		decimal prevMomentum = 100m;
		var hasPrev = false;

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

				if (!hasPrev)
				{
					prevMomentum = momVal;
					hasPrev = true;
					return;
				}

				if (!IsFormedAndOnlineAndAllowTrading())
				{
					prevMomentum = momVal;
					return;
				}

				var close = candle.ClosePrice;
				var bullishCross = prevMomentum <= 100m && momVal > 100m;
				var bearishCross = prevMomentum >= 100m && momVal < 100m;

				if (bullishCross && close > emaVal && Position <= 0)
					BuyMarket();
				else if (bearishCross && close < emaVal && Position >= 0)
					SellMarket();

				prevMomentum = momVal;
			})
			.Start();

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