在 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,能够快速跟踪微结构变化;HighLookback 与 LowLookback 默认为 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)。
- 过滤标签
- 分类:反转。
- 方向:双向。
- 指标:简单移动平均、最高/最低摆动。
- 止损:动态、基于摆动极值。
- 时间框架:日内到波段。
- 复杂度:中等。
- 风险等级:中等(止损紧但交易频繁)。
使用提示
- 推荐用于摆动明显的品种。重大新闻期间可能出现伪信号,可考虑暂停交易。
- 针对目标品种优化 SMA 周期与窗口长度。窗口越短,响应越快,同时假信号也越多。
- 只有在出现新的拐点信号时才会刷新止损。如果候选极值不满足条件(例如高点不高于上一收盘价),系统会忽略该水平,避免止损过近。
- 因为离场采用市价单,剧烈波动时可能产生滑点。如交易平台支持,可结合经纪商侧的保护性订单。
- 策略没有固定止盈。如需增加止盈,可在
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_sr_trading_strategy(Strategy):
def __init__(self):
super(ma_sr_trading_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20).SetDisplay("EMA Period", "EMA lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0; self._prev_ema = 0.0; self._has_prev = False; self._cooldown = 0
@property
def ema_period(self): return self._ema_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(ma_sr_trading_strategy, self).OnReseted()
self._prev_close = 0.0; self._prev_ema = 0.0; self._has_prev = False; self._cooldown = 0
def OnStarted2(self, time):
super(ma_sr_trading_strategy, self).OnStarted2(time)
self._has_prev = False; self._cooldown = 0
ema = ExponentialMovingAverage(); ema.Length = self.ema_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, self.process_candle).Start()
def process_candle(self, candle, ema):
if candle.State != CandleStates.Finished: return
if not self.IsFormedAndOnlineAndAllowTrading(): return
close = float(candle.ClosePrice); ema_val = float(ema)
if not self._has_prev:
self._prev_close = close; self._prev_ema = ema_val; self._has_prev = True; return
if self._cooldown > 0:
self._cooldown -= 1; self._prev_close = close; self._prev_ema = ema_val; return
if self._prev_close <= self._prev_ema and close > ema_val and self.Position <= 0:
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume); self._cooldown = 6
elif self._prev_close >= self._prev_ema and close < ema_val and self.Position >= 0:
volume = self.Volume + abs(self.Position)
self.SellMarket(volume); self._cooldown = 6
self._prev_close = close; self._prev_ema = ema_val
def CreateClone(self): return ma_sr_trading_strategy()