在 GitHub 上查看

Fractal RSI 策略

基于 Fractal RSI 指标的自适应策略。 该指标根据价格运动的分形维度调整 RSI 的周期, 使振荡器在趋势市场中更快,在盘整市场中更慢反应。

当指标穿越预设水平时策略开仓, 可选择顺势或逆势交易。

细节

  • 入场条件:
    • 趋势模式:
      • 做多:值下穿 LowLevel
      • 做空:值上穿 HighLevel
    • 逆势模式:
      • 做多:值上穿 HighLevel
      • 做空:值下穿 LowLevel
  • 多空: 双向
  • 出场条件: 反向信号
  • 止损: 可选的固定止损和止盈
  • 默认值:
    • CandleType = TimeSpan.FromHours(4).TimeFrame()
    • FractalPeriod = 30
    • NormalSpeed = 30
    • HighLevel = 60
    • LowLevel = 40
    • StopLoss = 1000 点
    • TakeProfit = 2000 点
  • 过滤器:
    • 类别: 趋势 / 振荡指标
      • 方向: 双向
    • 指标: Fractal Dimension, RSI
    • 止损: 有
    • 复杂度: 高级指标
    • 时间框架: 4H (可配置)
    • 季节性: 无
    • 神经网络: 无
    • 背离: 无
    • 风险级别: 中等
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that trades using adaptive Fractal RSI indicator computed inline.
/// </summary>
public class FractalRsiStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fractalPeriod;
	private readonly StrategyParam<int> _normalSpeed;
	private readonly StrategyParam<decimal> _highLevel;
	private readonly StrategyParam<decimal> _lowLevel;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;

	private readonly List<decimal> _prices = new();
	private decimal? _previousValue;
	private int _lastSignal;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FractalPeriod { get => _fractalPeriod.Value; set => _fractalPeriod.Value = value; }
	public int NormalSpeed { get => _normalSpeed.Value; set => _normalSpeed.Value = value; }
	public decimal HighLevel { get => _highLevel.Value; set => _highLevel.Value = value; }
	public decimal LowLevel { get => _lowLevel.Value; set => _lowLevel.Value = value; }
	public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
	public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }

	public FractalRsiStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for indicator", "General");

		_fractalPeriod = Param(nameof(FractalPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Fractal Period", "Period for fractal dimension", "Indicator");

		_normalSpeed = Param(nameof(NormalSpeed), 50)
			.SetGreaterThanZero()
			.SetDisplay("Normal Speed", "Base period for RSI", "Indicator");

		_highLevel = Param(nameof(HighLevel), 70m)
			.SetDisplay("High Level", "Upper threshold", "Indicator");

		_lowLevel = Param(nameof(LowLevel), 30m)
			.SetDisplay("Low Level", "Lower threshold", "Indicator");

		_stopLoss = Param(nameof(StopLoss), 1000m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");

		_takeProfit = Param(nameof(TakeProfit), 2000m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit", "Take profit in price units", "Risk");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prices.Clear();
		_previousValue = null;
		_lastSignal = 0;
	}

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

		SubscribeCandles(CandleType)
			.Bind(ProcessCandle)
			.Start();

		StartProtection(
			new Unit(TakeProfit, UnitTypes.Absolute),
			new Unit(StopLoss, UnitTypes.Absolute));
	}

	private decimal? ComputeFractalRsi()
	{
		var period = FractalPeriod;
		if (_prices.Count < period + 1)
			return null;

		var lastIndex = _prices.Count - 1;
		var startIndex = lastIndex - period + 1;

		var priceMax = _prices[startIndex];
		var priceMin = _prices[startIndex];
		for (var i = startIndex; i <= lastIndex; i++)
		{
			if (_prices[i] > priceMax) priceMax = _prices[i];
			if (_prices[i] < priceMin) priceMin = _prices[i];
		}

		double length = 0.0;
		double? priorDiff = null;

		if (priceMax - priceMin > 0m)
		{
			for (var k = 0; k < period; k++)
			{
				var p = (double)((_prices[lastIndex - k] - priceMin) / (priceMax - priceMin));
				if (priorDiff != null)
					length += Math.Sqrt(Math.Pow(p - priorDiff.Value, 2.0) + 1.0 / (period * period));
				priorDiff = p;
			}
		}

		var log2 = Math.Log(2.0);
		double fdi = length > 0.0 ? 1.0 + (Math.Log(length) + log2) / Math.Log(2.0 * (period - 1)) : 0.0;
		double hurst = 2.0 - fdi;
		double trailDim = hurst != 0.0 ? 1.0 / hurst : 0.0;
		var speed = (int)Math.Max(1, Math.Round(NormalSpeed * trailDim / 2.0));

		if (_prices.Count <= speed)
			return null;

		decimal sumUp = 0m;
		decimal sumDown = 0m;
		for (var i = lastIndex - speed + 1; i <= lastIndex; i++)
		{
			var diff = _prices[i] - _prices[i - 1];
			if (diff > 0) sumUp += diff;
			else sumDown -= diff;
		}

		var pos = sumUp / speed;
		var neg = sumDown / speed;

		if (neg > 0) return 100m - (100m / (1m + pos / neg));
		return pos > 0 ? 100m : 50m;
	}

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

		_prices.Add(candle.ClosePrice);
		if (_prices.Count > 500)
			_prices.RemoveAt(0);

		var value = ComputeFractalRsi();
		if (value == null)
			return;

		var prev = _previousValue;
		_previousValue = value;

		if (prev is null)
			return;

		// Direct mode: buy on oversold cross down, sell on overbought cross up
		if (prev > LowLevel && value <= LowLevel && _lastSignal != 1 && Position <= 0)
		{
			BuyMarket();
			_lastSignal = 1;
		}
		else if (prev < HighLevel && value >= HighLevel && _lastSignal != -1 && Position >= 0)
		{
			SellMarket();
			_lastSignal = -1;
		}
		else if (value > LowLevel && value < HighLevel)
			_lastSignal = 0;
	}
}