在 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);
}
}
}
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, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class global_stop_timer_strategy(Strategy):
def __init__(self):
super(global_stop_timer_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")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "EMA period", "Indicators")
self._prev_momentum = 100.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def MomentumLength(self):
return self._momentum_length.Value
@property
def EmaLength(self):
return self._ema_length.Value
def OnReseted(self):
super(global_stop_timer_strategy, self).OnReseted()
self._prev_momentum = 100.0
self._has_prev = False
def OnStarted2(self, time):
super(global_stop_timer_strategy, self).OnStarted2(time)
self._prev_momentum = 100.0
self._has_prev = False
momentum = Momentum()
momentum.Length = self.MomentumLength
ema = ExponentialMovingAverage()
ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(momentum, ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, mom_value, ema_value):
if candle.State != CandleStates.Finished:
return
mv = float(mom_value)
if not self._has_prev:
self._prev_momentum = mv
self._has_prev = True
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_momentum = mv
return
close = float(candle.ClosePrice)
ev = float(ema_value)
bullish_cross = self._prev_momentum <= 100 and mv > 100
bearish_cross = self._prev_momentum >= 100 and mv < 100
if bullish_cross and close > ev and self.Position <= 0:
self.BuyMarket()
elif bearish_cross and close < ev and self.Position >= 0:
self.SellMarket()
self._prev_momentum = mv
def CreateClone(self):
return global_stop_timer_strategy()