在 GitHub 上查看
经济日历侦测策略
概述
经济日历侦测策略 来源于 MetaTrader 专家顾问 SampleDetectEconomicCalendar.mq5。该策略监控手动提供的经济事件列表,当指定货币即将公布高重要度数据时,会在当前买卖价附近同时布置买入/卖出止损单。止损、止盈与移动止损逻辑全部继承自原始 EA。
由于 StockSharp 无法直接访问 MetaTrader 的日历服务,所有事件必须通过 CalendarDefinition 参数手动配置。
工作流程
- 策略订阅 Level1 行情以跟踪买价与卖价。
- 启动时解析
CalendarDefinition 中的每一行事件。
- 对于符合
BaseCurrency 且重要度为 High 的事件:
- 在距公布时间
LeadMinutes 分钟时开始准备。
- 根据固定手数或风险控制计算下单量。
- 分别在
BuyDistancePoints 与 SellDistancePoints 点距位置挂出买入/卖出止损单。
- 数据公布后,若订单未触发,策略会在
PostMinutes 分钟后或 ExpiryMinutes 超时时间到达时撤单。
- 其中一侧成交后,另一侧订单会立即取消,并按设定的止损、止盈及移动止损管理持仓。
参数说明
| 参数 |
说明 |
TradeNews |
是否在新闻公布前启用挂单逻辑。 |
OrderVolume |
当不启用资金管理时使用的固定手数。 |
StopLossPoints |
止损距离(点)。为 0 表示关闭。 |
TakeProfitPoints |
止盈距离(点)。为 0 表示关闭。 |
TrailingStopPoints |
移动止损距离(点)。为 0 表示关闭移动止损。 |
ExpiryMinutes |
公布后保留挂单的最长时间(分钟)。 |
UseMoneyManagement |
是否启用按账户风险计算手数。 |
RiskPercent |
触发资金管理时单笔风险占账户净值的百分比。 |
BuyDistancePoints |
买入止损相对于卖价的距离(点)。 |
SellDistancePoints |
卖出止损相对于买价的距离(点)。 |
LeadMinutes |
公布前提前挂单的分钟数。 |
PostMinutes |
公布后等待撤单的分钟数。 |
BaseCurrency |
需要匹配的货币代码,默认 USD。 |
CalendarDefinition |
多行文本,每行描述一个经济事件。 |
事件格式
每行事件使用以下格式:
yyyy-MM-dd HH:mm;CUR;High;事件名称
yyyy-MM-dd HH:mm —— UTC 时间戳,允许带秒,亦支持 yyyy/MM/dd、dd.MM.yyyy 等格式。
CUR —— 货币代码(如 USD)。只有匹配 BaseCurrency 的事件会触发挂单。
High —— 重要度关键字,可选 High、Medium、Low、Nfp,其中仅 High 会实际下单。
事件名称 —— 用于日志记录的说明文字。
示例:
2024-06-12 18:00;USD;High;FOMC Statement
2024-07-05 12:30;USD;Nfp;Non-Farm Payrolls
风险管理
- 当
UseMoneyManagement 关闭时,策略始终使用 OrderVolume 指定的固定手数。
- 当
UseMoneyManagement 打开时,策略以账户净值的 RiskPercent% 作为最大亏损,结合 StopLossPoints 计算下单量,并自动遵守交易品种的最小/最大手数及步长限制。
- 当价格向有利方向移动
TrailingStopPoints 点后,移动止损开始保护盈利,止损/止盈条件任意满足都会立即平仓。
与原始 EA 的差异
- 必须手动在
CalendarDefinition 中输入经济事件。
- 每个策略实例仅处理一个货币对。
- 因 StockSharp 不支持 MetaTrader 的
ORDER_TIME_SPECIFIED 到期设置,挂单失效由 PostMinutes 与 ExpiryMinutes 定时器控制。
使用建议
- 在启动前填写
CalendarDefinition,并确认事件时间为 UTC。
- 打开
TradeNews,根据需要调整距离及风险参数。
- 确保连接提供 Level1 买卖价数据,以便在窗口开始时能够即时挂单。
- 通过日志核对每次事件的下单与撤单是否符合预期。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Sample Detect Economic Calendar strategy: Parabolic SAR trend with EMA filter.
/// Buys when close above both SAR and EMA, sells when close below both.
/// </summary>
public class SampleDetectEconomicCalendarStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaPeriod;
private decimal _prevClose;
private decimal _prevSar;
private decimal _prevEma;
private bool _hasPrev;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public SampleDetectEconomicCalendarStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_emaPeriod = Param(nameof(EmaPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA filter period", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = 0;
_prevSar = 0;
_prevEma = 0;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = 0;
_prevSar = 0;
_prevEma = 0;
_hasPrev = false;
var sar = new ParabolicSar { Acceleration = 0.01m, AccelerationMax = 0.1m };
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(sar, ema, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal sarValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished) return;
if (_hasPrev)
{
var aboveSar = candle.ClosePrice > sarValue;
var belowSar = candle.ClosePrice < sarValue;
var aboveEma = candle.ClosePrice > emaValue;
var belowEma = candle.ClosePrice < emaValue;
if (aboveSar && aboveEma && !(_prevClose > _prevSar && _prevClose > _prevEma) && Position <= 0)
BuyMarket();
else if (belowSar && belowEma && !(_prevClose < _prevSar && _prevClose < _prevEma) && Position >= 0)
SellMarket();
}
_prevClose = candle.ClosePrice;
_prevSar = sarValue;
_prevEma = emaValue;
_hasPrev = true;
}
}
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 ParabolicSar, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class sample_detect_economic_calendar_strategy(Strategy):
def __init__(self):
super(sample_detect_economic_calendar_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._ema_period = self.Param("EmaPeriod", 50)
self._prev_close = 0.0
self._prev_sar = 0.0
self._prev_ema = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
def OnReseted(self):
super(sample_detect_economic_calendar_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_sar = 0.0
self._prev_ema = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(sample_detect_economic_calendar_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_sar = 0.0
self._prev_ema = 0.0
self._has_prev = False
sar = ParabolicSar()
sar.Acceleration = 0.01
sar.AccelerationMax = 0.1
ema = ExponentialMovingAverage()
ema.Length = self.EmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(sar, ema, self._process_candle).Start()
def _process_candle(self, candle, sar_value, ema_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
sar_val = float(sar_value)
ema_val = float(ema_value)
if self._has_prev:
above_sar = close > sar_val
below_sar = close < sar_val
above_ema = close > ema_val
below_ema = close < ema_val
was_above_both = self._prev_close > self._prev_sar and self._prev_close > self._prev_ema
was_below_both = self._prev_close < self._prev_sar and self._prev_close < self._prev_ema
if above_sar and above_ema and not was_above_both and self.Position <= 0:
self.BuyMarket()
elif below_sar and below_ema and not was_below_both and self.Position >= 0:
self.SellMarket()
self._prev_close = close
self._prev_sar = sar_val
self._prev_ema = ema_val
self._has_prev = True
def CreateClone(self):
return sample_detect_economic_calendar_strategy()