在 GitHub 上查看
Boring EA2 Alert
概述
Boring EA2 Alert 重现了 MetaTrader 4 专家顾问 boring-ea2 的通知逻辑。策略监听收盘的K线,计算三条简单移动平均线(SMA 3、SMA 20、SMA 150),并在均线之间出现金叉或死叉时输出详细日志。实现刻意不下单,目的是为交易者提供及时的提醒,方便与自主执行或其他自动化策略组合使用。
策略逻辑
均线跟踪
- 短周期偏移 – 3 周期 SMA 对即时价格变动高度敏感。
- 中期趋势 – 20 周期 SMA 平滑短线波动。
- 长期背景 – 150 周期 SMA 刻画主导趋势框架。
交叉检测
- SMA3 vs SMA20 – 当 SMA3 上穿 SMA20 时报告 "crossed up",下穿时报告 "crossed down"。内部状态标记确保同一次切换只通知一次。
- SMA3 vs SMA150 – 与长期均线复用相同逻辑,用于捕捉动量爆发或对主趋势的逆转。
- SMA20 vs SMA150 – 额外的中长期确认层,长期结构发生变化时会生成独立提醒。
- 初始化保护 – 第一根收盘K线仅用于建立初始关系,真正的提醒从第二根收盘K线开始。
通知格式
- 提醒沿用原始 EA 的格式:
Alert!!! - SYMBOL - TF - description。
- 时间框架代码由所选 K 线类型推导,优先使用 MetaTrader 风格缩写(M1、M5、H1 等),无法匹配时退化为紧凑表示(如
M45、D2)。
- 消息通过
AddInfoLog 写入,可由日志查看器、脚本或图形界面捕获。
参数
- Short SMA Length – 快速均线的周期数(默认
3)。
- Medium SMA Length – 中期均线的周期数(默认
20)。
- Long SMA Length – 慢速均线的周期数(默认
150)。
- Candle Type – 计算均线的K线周期。默认采用 1 分钟,以保持与 EA 基于 tick 的高灵敏度一致。
其他说明
- 策略不会提交、修改或撤销订单,完全用于信息提醒。
- 通过
Bind 获取收盘数据,每次交叉都基于完成的K线判断,避免原始 EA 为抑制噪音而计数 tick 的复杂度。
- 通过订阅策略日志事件,可以将通知整合到自定义处理程序中。
- 目前仅提供 C# 版本,API 包中没有 Python 实现。
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Boring EA2 strategy: Triple SMA crossover.
/// Buys when fast crosses above medium while medium above slow.
/// Sells when fast crosses below medium while medium below slow.
/// </summary>
public class BoringEa2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public BoringEa2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fast = new SimpleMovingAverage { Length = 10 };
var med = new SimpleMovingAverage { Length = 20 };
var slow = new SimpleMovingAverage { Length = 40 };
decimal? prevFast = null;
decimal? prevMed = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, med, slow, (candle, fastVal, medVal, slowVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (prevFast.HasValue && prevMed.HasValue)
{
var fastCrossUp = prevFast.Value <= prevMed.Value && fastVal > medVal;
var fastCrossDown = prevFast.Value >= prevMed.Value && fastVal < medVal;
if (fastCrossUp && medVal > slowVal && Position <= 0)
BuyMarket();
else if (fastCrossDown && medVal < slowVal && Position >= 0)
SellMarket();
}
prevFast = fastVal;
prevMed = medVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, med);
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class boring_ea2_strategy(Strategy):
def __init__(self):
super(boring_ea2_strategy, self).__init__()
self._fast = None
self._med = None
self._slow = None
self._prev_fast = None
self._prev_med = None
def OnReseted(self):
super(boring_ea2_strategy, self).OnReseted()
self._fast = None
self._med = None
self._slow = None
self._prev_fast = None
self._prev_med = None
def OnStarted2(self, time):
super(boring_ea2_strategy, self).OnStarted2(time)
self._fast = SimpleMovingAverage()
self._fast.Length = 10
self._med = SimpleMovingAverage()
self._med.Length = 20
self._slow = SimpleMovingAverage()
self._slow.Length = 40
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(15)))
subscription.Bind(self._fast, self._med, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, med_value, slow_value):
if candle.State != CandleStates.Finished:
return
if not self._fast.IsFormed or not self._med.IsFormed or not self._slow.IsFormed:
return
fast_val = float(fast_value)
med_val = float(med_value)
slow_val = float(slow_value)
if self._prev_fast is not None and self._prev_med is not None:
fast_cross_up = self._prev_fast <= self._prev_med and fast_val > med_val
fast_cross_down = self._prev_fast >= self._prev_med and fast_val < med_val
if fast_cross_up and med_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif fast_cross_down and med_val < slow_val and self.Position >= 0:
self.SellMarket()
self._prev_fast = fast_val
self._prev_med = med_val
def CreateClone(self):
return boring_ea2_strategy()