在 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 等),无法匹配时退化为紧凑表示(如 M45D2)。
  • 消息通过 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);
		}
	}
}