在 GitHub 上查看

趋势反转策略

概述

趋势反转策略旨在捕捉既有趋势中的回调结束后出现的突破行情。该版本基于 MetaTrader 平台上的 "Trend Reversal" 智能交易系统移植,并使用 StockSharp 的高级 API 重新实现。移植过程中保留了关键的确认条件(移动平均线、动量和 MACD),将原策略依赖的人工趋势线替换为可重复的 K 线重叠判断。

指标组合

  • 线性加权移动平均线(LWMA):基于典型价(高+低+收盘的平均值),可分别设置快线与慢线周期。快线响应最近的波动,慢线描述主要趋势。
  • 动量指标(Momentum):使用同一时间框架。策略维护最近三根已完成 K 线的动量数值与 100 的偏差,以复制原策略的判定方式。
  • MACD 主线与信号线:快、慢、信号 EMA 周期均可独立设置。MACD 的方向用作多空交易的高级别过滤条件。

交易逻辑

  1. 仅在目标时间框架的 K 线收盘后评估信号,未完成的 K 线会被忽略。
  2. 必须确保两条 LWMA 和动量指标都已经形成,否则策略保持空仓。
  3. 维护最近三次动量偏离 100 的绝对值,只要其中任意一次超过多头或空头的阈值即可满足动量条件。
  4. 要求两根 K 线之前的最低价低于前一根 K 线的最高价,用以刻画原策略的窄幅整理结构。
  5. 多空过滤条件:
    • 做多: 快速 LWMA 高于慢速 LWMA,且 MACD 主线高于信号线。
    • 做空: 快速 LWMA 低于慢速 LWMA,且 MACD 主线低于信号线。
  6. 遵守净持仓限制。只有在当前持仓量(除以下单量)小于参数 MaxPositions 时才允许开仓或加仓。
  7. 通过 BuyMarket()SellMarket() 下达市价单,可在满足条件时逐步反转头寸。

风险控制

  • 可选的止盈止损(以价格单位表示)通过 StockSharp 的保护模块自动附加,参数为 0 时代表禁用该水平。
  • 本移植版本未实现原策略的跟踪止损与保本移动,如有需要可以另行添加事件处理器。

参数

名称 说明 默认值
CandleType 生成信号所使用的主时间框架。 15 分钟
FastLength 快速 LWMA 的周期。 6
SlowLength 慢速 LWMA 的周期。 85
MomentumLength 动量指标的周期。 14
MomentumBuyThreshold 判定多头信号所需的最小动量偏差(距离 100)。 0.3
MomentumSellThreshold 判定空头信号所需的最小动量偏差。 0.3
MacdFastLength MACD 快速 EMA 周期。 12
MacdSlowLength MACD 慢速 EMA 周期。 26
MacdSignalLength MACD 信号 EMA 周期。 9
TakeProfit 止盈距离(价格单位,0 表示关闭)。 50
StopLoss 止损距离(价格单位,0 表示关闭)。 20
TradeVolume 每次下单的交易手数。 1
MaxPositions 允许持有的净头寸(以下单量为单位)的最大值。 1

使用建议

  • 运行前确保标的证券的价格步长和货币步长设置正确,否则保护性订单可能无法按预期运作。
  • 若需要分批加仓或金字塔式建仓,可以提高 MaxPositions。只要过滤条件满足且未超过限制,策略会继续累积头寸。
  • 回测时请使用与 CandleType 相同的时间框架,策略启动时 StockSharp 会自动请求对应数据。
  • 由于原策略依赖人工绘制的趋势线,本版本改用固定的 K 线重叠条件,使得回测与实时环境保持一致性。

与原版 EA 的差异

  • 未实现跟踪止损、保本移动以及基于权益的紧急平仓,以便聚焦于核心信号逻辑。
  • 移除了批量加仓、Magic Number 过滤等 MetaTrader 专用的资金管理功能,这些在 StockSharp 架构中并非必需。
  • MACD 使用与交易相同的时间框架进行计算。如果希望重现原来的多时间框架结构,可额外订阅较慢的 K 线并在其中绑定 MACD。

优化提示

  • 优先调节两条 LWMA 的周期以适配标的的趋势节奏,再微调动量阈值。
  • 在高波动市场中适当扩大止损与止盈距离,趋势策略通常需要更宽的安全边际。
  • 在优化过程中关注回撤指标。增大 MaxPositions 会提升反应速度,但同时增加风险敞口。
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 TrendReversalStrategy : 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 TrendReversalStrategy()
	{
		_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;
	}
}