在 GitHub 上查看

SSB5_123 多指标策略

概述

本策略是 MetaTrader 5 专家顾问“ssb5_123”的 StockSharp 版本。原始 EA 出自 Yury V. Reshetov 的 SSB(Step by Step)系列,通过多种经典振荡指标共同确认趋势突破。移植后的实现完全采用 StockSharp 的高层蜡烛订阅 API 与内置指标,保留了所有判定逻辑。

策略仅在每根蜡烛收盘后运行一次。它比较当前柱的开盘价与上一柱,分析 Awesome Oscillator、MACD 与 OsMA 直方图的方向和动量,并检查价格是否位于平滑移动平均线上方或下方。最后使用随机指标,要求 %K 与 %D 同时位于 50 上方(做多)或下方(做空)。

指标与信号

  • 平滑移动平均(SMMA,45):基于开盘价的平滑移动平均,要求开盘价处于趋势方向的一侧。
  • MACD(快 47、慢 95、信号 74):主线在多头时为正、空头时为负,同时主线相对上一柱需要上升或下降。
  • OsMA 直方图:等于 MACD 主线减去信号线。多头要求直方图不增加(当前值减上一值 ≤ 0),空头要求不减少。
  • Awesome Oscillator:默认 5/34 周期,数值本身以及与上一柱的差值必须指向交易方向。
  • 随机指标(K=25、D=12、Slowing=56):%K 与 %D 必须同时高于或低于 50,作为趋势过滤。

交易逻辑

  1. 等待新的已完成蜡烛。
  2. 判断做多条件,全部成立时才入场:
    • 当前开盘价 ≤ 上一柱开盘价。
    • AO 为正且较上一柱下降。
    • MACD 主线为正且较上一柱上升。
    • OsMA 直方图不增加(当前-上一 ≤ 0)。
    • 当前开盘价高于 SMMA。
    • 随机指标 %K、%D ≥ 50。
  3. 判断做空条件,全部成立时才入场:
    • 当前开盘价 ≥ 上一柱开盘价。
    • AO 为负且较上一柱上升。
    • MACD 主线为负且较上一柱下降。
    • OsMA 直方图不减少(当前-上一 ≥ 0)。
    • 当前开盘价低于 SMMA。
    • 随机指标 %K、%D ≤ 50。
  4. 若已有仓位,出现反向信号时立即平仓,保持与原始 EA 相同的顺序。
  5. 空仓时优先考虑做多信号。如果两个方向同时满足(极少发生,所有指标为零),策略与原程序一致,先执行做多。

参数

  • SMMA Period – 平滑移动平均周期,默认 45。
  • MACD Fast / Slow / Signal – MACD 指标的 EMA 周期,默认 47 / 95 / 74。
  • Stochastic %K / %D / Slowing – 随机指标主周期、平滑周期和额外平滑,默认 25 / 12 / 56。
  • Order Volume – 下单数量,默认 1。
  • Candle Type – 蜡烛时间框架,默认 1 小时,可根据需求调整为与 MT5 相同的周期。

使用提示

  • 策略只处理收盘后的数据;未完成蜡烛不会触发任何操作。
  • 为了重现 fao1fmacd1fosma1 的行为,上一柱指标值会缓存,用于计算动量方向。
  • 原始 EA 未设置止损或止盈,风险控制需要在外部策略或子策略中实现。
  • 所有参数都通过 StrategyParam 暴露,可在 StockSharp 优化器中做批量优化。

移植注意事项

  • MQL 版本包含的魔术号与手动手数校验在 StockSharp 中不再需要,已移除。
  • 仓位管理顺序保持一致:先平仓再考虑反向开仓,且同一根蜡烛不会立即翻仓。
  • Awesome Oscillator 与 MACD 由 StockSharp 指标库直接提供,省去了原代码中手动读取指标缓冲区的繁琐步骤。
using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// SSB5 123 strategy. Uses triple EMA alignment for 1-2-3 pattern detection.
/// </summary>
public class Ssb5123Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _midPeriod;
	private readonly StrategyParam<int> _slowPeriod;

	private decimal? _prevFast;
	private decimal? _prevMid;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int MidPeriod
	{
		get => _midPeriod.Value;
		set => _midPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public Ssb5123Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_fastPeriod = Param(nameof(FastPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_midPeriod = Param(nameof(MidPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("Mid EMA", "Middle EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevMid = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevFast = null;
		_prevMid = null;

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var mid = new ExponentialMovingAverage { Length = MidPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, mid, slow, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fast);
			DrawIndicator(area, mid);
			DrawIndicator(area, slow);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal midVal, decimal slowVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevFast = fastVal;
			_prevMid = midVal;
			return;
		}

		if (_prevFast == null || _prevMid == null)
		{
			_prevFast = fastVal;
			_prevMid = midVal;
			return;
		}

		// Fast crosses above mid while both above slow
		if (_prevFast.Value <= _prevMid.Value && fastVal > midVal && midVal > slowVal && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Fast crosses below mid while both below slow
		else if (_prevFast.Value >= _prevMid.Value && fastVal < midVal && midVal < slowVal && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevFast = fastVal;
		_prevMid = midVal;
	}
}