在 GitHub 上查看

ComFracti分形RSI策略

概述

ComFracti分形RSI策略是MetaTrader专家顾问ComFracti的StockSharp移植版本。算法通过两个时间框架的比尔·威廉姆斯分形来判断方向性偏好,并使用基于日线的快速RSI过滤信号。一旦出现有效条件,策略将开立单一头寸,使用可配置的止损/止盈距离保护仓位,并且可选地在信号反转或持仓时间超过阈值时退出。

默认设置复刻原始EA:15分钟交易周期、1小时确认周期以及在日线开盘价上计算、周期为3的RSI。

交易逻辑

  1. 分形方向判定
    • 交易周期和较高周期的完结K线都会放入一个包含5根K线的滑动窗口中。
    • Primary*ShiftHigher*Shift参数决定回溯多少根已确认的分形(默认值为3,意味着检查三根K线之前才被确认的分形)。
    • 仅出现下分形(摆动低点)视为看多(+1),仅出现上分形视为看空(-1)。
  2. 日线RSI过滤
    • 在日线周期上运行RelativeStrengthIndex指标,参数为RsiPeriod(默认3),输入值采用K线开盘价,与原始EA的PRICE_OPEN设置保持一致。
    • 做多需要RSI低于50 - RsiBuyOffset,做空需要RSI高于50 + RsiSellOffset
  3. 入场条件
    • 买入:两个时间框架的分形均给出+1,且RSI满足做多条件;若当前为空仓或空头,将买入足够数量以转为多头。
    • 卖出:两个时间框架的分形均给出-1,且RSI满足做空条件;若当前为空仓或多头,将卖出足够数量以转为空头。
  4. 仓位管理
    • 每当仓位变动时立即根据StopLossPipsTakeProfitPips乘以合约点值计算止损与止盈价位。
    • 价格触及止损/止盈、ExpiryMinutes计时器到期或CloseOnOppositeSignal启用且信号反转时,都会执行平仓。

参数

名称 说明 默认值
Volume 每次入场使用的下单数量。 0.1
TakeProfitPips 止盈距离(点)。为0时禁用止盈。 700
StopLossPips 止损距离(点)。为0时禁用止损。 2500
ExpiryMinutes 持仓最长时间(分钟)。0表示不限制。 5555
CloseOnOppositeSignal 当信号反向时是否强制平仓。 false
PrimaryBuyShift 交易周期上用于做多的分形回溯值。 3
HigherBuyShift 高周期上用于做多的分形回溯值。 3
PrimarySellShift 交易周期上用于做空的分形回溯值。 3
HigherSellShift 高周期上用于做空的分形回溯值。 3
RsiBuyOffset 做多所需的RSI低于50的偏移量。 3
RsiSellOffset 做空所需的RSI高于50的偏移量。 3
RsiPeriod 日线RSI的周期。 3
CandleType 交易周期K线类型。 15分钟K线
HigherTimeFrame 趋势确认所用的高周期K线类型。 1小时K线
DailyTimeFrame 计算RSI所用的日线K线类型。 1日K线

实现细节

  • 采用高级K线订阅API(SubscribeCandles().Bind(...)),指标在策略内部维护,不会附加到Strategy.Indicators集合。
  • 分形通过内部辅助类实现,维护滚动的5根K线并仅在分形确认后更新信号。
  • RSI通过RelativeStrengthIndex.Process(...)以开盘价更新,完全对齐MT4版本的PRICE_OPEN设置。
  • 策略始终只保持一个净头寸;若方向需要翻转,会自动下达足够的市价单以覆盖现有仓位。
  • GetPipSize根据Security.PriceStepSecurity.Decimals估算点值,对于三位或更多小数报价的品种采用10倍步长,模拟MT4中Point到点(pip)的换算。

使用建议

  • 分形回溯值必须保证足够的历史K线可用。默认值3意味着需要至少5根完结K线才能生成信号。
  • 交易不同最小报价单位的品种时(例如指数或股票),请根据实际点值调整TakeProfitPipsStopLossPips
  • 保持CloseOnOppositeSignalfalse可以完全复制原EA的行为,仅依赖止损、止盈或持仓时间退出。
  • 原版的逐笔加仓/风险计算依赖账户保证金信息,在StockSharp中不可用;若需要动态头寸管理,请结合外部资金管理模块或调整Volume参数。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// ComFracti Fractal RSI: Fractal breakout with RSI filter and ATR stops.
/// </summary>
public class ComFractiFractalRsiStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _entryPrice;
	private decimal _prevHigh5;
	private decimal _prevLow5;
	private decimal _high1, _high2, _high3, _high4, _high5;
	private decimal _low1, _low2, _low3, _low4, _low5;
	private int _barCount;

	public ComFractiFractalRsiStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

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

	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_entryPrice = 0;
		_barCount = 0;
		_prevHigh5 = 0;
		_prevLow5 = 0;
		_high1 = _high2 = _high3 = _high4 = _high5 = 0;
		_low1 = _low2 = _low3 = _low4 = _low5 = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_entryPrice = 0;
		_barCount = 0;
		_prevHigh5 = 0;
		_prevLow5 = 0;
		_high1 = _high2 = _high3 = _high4 = _high5 = 0;
		_low1 = _low2 = _low3 = _low4 = _low5 = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(rsi, atr, ProcessCandle)
			.Start();

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

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

		// Shift fractal window
		_high5 = _high4; _high4 = _high3; _high3 = _high2; _high2 = _high1;
		_high1 = candle.HighPrice;
		_low5 = _low4; _low4 = _low3; _low3 = _low2; _low2 = _low1;
		_low1 = candle.LowPrice;
		_barCount++;

		if (_barCount < 5 || atrVal <= 0)
			return;

		var close = candle.ClosePrice;

		// Detect fractal high (center bar _high3 is highest)
		var fractalUp = _high3 > _high1 && _high3 > _high2 && _high3 > _high4 && _high3 > _high5;
		// Detect fractal low (center bar _low3 is lowest)
		var fractalDown = _low3 < _low1 && _low3 < _low2 && _low3 < _low4 && _low3 < _low5;

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 2m || fractalDown)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 2m || fractalUp)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (fractalDown && rsiVal < 45)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (fractalUp && rsiVal > 55)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevHigh5 = _high5;
		_prevLow5 = _low5;
	}
}