在 GitHub 上查看
全局止损与交易时段策略
概述
该策略复刻 MetaTrader 专家顾问 Exp_GStopLoss_Tm 的核心风控逻辑,用作其它交易模块之上的风险覆盖层。策略自身不产
生入场信号,而是持续监控当前持仓的盈亏情况,在亏损超过设定阈值或市场时间不在允许区间时强制平仓并暂停交易,直至仓
位再次归零。
交易逻辑
- 启动时记录当前已实现盈亏作为基准值,用于计算后续持仓的浮动盈亏。
- 每当订阅的 K 线完成时触发一次风控检测。默认时间框架为 1 分钟,以便在较高频率下模拟逐笔监控。
- 将当前策略的 PnL 与基准值之差视为当前浮亏。只要策略仍处于允许的交易窗口内且浮盈为正,就不会触发保护措施,与原
有 EA 的行为保持一致。
- 当止损模式为 Percent 时,浮亏百分比会与
Portfolio.CurrentValue 提供的账户权益进行比较;若模式为 Currency,
则直接比较账户货币计价的绝对亏损额。
- 一旦亏损超过阈值,策略就会锁定止损标志,并在下一次迭代中开始平仓。只有当仓位完全清零并刷新基准 PnL 后,止损标志
才会被解除。
- 若启用时间过滤器,策略还会检查 K 线的收盘时间是否处于允许的交易窗口中。时间窗口支持跨越午夜,与原始脚本的判定逻
辑一致。
- 在止损标志被触发或交易时间不满足条件时,策略会发送对向市价单以快速平仓,并在日志中记录触发原因。
参数说明
| 参数 |
说明 |
LossMode |
选择止损阈值的衡量方式:按账户权益百分比或按账户货币绝对值。 |
StopLoss |
止损阈值。百分比模式下表示百分比,货币模式下为账户货币金额。 |
UseTimeFilter |
是否启用日内交易时间过滤。关闭后策略将忽略时间窗口。 |
StartTime |
交易窗口的起始时间(UTC),与 EndTime 配合定义有效区间。 |
EndTime |
交易窗口的结束时间(UTC,开区间)。若结束时间早于起始时间则表示跨夜时段。 |
CandleType |
用于驱动风险检测的 K 线类型,默认为 1 分钟。 |
实现细节
- 当持仓归零时会重新记录基准 PnL,确保每一笔新交易都从零基准开始评估浮盈浮亏。
- 百分比模式依赖实时账户权益,因此能够同时反映已实现与未实现盈亏。
- 代码中的注释全部使用英文,符合仓库的统一规范。
- 在可用情况下,策略会在图表上绘制 K 线与自身成交,方便回测时观察。
使用指南
- 将策略附加到需要监控的品种上。其它策略仍可以发出交易指令,本模块只负责监督并在必要时平仓。
- 根据风险偏好配置止损模式与阈值。例如设置
LossMode = Percent 与 StopLoss = 5 时,一旦出现 5% 的未实现回撤就会
平仓。
- 设置
StartTime 与 EndTime 以限制交易时段。若需要覆盖夜盘,只需让起始时间大于结束时间(如 20:00 至 06:00)。
- 运行回测或实时交易。策略会在仓位清空后自动重置止损标志,并继续监控后续的交易。
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 Loss Time strategy (simplified). Trades EMA crossover with
/// session time filter and global stop loss based on drawdown.
/// </summary>
public class GlobalStopLossTimeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaFastLength;
private readonly StrategyParam<int> _emaSlowLength;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaFastLength
{
get => _emaFastLength.Value;
set => _emaFastLength.Value = value;
}
public int EmaSlowLength
{
get => _emaSlowLength.Value;
set => _emaSlowLength.Value = value;
}
public GlobalStopLossTimeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candles", "General");
_emaFastLength = Param(nameof(EmaFastLength), 8)
.SetGreaterThanZero()
.SetDisplay("EMA Fast", "Fast EMA period", "Indicators");
_emaSlowLength = Param(nameof(EmaSlowLength), 21)
.SetGreaterThanZero()
.SetDisplay("EMA Slow", "Slow EMA period", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var emaFast = new ExponentialMovingAverage { Length = EmaFastLength };
var emaSlow = new ExponentialMovingAverage { Length = EmaSlowLength };
decimal prevFast = 0, prevSlow = 0;
var hasPrev = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(emaFast, emaSlow, (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;
}
// EMA crossover signals
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, emaFast);
DrawIndicator(area, emaSlow);
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 global_stop_loss_time_strategy(Strategy):
def __init__(self):
super(global_stop_loss_time_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candles", "General")
self._ema_fast_length = self.Param("EmaFastLength", 8) \
.SetDisplay("EMA Fast", "Fast EMA period", "Indicators")
self._ema_slow_length = self.Param("EmaSlowLength", 21) \
.SetDisplay("EMA Slow", "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 EmaFastLength(self):
return self._ema_fast_length.Value
@property
def EmaSlowLength(self):
return self._ema_slow_length.Value
def OnReseted(self):
super(global_stop_loss_time_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(global_stop_loss_time_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
ema_fast = ExponentialMovingAverage()
ema_fast.Length = self.EmaFastLength
ema_slow = ExponentialMovingAverage()
ema_slow.Length = self.EmaSlowLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema_fast, ema_slow, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema_fast)
self.DrawIndicator(area, ema_slow)
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 global_stop_loss_time_strategy()