在 GitHub 上查看

3207 – MA 趋势策略

概述

MA Trend Strategy 使用 StockSharp 高级 API 重现 MetaTrader 专家顾问 MA Trend.mq5。策略跟随一条可以向前平移的线性加权移动平均线。当收盘价上穿平移后的均线时做多,下穿时做空,并提供与原 MQL 版本相同的止损、止盈和移动止损管理。

交易逻辑

  1. 订阅设定的蜡烛类型(默认 1 分钟),根据选择的均线方法和价格源计算移动平均。
  2. 将得到的均线数值按照 MaShift 参数向前平移若干已完成的 K 线。
  3. 生成信号:
    • 做多 – 收盘价高于平移后的均线(当 ReverseSignalstrue 时条件反向)。
    • 做空 – 收盘价低于平移后的均线(当 ReverseSignalstrue 时条件反向)。
  4. 应用持仓管理选项:
    • CloseOpposite=true,在开仓前先平掉相反方向的持仓。
    • OnlyOnePosition=true 且已有持仓,则阻止新的开仓。
  5. 止损、止盈和移动止损的距离均以点(pip)为单位。当未实现盈利超过 TrailingStopPips + TrailingStepPips 时,移动止损才会推进,与 MQL 原版一致。

参数

名称 类型 默认值 说明
OrderVolume decimal 0.1 下单手数(合约数量)。
StopLossPips int 50 止损点数,0 表示关闭固定止损。
TakeProfitPips int 140 止盈点数,0 表示关闭固定止盈。
TrailingStopPips int 15 移动止损的基础距离,0 表示禁用。
TrailingStepPips int 5 更新移动止损前所需的额外盈利点数,启用移动止损时必须为正。
MaPeriod int 12 移动平均的周期长度。
MaShift int 3 移动平均向前平移的已完成 K 线数量。
MaMethod MovingAverageKind Weighted 移动平均的计算方式(Simple、Exponential、Smoothed、Weighted)。
AppliedPrice AppliedPriceMode Weighted 输入到均线的价格类型(Close、Open、High、Low、Median、Typical、Weighted)。
OnlyOnePosition bool false 是否限制同一时间只持有一笔仓位。
ReverseSignals bool false 是否反转买卖信号。
CloseOpposite bool false 是否在开仓前先平掉反向仓位。
CandleType DataType 1 分钟 用于计算的蜡烛类型/时间周期。

说明

  • Pip 大小会根据 3/5 位报价的品种自动调整,与 MetaTrader 的点值兼容。
  • TrailingStopPips > 0TrailingStepPips <= 0,策略会在启动时抛出异常,以确保移动止损逻辑有效。
  • 仅在蜡烛收盘后才进行指标计算和交易决策,便于回测和优化复现。
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 MaTrendStrategy : 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 MaTrendStrategy()
	{
		_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;
	}
}