在 GitHub 上查看
双均线日内过滤策略
概览
该策略基于 MetaTrader 专家顾问 Expert_2EMA_ITF,使用 StockSharp 高级 API 复刻实现。策略通过两条指数移动平均线(EMA)的金叉/死叉寻找方向,并利用平均真实波幅(ATR)计算限价单、止损和止盈位置,同时提供日内时间过滤器以避开不希望交易的分钟、小时或星期几。
交易逻辑
- 在所选蜡烛周期上计算快 EMA 与慢 EMA。
- 当快 EMA 从下向上穿越慢 EMA 时视为多头信号;从上向下穿越时视为空头信号。
- 多头信号出现时,根据
LimitMultiplier * ATR 的偏移量(并在可用时加上价差)在慢 EMA 下方挂买入限价单;空头信号则在慢 EMA 上方挂卖出限价单。
- 记录由 ATR 派生的止损与止盈价格,一旦限价单成交立即发送对应的保护性委托。
- 若挂单在
ExpirationBars 根 K 线后仍未成交则自动撤销。
- 仅在当前时间满足日内过滤条件(允许的分钟、小时、星期几,并且未被位掩码屏蔽)时才允许发送新信号。
指标
- 快 EMA:用于快速响应价格变化。
- 慢 EMA:用于定义趋势方向。
- ATR:衡量波动性并为入场、止损、止盈提供缩放因子。
参数
| 参数 |
说明 |
默认值 |
CandleType |
计算所用的蜡烛类型/周期。 |
30 分钟蜡烛 |
FastEmaPeriod |
快 EMA 周期。 |
5 |
SlowEmaPeriod |
慢 EMA 周期(必须大于快 EMA)。 |
30 |
AtrPeriod |
ATR 计算周期。 |
7 |
LimitMultiplier |
入场价格的 ATR 偏移倍数。 |
1.2 |
StopLossMultiplier |
止损价格的 ATR 倍数。 |
5 |
TakeProfitMultiplier |
止盈价格的 ATR 倍数。 |
8 |
ExpirationBars |
挂单最多保留的 K 线数量。 |
4 |
GoodMinuteOfHour |
允许的分钟(-1 表示不限)。 |
-1 |
BadMinutesMask |
分钟屏蔽位掩码(第 n 位为 1 表示屏蔽第 n 分钟)。 |
0 |
GoodHourOfDay |
允许的小时(-1 表示不限)。 |
-1 |
BadHoursMask |
小时屏蔽位掩码。 |
0 |
GoodDayOfWeek |
允许的星期几(-1 表示不限,0=周日)。 |
-1 |
BadDaysMask |
星期几屏蔽位掩码(0=周日)。 |
0 |
委托管理
- 入场委托:依据 ATR 偏移量计算限价单价格,多头入场在可用时报价差补偿。
- 过期控制:记录挂单创建时的 K 线序号,超过
ExpirationBars 仍未成交则撤单。
- 保护性委托:当挂单成交后,取消旧的止损/止盈委托,并使用当时 ATR 计算得到的价格立即下达新的止损与止盈;当持仓归零时撤销保护单。
日内过滤细节
- 单值限制:
GoodMinuteOfHour、GoodHourOfDay 与 GoodDayOfWeek 分别限制允许的分钟、小时、星期几。
- 位掩码屏蔽:
BadMinutesMask、BadHoursMask、BadDaysMask 可一次性屏蔽多个时间段,例如 BadMinutesMask = (1 << 0) | (1 << 30) 会屏蔽每小时的第 0 与第 30 分钟。
- 综合判断:只有在全部“允许”条件成立且没有掩码命中时,才允许发出新的交易信号。
与原始 EA 的差异
- 使用 StockSharp 的
BuyLimit/SellLimit/SellStop/BuyStop 等高阶方法管理挂单与保护单,而非 MetaTrader 的 Expert 库结构。
- 买入方向的价差补偿取自当前
Security.BestBid / Security.BestAsk,若无报价则偏移为 0。
- 时间过滤逻辑通过位掩码实现,代替了 MetaTrader 中的
CSignalITF 类。
- 订单填补后立刻下达保护性委托,行为与原始信号中返回的止损/止盈水平保持一致。
使用提示
- 启动策略前需设置
Volume,否则策略会记录警告并拒绝下单。
- 关键参数均开启了优化标记,可直接用于参数优化流程。
- 策略使用蜡烛的收盘时间来判定日内过滤条件。
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Two EMA Intraday Filter strategy: EMA crossover.
/// Buys when fast EMA crosses above slow EMA. Sells on cross below.
/// </summary>
public class TwoEmaIntradayFilterStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public TwoEmaIntradayFilterStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
decimal? prevFast = null;
decimal? prevSlow = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, (candle, fastVal, slowVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (prevFast.HasValue && prevSlow.HasValue)
{
if (prevFast.Value <= prevSlow.Value && fastVal > slowVal && Position <= 0)
BuyMarket();
else if (prevFast.Value >= prevSlow.Value && fastVal < slowVal && Position >= 0)
SellMarket();
}
prevFast = fastVal;
prevSlow = slowVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
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 two_ema_intraday_filter_strategy(Strategy):
def __init__(self):
super(two_ema_intraday_filter_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._fast_period = self.Param("FastPeriod", 12) \
.SetGreaterThanZero() \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetGreaterThanZero() \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(two_ema_intraday_filter_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(two_ema_intraday_filter_strategy, self).OnStarted2(time)
self._fast_ind = ExponentialMovingAverage()
self._fast_ind.Length = self._fast_period.Value
self._slow_ind = ExponentialMovingAverage()
self._slow_ind.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._fast_ind, self._slow_ind, self._process_candle).Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if self._prev_fast is not None and self._prev_slow is not None:
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return two_ema_intraday_filter_strategy()