在 GitHub 上查看

MACD 随机指标移动止损策略

概述

  • 基于 MetaTrader 4 专家顾问 MQL/7637/3_lccfpgubwykd__www_forex-instruments_info.mq4 改写。
  • 采用 三重周期 结构:小时线驱动两组 MACD 过滤器,15 分钟线提供两个随机指标,1 分钟线确认突破并负责移动止损。
  • 完全使用 StockSharp 高阶 API(SubscribeCandles(...).Bind(...) / BindEx(...)),无需手动遍历历史序列。
  • 通过市价单进出场,所有仓位管理逻辑都封装在策略内部,无需修改测试基础设施。

指标与参数

名称 类型 默认值 说明
LongStopLoss decimal 17 多头初始止损,单位为品种点值。
ShortStopLoss decimal 40 空头初始止损,单位为点值。
LongTrailingStop decimal 88 多头移动止损距离。
ShortTrailingStop decimal 76 空头移动止损距离。
OrderVolume decimal 0.1 基础下单手数,对应原 EA 的 Lots 输入。
MacdCandleType DataType H1 多空 MACD 过滤器所用周期(22/27/919/77/9)。
StochasticCandleType DataType M15 两个随机指标所用周期(5/3/119/3/19)。
EntryCandleType DataType M1 用于价格确认与移动止损的周期。

所有点值类参数都会乘以交易品种的 PriceStep,与 MetaTrader 的 Point 逻辑一致。

交易规则

做多条件

  1. 小时 MACD(22,27,9) 主线高于上一根且仍位于零线以下。
  2. 15 分钟随机指标 (%K=5,%D=3,slowing=11) 低于 26 且较上一数值上升。
  3. 当前 1 分钟收盘价突破上一根 1 分钟最高价。
  4. 满足条件且无持仓时,按 OrderVolume 下多单,并在需要时先反转空头仓位。

做空条件

  1. 小时 MACD(19,77,9) 主线低于上一根,且上一根值在零线上方。
  2. 15 分钟随机指标 (%K=9,%D=3,slowing=19) 高于 70。
  3. 当前 1 分钟收盘价跌破上一根 1 分钟最低价。
  4. 满足条件时开空仓,逻辑与原始 EA 一致。

止损与移动止损

  • 初始止损距离直接复刻 MQL 输入的点值。
  • 当价格朝有利方向运行超过设定距离后,移动止损在每根已完成的 1 分钟 K 线中重新计算。
  • 一旦价格触及当前止损(初始或移动),仓位将通过市价单平仓。

实现细节

  • 按周期拆分订阅,保证指标更新顺序与 EA 的多周期逻辑完全对应。
  • MQL 中基于 Bid/Ask 的比较在高阶 API 下通过 1 分钟 K 线的高低价近似实现。
  • 代码遵循仓库规范:使用制表符缩进、命名空间 StockSharp.Samples.Strategies、英文注释,并在构造函数中通过 Param(...) 定义参数。
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>
/// MACD + Stochastic trailing strategy.
/// Enters long when MACD histogram is positive and Stochastic K crosses above D from oversold.
/// Enters short when MACD histogram is negative and Stochastic K crosses below D from overbought.
/// </summary>
public class MacdStochasticTrailingStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _prevK;
	private decimal? _prevD;

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

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

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

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

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

		_prevK = null;
		_prevD = null;

		var macd = new MovingAverageConvergenceDivergenceSignal();
		var stoch = new StochasticOscillator();

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(macd, stoch, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue stochValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!macdValue.IsFinal || !stochValue.IsFinal)
			return;

		var macdVal = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
		var stochVal = (StochasticOscillatorValue)stochValue;

		if (macdVal.Macd is not decimal macd || macdVal.Signal is not decimal signal)
			return;
		if (stochVal.K is not decimal k || stochVal.D is not decimal d)
			return;

		if (_prevK is not decimal prevK || _prevD is not decimal prevD)
		{
			_prevK = k;
			_prevD = d;
			return;
		}

		var histogram = macd - signal;
		var kCrossUp = prevK <= prevD && k > d;
		var kCrossDown = prevK >= prevD && k < d;

		// Buy: MACD histogram positive + stochastic K crosses above D
		if (histogram > 0 && kCrossUp && k < 50 && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Sell: MACD histogram negative + stochastic K crosses below D
		else if (histogram < 0 && kCrossDown && k > 50 && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevK = k;
		_prevD = d;
	}
}