在 GitHub 上查看

Exp KWAN NRP 策略

概述

Exp KWAN NRP 策略通过将随机指标、相对强弱指数和动量指标组合成一个比率来复刻原版 MetaTrader 专家顾问。该比率再通过可配置的移动平均线进行平滑,平滑线的斜率决定何时开仓或平仓。策略适用于任意品种和周期,专注在动量发生拐点时捕捉方向性机会。

交易逻辑

  1. 计算 KWAN 比率:使用随机指标 %D 线与 RSI 数值相乘,再除以动量指标的读数。
  2. 通过可选择的移动平均(简单、指数、平滑、加权)对比率进行平滑。
  3. 在可配置的信号栏位处评估平滑线的斜率。
  4. 当平滑线向上转折时开多并平空;当平滑线向下转折时开空并平多。
  5. 可选的止损/止盈保护可以在价格按步长移动到指定距离后自动平仓。

信号说明

  • 做多开仓:信号栏位上的平滑 KWAN 值高于前一栏,且允许做多开仓。
  • 做多平仓:持有多单时平滑 KWAN 值转为下行,且允许做多平仓。
  • 做空开仓:信号栏位上的平滑 KWAN 值低于前一栏,且允许做空开仓。
  • 做空平仓:持有空单时平滑 KWAN 值转为上行,且允许做空平仓。

风险管理

  • 使用策略的 Volume 属性设定基础下单数量。翻转仓位时会先关闭反向仓位再建立新仓。
  • 启用 UseProtection 可根据合约最小报价步长设置止损与止盈,两个保护可单独或同时使用。
  • 策略基于 CandleType 指定的K线订阅数据,只在完全收盘的K线结束时触发交易。

参数列表

名称 说明 默认值
CandleType 指标计算与信号评估所使用的K线周期。 1 小时K线
KPeriod 随机指标 %K 的周期。 5
DPeriod 随机指标 %D 的周期。 3
SlowingPeriod 应用于 %K 的附加平滑周期。 3
RsiPeriod RSI 指标周期。 14
MomentumPeriod 动量指标周期。 14
SmoothingMethod 对 KWAN 比率进行平滑的移动平均类型(Simple、Exponential、Smoothed、Weighted)。 Simple
SmoothingLength 平滑移动平均的周期长度。 3
SignalBar 用于评估斜率的历史栏位偏移(0 表示当前已收盘的K线)。 1
EnableBuyEntries 允许在看多信号时开多。 true
EnableSellEntries 允许在看空信号时开空。 true
EnableBuyExits 允许在看空信号出现时平多。 true
EnableSellExits 允许在看多信号出现时平空。 true
UseProtection 启用止损/止盈保护。 true
StopLossSteps 以报价步长表示的止损距离。 1000
TakeProfitSteps 以报价步长表示的止盈距离。 2000

使用提示

  • 当动量指标为零时 KWAN 比率会变得不稳定,策略会自动跳过这些栏位以避免除零错误。
  • 通过调整 SignalBar 可以延迟确认或对齐历史信号。
  • 如需在生产环境使用,可结合券商层面的风险控制或额外过滤条件。
namespace StockSharp.Samples.Strategies;

using System;

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

/// <summary>
/// Strategy replicating the Exp KWAN NRP expert advisor by combining RSI and Momentum signals.
/// Enters long when RSI crosses above 50 and momentum is positive,
/// enters short when RSI crosses below 50 and momentum is negative.
/// </summary>
public class ExpKwanNrpStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _momentumPeriod;

	private decimal _prevRsi;
	private decimal _prevMom;
	private bool _initialized;

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

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public int MomentumPeriod
	{
		get => _momentumPeriod.Value;
		set => _momentumPeriod.Value = value;
	}

	public ExpKwanNrpStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for calculations", "General");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI length", "Indicators");

		_momentumPeriod = Param(nameof(MomentumPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Period", "Momentum length", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevRsi = 0m;
		_prevMom = 0m;
		_initialized = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevRsi = 0;
		_prevMom = 0;
		_initialized = false;

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var momentum = new Momentum { Length = MomentumPeriod };

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(rsi, momentum, (ICandleMessage candle, decimal rsiValue, decimal momValue) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				if (!_initialized)
				{
					_prevRsi = rsiValue;
					_prevMom = momValue;
					_initialized = true;
					return;
				}

				var rsiUp = rsiValue > _prevRsi;
				var rsiDown = rsiValue < _prevRsi;
				var momUp = momValue > _prevMom;
				var momDown = momValue < _prevMom;

				// Buy when both RSI and Momentum are turning up
				if (rsiUp && momUp && Position <= 0)
				{
					BuyMarket();
				}
				// Sell when both RSI and Momentum are turning down
				else if (rsiDown && momDown && Position >= 0)
				{
					SellMarket();
				}

				_prevRsi = rsiValue;
				_prevMom = momValue;
			})
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, rsi);
			DrawOwnTrades(area);
		}
	}
}