在 GitHub 上查看

STO M5xM15xM30 策略

概述

该策略将 MetaTrader 4 中的 "STO_m5xm15xm30" 专家顾问迁移到 StockSharp 高级 API。核心思想是利用 M5、M15 与 M30 三个周期的随机指标,寻找同步的动量反转。所有关键常量都通过 StrategyParam 暴露,可在 StockSharp Designer 中直接优化。

交易逻辑

  1. 多周期确认
    • 主周期(默认 M5)随机指标必须出现金叉(%K 上穿 %D)。
    • 中周期(默认 M15)和慢周期(默认 M30)的 %K 已经位于 %D 之上。
    • 做空信号需要满足相反的条件。
  2. 位移过滤
    • 主周期随机指标会检查 ShiftBars 根之前的状态。做多时要求历史 %K 低于 %D,确保当前交叉是新的反转;做空时取反。
  3. 价格动量过滤
    • 当前收盘价必须高于上一根收盘价(做多)或低于上一根收盘价(做空),与原脚本中的 Close[0] > Close[1] 完全一致。
  4. 入场规则
    • 当没有持仓且满足做多条件时,以 TradeVolume 设定的数量买入。
    • 如果已有反向持仓,则先平仓再根据新信号开仓。做空逻辑相同。
  5. 离场规则
    • 单独的 M5 随机指标(周期 ExitKPeriod)使用上一根 K 线数据(shift = 1)。当多头 %K 下穿 %D 时平仓;空头 %K 上穿 %D 时平仓。
    • 触发离场后不会在同一根 K 线上立即反向开仓,贴合原始 EA 的循环顺序。

数据订阅与指标

  • 主周期蜡烛:默认 5 分钟 (CandleType).
  • 中周期确认蜡烛:默认 15 分钟 (MiddleCandleType).
  • 慢周期确认蜡烛:默认 30 分钟 (SlowCandleType).
  • 所有随机指标的 %K 平滑和 %D 平滑均为 3。

参数

参数 默认值 说明
CandleType 5 分钟 主工作周期。
MiddleCandleType 15 分钟 第一次确认周期。
SlowCandleType 30 分钟 第二次确认周期。
FastKPeriod 5 主随机指标的 %K 周期。
MiddleKPeriod 5 中周期 %K。
SlowKPeriod 5 慢周期 %K。
ExitKPeriod 5 离场随机指标的 %K 周期。
ShiftBars 3 当前信号与历史参考之间的间隔 K 数。
TakeProfitPoints 30 止盈距离(点)。
StopLossPoints 10 止损距离(点)。
TradeVolume 0.1 每次开仓量。

所有参数都可以在 Designer 中优化,并实时反映到策略行为中。

风险控制

StartProtection() 会把原脚本中的 TPSL 参数转换为 StockSharp 的保护性订单。如需关闭保护,将对应参数设为 0 即可。

实现细节

  • 通过 SubscribeCandles().BindEx(...) 获取指标数值,完全符合 AGENTS.md 的高层 API 约束。
  • StochasticShiftBuffer 辅助类模拟 MQL 的 shift 参数,无需显式访问 GetValue
  • 每根完成的蜡烛只处理一次:先执行离场逻辑,再评估新的入场条件。
  • 代码中提供英文注释,说明各步骤如何对应原始 MQL 逻辑。

使用步骤

  1. 在 StockSharp 方案或 Designer 中添加该策略。
  2. 选择交易品种,并准备好 M5/M15/M30 周期的历史数据。
  3. 根据需要调整参数(周期、位移、止盈止损、手数等)。
  4. 启动策略后,系统会自动提交对应的止损/止盈单。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// STO M5xM15xM30: RSI momentum with dual EMA confirmation.
/// Uses RSI crossover of 50 level with fast/slow EMA alignment.
/// </summary>
public class StoM5xM15xM30Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevRsi;
	private decimal _entryPrice;

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

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");

		_fastLength = Param(nameof(FastLength), 10)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowLength = Param(nameof(SlowLength), 30)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period for stops.", "Indicators");
	}

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

	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	/// <inheritdoc />
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevRsi = 0;
		_entryPrice = 0;
	}

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

		_prevRsi = 0;
		_entryPrice = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var fast = new ExponentialMovingAverage { Length = FastLength };
		var slow = new ExponentialMovingAverage { Length = SlowLength };
		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		if (_prevRsi == 0 || atrVal <= 0)
		{
			_prevRsi = rsiVal;
			return;
		}

		var close = candle.ClosePrice;

		// Exit management
		if (Position > 0)
		{
			if (close <= _entryPrice - atrVal * 2m || close >= _entryPrice + atrVal * 3m)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (rsiVal < 40 && fastVal < slowVal)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close >= _entryPrice + atrVal * 2m || close <= _entryPrice - atrVal * 3m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			else if (rsiVal > 60 && fastVal > slowVal)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		// Entry: RSI crosses 50 with EMA alignment
		if (Position == 0)
		{
			if (_prevRsi <= 50 && rsiVal > 50 && fastVal > slowVal)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (_prevRsi >= 50 && rsiVal < 50 && fastVal < slowVal)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevRsi = rsiVal;
	}
}