在 GitHub 上查看

MA S.R. Trading 策略

MA S.R. Trading 策略移植自 MetaTrader 顾问程序“MA S.R Trading”。系统监控一条短周期简单移动平均线(SMA)的形态,以识别价格动量在最近几个K线上形成的局部高点或低点。一旦检测到拐点,策略立即按照趋势反转方向建仓,并把最近的摆动极值记录为止损水平。

与传统的多均线交叉不同,该策略只分析同一条 SMA 的曲率,通过比较最近三根已收盘K线的值来判断走势。如果 SMA[t-2] 同时高于 SMA[t-1]SMA[t-3],说明出现局部峰值,触发做空;如果 SMA[t-2] 同时低于两侧数值,说明出现谷底,触发做多。信号出现后,系统会在可配置的窗口内寻找最高点或最低点,并将其作为保护性止损。

离场逻辑完全复刻了原始 MQL 代码中的追踪止损。空头仓位使用 HighLookback 窗口内的最高点,但只有当该水平高于上一根K线的收盘价时才生效;多头仓位使用 LowLookback 窗口内的最低点,且该水平必须低于上一根收盘价。之后每根完成的K线都会检查价格是否触碰该水平,一旦命中即市价平仓,从而模拟 OrderModify 的行为。

该策略适合在存在明显摆动的日内或短周期市场中运行。默认 SMA 周期为 5,能够快速跟踪微结构变化;HighLookbackLowLookback 默认为 5,可控制止损跟随的紧密程度。对于高频/剥头皮场景可使用更短的窗口,而波动噪声较大的市场则需要更长的窗口。

在主要外汇对和部分指数 CFD 的历史测试中,策略在震荡行情表现最佳;在单边缓慢趋势中,建议增加过滤器或波动性确认,以减少过早逆势进场。真实交易中可结合时间过滤或宏观背景分析使用。

细节

  • 入场条件
    • 做空SMA[t-1] < SMA[t-2]SMA[t-3] < SMA[t-2],最近的 SMA 形成局部顶部。
    • 做多SMA[t-1] > SMA[t-2]SMA[t-3] > SMA[t-2],最近的 SMA 形成局部底部。
  • 止损管理
    • 做空:止损 = HighLookback 根K线内的最高价,并且该价位必须高于上一根收盘价,价格触碰后平仓。
    • 做多:止损 = LowLookback 根K线内的最低价,并且该价位必须低于上一根收盘价,价格触碰后平仓。
  • 持仓规则:始终跟随最新信号。当方向反转时,通过单一市场订单同时平掉旧仓并开立新仓,订单量覆盖原仓位和目标交易量。
  • 默认参数
    • SmaPeriod = 5。
    • HighLookback = 5。
    • LowLookback = 5。
    • CandleType = 30 分钟K线。
    • TradeVolume = 1 手(在启动时赋值给 Volume)。
  • 过滤标签
    • 分类:反转。
    • 方向:双向。
    • 指标:简单移动平均、最高/最低摆动。
    • 止损:动态、基于摆动极值。
    • 时间框架:日内到波段。
    • 复杂度:中等。
    • 风险等级:中等(止损紧但交易频繁)。

使用提示

  1. 推荐用于摆动明显的品种。重大新闻期间可能出现伪信号,可考虑暂停交易。
  2. 针对目标品种优化 SMA 周期与窗口长度。窗口越短,响应越快,同时假信号也越多。
  3. 只有在出现新的拐点信号时才会刷新止损。如果候选极值不满足条件(例如高点不高于上一收盘价),系统会忽略该水平,避免止损过近。
  4. 因为离场采用市价单,剧烈波动时可能产生滑点。如交易平台支持,可结合经纪商侧的保护性订单。
  5. 策略没有固定止盈。如需增加止盈,可在 ProcessCandle 方法中拓展逻辑。
using System;







using StockSharp.Algo.Indicators;



using StockSharp.Algo.Strategies;



using StockSharp.BusinessEntities;



using StockSharp.Messages;







namespace StockSharp.Samples.Strategies;







public class MaSrTradingStrategy : Strategy



{



	private readonly StrategyParam<int> _emaPeriod;



	private readonly StrategyParam<int> _momentumPeriod;



	private readonly StrategyParam<DataType> _candleType;







	private decimal _prevClose; private decimal _prevEma; private bool _hasPrev;



	private int _cooldown;







	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }



	public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }



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







	public MaSrTradingStrategy()



	{



		_emaPeriod = Param(nameof(EmaPeriod), 20).SetDisplay("EMA Period", "EMA lookback", "Indicators");



		_momentumPeriod = Param(nameof(MomentumPeriod), 14).SetDisplay("Momentum", "Momentum period", "Indicators");



		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");



	}







	/// <inheritdoc />



	protected override void OnReseted()



	{



		base.OnReseted();



		_prevClose = default;



		_prevEma = default;



		_hasPrev = default;



		_cooldown = default;



	}







	/// <inheritdoc />



	/// <inheritdoc />

	protected override void OnStarted2(DateTime time)



	{



		base.OnStarted2(time);



		_hasPrev = false;



		var ema = new ExponentialMovingAverage { Length = EmaPeriod };



		var subscription = SubscribeCandles(CandleType);



		subscription.Bind(ema, ProcessCandle).Start();



	}







	private void ProcessCandle(ICandleMessage candle, decimal ema)



	{



		if (candle.State != CandleStates.Finished) return;



		if (!IsFormedAndOnlineAndAllowTrading()) return;



		var close = candle.ClosePrice;



		if (!_hasPrev) { _prevClose = close; _prevEma = ema; _hasPrev = true; return; }



		if (_cooldown > 0)



		{



			_cooldown--;



			_prevClose = close; _prevEma = ema;



			return;



		}







		if (_prevClose <= _prevEma && close > ema && Position <= 0)



		{



			var volume = Volume + Math.Abs(Position);



			BuyMarket(volume);



			_cooldown = 6;



		}



		else if (_prevClose >= _prevEma && close < ema && Position >= 0)



		{



			var volume = Volume + Math.Abs(Position);



			SellMarket(volume);



			_cooldown = 6;



		}



		_prevClose = close; _prevEma = ema;



	}



}