在 GitHub 上查看

Simple 2 MA I 策略

概述

Simple 2 MA I 是一套趋势跟随策略,重现了 MetaTrader 专家顾问的核心逻辑。策略使用基于典型价计算的两条线性加权移动平均线(LWMA)来识别主要趋势,同时通过动量和 MACD 方向过滤掉噪声信号。系统还提供自动止损、止盈、保本和基于 K 线的移动止损等风险控制工具。

交易逻辑

多头条件

  1. 快速 LWMA 位于慢速 LWMA 上方,确认上升趋势。
  2. 前两根 K 线的最低价低于上一根 K 线的最高价,显示新的多头结构。
  3. 最近三次动量(Rate of Change)数值中至少有一次超过动量阈值。
  4. MACD 主线高于信号线。
  5. 当前净仓位小于 Max Net Volume 限制。

满足条件时,策略会先平掉空头仓位(如果存在),然后以市价做多。

空头条件

  1. 快速 LWMA 位于慢速 LWMA 下方,确认下降趋势。
  2. 上一根 K 线的最低价低于前两根 K 线的最高价,显示新的空头结构。
  3. 最近三次动量的绝对值中至少有一次超过动量阈值。
  4. MACD 主线低于信号线。
  5. 当前净仓位小于 Max Net Volume 限制。

满足条件时,策略会先平掉多头仓位(如果存在),然后以市价做空。

风险管理

  • 止损 / 止盈: 可选的固定距离(以点数表示),相对于入场价设置。
  • 保本: 当价格达到指定盈利距离时,将止损移动至入场价 ± 偏移量。
  • K 线跟踪: 当利润达到启动阈值后,止损跟随最近一根 K 线的极值,并根据设置留出缓冲。
  • 平掉仓位后会自动撤销所有保护性订单。

参数

名称 说明 默认值
Candle Type 指标计算使用的时间框架。 15 分钟 K 线
Fast LWMA 快速 LWMA 的周期。 6
Slow LWMA 慢速 LWMA 的周期。 85
Momentum Length Rate of Change 指标的回溯周期。 14
Momentum Threshold 触发信号所需的最小动量绝对值。 0.3
MACD Fast MACD 中的快 EMA 周期。 12
MACD Slow MACD 中的慢 EMA 周期。 26
MACD Signal MACD 信号线的周期。 9
Use Stop-Loss 是否启用止损。 true
Stop-Loss (points) 止损与入场价之间的距离(点数)。 20
Use Take-Profit 是否启用止盈。 true
Take-Profit (points) 止盈与入场价之间的距离(点数)。 50
Use Break-Even 是否启用保本移动。 true
Break-Even Trigger 触发保本所需的利润(点数)。 30
Break-Even Offset 移动至保本时附加的偏移(点数)。 30
Use Candle Trailing 是否启用基于 K 线的移动止损。 true
Trailing Activation 启动移动止损所需的利润(点数)。 40
Trailing Padding 在 K 线极值外侧保留的缓冲距离(点数)。 10
Max Net Volume 允许的最大净仓位量。 1

说明

  • 所有距离参数均以品种的最小价格变动单位(点)表示,策略会自动乘以实际最小跳动。
  • 默认时间框架与原始 EA 的设定一致,用户可根据需要调整。
  • 策略仅处理已完成的 K 线,以保持与原版程序一致的行为。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class Simple2MaIStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public Simple2MaIStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}