在 GitHub 上查看

KlPrice Reversal 策略

该策略是 MQL5 专家 exp_i-KlPrice.mq5 的 C# 版本。它基于归一化价格振荡指标实现反转交易系统。振荡器将当前价格与由移动平均线和平均真实波幅(ATR)构建的平滑价格带进行比较。穿越预设边界会产生交易信号。

工作原理

  1. 简单移动平均线(SMA)平滑收盘价。

  2. 平均真实波幅(ATR)评估市场波动。

  3. 振荡器计算公式:

    jres = 100 * (Close - (SMA - ATR)) / (2 * ATR) - 50

  4. 振荡器数值分为五个区域:

    • 4 – 高于上方阈值
    • 3 – 介于零和上方阈值之间
    • 2 – 介于上方和下方阈值之间
    • 1 – 介于下方阈值和零之间
    • 0 – 低于下方阈值
  5. 当振荡器离开区域 4 时开多单;离开区域 0 时开空单;当振荡器穿越零轴时平仓。

参数

名称 描述
CandleType 计算所用的K线周期。
PriceMaLength 平滑价格的 SMA 周期。
AtrLength ATR 周期,用于估算价格带。
UpLevel 振荡器上方阈值。
DownLevel 振荡器下方阈值。
EnableBuy 允许开多头。
EnableSell 允许开空头。

使用方法

  1. 创建 KlPriceReversalStrategy 实例。
  2. 设置所需参数。
  3. 将策略连接到投资组合和证券。
  4. 启动策略以接收信号并发送订单。

策略通过 BuyMarket 和 SellMarket 提交市价单,并通过 StartProtection() 启用仓位保护。

说明

  • 实现使用 StockSharp 内置指标 SimpleMovingAverage 和 AverageTrueRange,以近似原始 MQL 指标。
  • 所有计算仅基于已完成的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;

/// <summary>
/// KlPrice reversal strategy.
/// Calculates a normalized oscillator based on price position relative to EMA and ATR.
/// Buys when leaving overbought zone, sells when leaving oversold zone.
/// </summary>
public class KlPriceReversalStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _priceMaLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _upLevel;
	private readonly StrategyParam<decimal> _downLevel;

	private decimal _prevColor;
	private bool _isFirst;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int PriceMaLength { get => _priceMaLength.Value; set => _priceMaLength.Value = value; }
	public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
	public decimal UpLevel { get => _upLevel.Value; set => _upLevel.Value = value; }
	public decimal DownLevel { get => _downLevel.Value; set => _downLevel.Value = value; }

	public KlPriceReversalStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for calculations", "General");

		_priceMaLength = Param(nameof(PriceMaLength), 100)
			.SetGreaterThanZero()
			.SetDisplay("Price MA Length", "EMA period for price smoothing", "Parameters");

		_atrLength = Param(nameof(AtrLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("ATR Length", "ATR period for range estimation", "Parameters");

		_upLevel = Param(nameof(UpLevel), 50m)
			.SetDisplay("Upper Level", "Upper threshold for signals", "Parameters");

		_downLevel = Param(nameof(DownLevel), -50m)
			.SetDisplay("Lower Level", "Lower threshold for signals", "Parameters");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevColor = 2m;
		_isFirst = true;
	}

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

		_isFirst = true;
		_prevColor = 2m;

		var priceMa = new ExponentialMovingAverage { Length = PriceMaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(priceMa, atr, ProcessCandle)
			.Start();
	}

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

		if (!maValue.IsFormed || !atrValue.IsFormed)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var ma = maValue.ToDecimal();
		var tr = atrValue.ToDecimal();
		if (tr == 0m)
			return;

		var dwband = ma - tr;
		var jres = 100m * (candle.ClosePrice - dwband) / (2m * tr) - 50m;

		var color = 2m;
		if (jres > UpLevel)
			color = 4m;
		else if (jres > 0m)
			color = 3m;

		if (jres < DownLevel)
			color = 0m;
		else if (jres < 0m)
			color = 1m;

		if (!_isFirst)
		{
			// Buy: leaving overbought (was 4, now < 4)
			if (_prevColor == 4m && color < 4m && Position <= 0)
			{
				if (Position < 0) BuyMarket();
				BuyMarket();
			}
			// Sell: leaving oversold (was 0, now > 0)
			else if (_prevColor == 0m && color > 0m && Position >= 0)
			{
				if (Position > 0) SellMarket();
				SellMarket();
			}
		}

		_prevColor = color;
		_isFirst = false;
	}
}