在 GitHub 上查看

Cronex RSI 策略

Cronex RSI 策略 将 MQL5 中的 Exp_CronexRSI.mq5 专家顾问迁移到 StockSharp 高阶 API。策略以 RSI 为核心,通过两层移动平均 对指标进行平滑,构建出快线与慢线。信号在两条平滑曲线交叉时触发,并提供与原始脚本一致的多空开平仓开关。

交易逻辑

  1. 根据选定的价格类型和周期计算 RSI。
  2. 将 RSI 结果先经过“快”均线平滑,再经过“慢”均线二次平滑。
  3. 使用可配置的确认位移(SignalShift)检测交叉:
    • 如果上一根确认柱中快线高于慢线,而当前确认柱快线跌破慢线,则平掉空头并在允许的情况下开多。
    • 如果上一根确认柱快线低于慢线,而当前确认柱快线上穿慢线,则平掉多头并在允许的情况下开空。
  4. 持仓翻转时,先平掉原方向仓位,再使用设定的基础手数建立新方向,保持进出场对称。

默认 SignalShift = 1,意味着在新柱开启后对上一根完整 K 线产生的信号进行确认,与 Exp_CronexRSI 的行为完全一致。将该值设置为 0 时则会在交叉发生的收盘柱立即触发交易。

参数

参数 说明
RsiPeriod RSI 计算周期。
FastPeriod 第一层平滑均线长度。
SlowPeriod 第二层平滑均线长度。
SignalShift 用于确认信号的已完成柱数量(0 表示不延迟)。
SmoothingMethod 两层平滑所使用的均线类型(简单、指数、平滑、线性加权、成交量加权)。
AppliedPrice 送入 RSI 的价格类型(收盘、开盘、最高、最低、中值、典型价、加权收盘)。
CandleType 用于计算的 K 线类型。
TradeVolume 建立新仓位时使用的基础手数。
EnableLongEntry / EnableShortEntry 允许开多 / 开空。
EnableLongExit / EnableShortExit 允许在反向信号时平多 / 平空。

实现细节

  • 平滑阶段使用 StockSharp 中的标准均线类;VolumeWeighted 选项同时承担 VIDYA 与 AMA 类似的自适应平滑需求。
  • AppliedPrice 的处理逻辑与原专家顾问一致,可在不同价格类型之间自由切换。
  • 所有指标值均通过 DecimalIndicatorValue 传递,保持与 StockSharp 指标框架兼容并避免直接读取内部缓存。
  • 当确认位移参数改变时,策略会自动调整内部历史缓存,保证交叉逻辑始终依赖正确数量的历史柱。

使用方法

  1. 在 StockSharp 设计器或代码中为策略指定投资组合与交易标的。
  2. 配置时间框架、平滑方式以及多空开关,使其匹配期望的 Cronex RSI 设置。
  3. 启动策略。系统会自动订阅 K 线、更新 RSI 及平滑曲线,并在确认后的交叉点发送市价单。
  4. 借助内置图表工具,可以直观地查看指标曲线与成交记录,验证策略行为。
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 CronexRsiStrategy : 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 CronexRsiStrategy()
	{
		_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;
	}
}