在 GitHub 上查看
ComFracti分形RSI策略
概述
ComFracti分形RSI策略是MetaTrader专家顾问ComFracti的StockSharp移植版本。算法通过两个时间框架的比尔·威廉姆斯分形来判断方向性偏好,并使用基于日线的快速RSI过滤信号。一旦出现有效条件,策略将开立单一头寸,使用可配置的止损/止盈距离保护仓位,并且可选地在信号反转或持仓时间超过阈值时退出。
默认设置复刻原始EA:15分钟交易周期、1小时确认周期以及在日线开盘价上计算、周期为3的RSI。
交易逻辑
- 分形方向判定
- 交易周期和较高周期的完结K线都会放入一个包含5根K线的滑动窗口中。
Primary*Shift与Higher*Shift参数决定回溯多少根已确认的分形(默认值为3,意味着检查三根K线之前才被确认的分形)。
- 仅出现下分形(摆动低点)视为看多(+1),仅出现上分形视为看空(-1)。
- 日线RSI过滤
- 在日线周期上运行
RelativeStrengthIndex指标,参数为RsiPeriod(默认3),输入值采用K线开盘价,与原始EA的PRICE_OPEN设置保持一致。
- 做多需要RSI低于
50 - RsiBuyOffset,做空需要RSI高于50 + RsiSellOffset。
- 入场条件
- 买入:两个时间框架的分形均给出+1,且RSI满足做多条件;若当前为空仓或空头,将买入足够数量以转为多头。
- 卖出:两个时间框架的分形均给出-1,且RSI满足做空条件;若当前为空仓或多头,将卖出足够数量以转为空头。
- 仓位管理
- 每当仓位变动时立即根据
StopLossPips与TakeProfitPips乘以合约点值计算止损与止盈价位。
- 价格触及止损/止盈、
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.PriceStep及Security.Decimals估算点值,对于三位或更多小数报价的品种采用10倍步长,模拟MT4中Point到点(pip)的换算。
使用建议
- 分形回溯值必须保证足够的历史K线可用。默认值3意味着需要至少5根完结K线才能生成信号。
- 交易不同最小报价单位的品种时(例如指数或股票),请根据实际点值调整
TakeProfitPips和StopLossPips。
- 保持
CloseOnOppositeSignal为false可以完全复制原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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import RelativeStrengthIndex, AverageTrueRange
class com_fracti_fractal_rsi_strategy(Strategy):
def __init__(self):
super(com_fracti_fractal_rsi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._entry_price = 0.0
self._prev_high5 = 0.0
self._prev_low5 = 0.0
self._high1 = 0.0
self._high2 = 0.0
self._high3 = 0.0
self._high4 = 0.0
self._high5 = 0.0
self._low1 = 0.0
self._low2 = 0.0
self._low3 = 0.0
self._low4 = 0.0
self._low5 = 0.0
self._bar_count = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(com_fracti_fractal_rsi_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._bar_count = 0
self._prev_high5 = 0.0
self._prev_low5 = 0.0
self._high1 = self._high2 = self._high3 = self._high4 = self._high5 = 0.0
self._low1 = self._low2 = self._low3 = self._low4 = self._low5 = 0.0
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_val)
av = float(atr_val)
# Shift fractal window
self._high5 = self._high4
self._high4 = self._high3
self._high3 = self._high2
self._high2 = self._high1
self._high1 = float(candle.HighPrice)
self._low5 = self._low4
self._low4 = self._low3
self._low3 = self._low2
self._low2 = self._low1
self._low1 = float(candle.LowPrice)
self._bar_count += 1
if self._bar_count < 5 or av <= 0:
return
close = float(candle.ClosePrice)
# Detect fractal high (center bar _high3 is highest)
fractal_up = (self._high3 > self._high1 and self._high3 > self._high2 and
self._high3 > self._high4 and self._high3 > self._high5)
# Detect fractal low (center bar _low3 is lowest)
fractal_down = (self._low3 < self._low1 and self._low3 < self._low2 and
self._low3 < self._low4 and self._low3 < self._low5)
if self.Position > 0:
if close >= self._entry_price + av * 3.0 or close <= self._entry_price - av * 2.0 or fractal_down:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 3.0 or close >= self._entry_price + av * 2.0 or fractal_up:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_high5 = self._high5
self._prev_low5 = self._low5
return
if self.Position == 0:
if fractal_down and rv < 45:
self._entry_price = close
self.BuyMarket()
elif fractal_up and rv > 55:
self._entry_price = close
self.SellMarket()
self._prev_high5 = self._high5
self._prev_low5 = self._low5
def OnReseted(self):
super(com_fracti_fractal_rsi_strategy, self).OnReseted()
self._entry_price = 0.0
self._bar_count = 0
self._prev_high5 = 0.0
self._prev_low5 = 0.0
self._high1 = self._high2 = self._high3 = self._high4 = self._high5 = 0.0
self._low1 = self._low2 = self._low3 = self._low4 = self._low5 = 0.0
def CreateClone(self):
return com_fracti_fractal_rsi_strategy()