在 GitHub 上查看
IFS Fractals
概述
IFS Fractals 是 MetaTrader 5 脚本 IFS_Fractals 的移植版本。原始脚本通过对点云连续应用 28 组仿射变换来绘制“分形单词”的位图。StockSharp 版本沿用了相同的迭代函数系统 (IFS),但将其转换为交易信号:生成点的 X 坐标按原始比例缩放、再用指数移动平均线 (EMA) 平滑,并作为动量指标来驱动多空交易。
策略逻辑
迭代函数系统
- 仿射变换 – 每根完成的 K 线都会触发一批迭代(可配置数量)。在每次迭代中,依据原脚本提供的概率权重(全部为 35)随机选择 28 个变换中的一个,并使用与 MQL5 代码一致的系数更新当前点
(x, y)。
- 概率表 – 启动时预先计算概率累积表,可通过一次随机抽样在总概率质量内快速定位下一个变换。
信号构建
- 归一化 – X 坐标除以原脚本绘制位图时使用的缩放系数(默认
50),从而保持信号幅度与标的价格无关。
- EMA 平滑 – 归一化后的序列送入可配置周期的 EMA。该指标相当于低通滤波器,用于提取混沌迭代的主导趋势。
- 入场规则 – 当 EMA 上穿正向入场阈值时,策略开多或反手做多;当 EMA 下穿负向阈值时,则开空或反手做空。
- 出场规则 – 多单在 EMA 回落到出场阈值或更低时平仓;空单在 EMA 回升到负向出场阈值以上时平仓。这样形成的滞回区可以减少信号在零轴附近的频繁翻转。
风险控制
- 仓位保护 – 通过
StartProtection 可设置绝对止盈/止损距离,参数为 0 表示关闭,对应原脚本默认不使用保护单的行为。
- 仓位规模 – 开仓始终使用固定的市场成交量;在建立新仓前会先平掉相反方向的持仓,确保策略始终只有一个方向的敞口。
参数
- Volume – 新开仓时使用的市场成交量。
- Candle Type – 触发 IFS 迭代的 K 线类型(默认 5 分钟)。
- Iterations – 每根完成 K 线后执行的迭代次数。
- Scale – 在送入 EMA 之前对 X 坐标应用的缩放因子。
- Entry Threshold – 开仓所需的 EMA 绝对值(正值对应多单,负值镜像应用于空单)。
- Exit Threshold – EMA 回到该阈值时触发平仓。
- EMA Period – 应用于分形信号的指数移动平均周期。
- Take Profit – 绝对止盈距离;填
0 关闭。
- Stop Loss – 绝对止损距离;填
0 关闭。
其他说明
- 若不在代码中设定固定随机种子,每次运行都会得到不同的交易序列,这与原脚本绘制位图时的随机性一致。
- 策略无需依赖市场指标,所有信号完全由 IFS 系数生成,订阅的 K 线只负责提供节奏。
- 本包仅包含 C# 实现(位于
CS/ 目录),暂未提供 Python 版本。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// IFS Fractals strategy: Williams %R oscillator crossover.
/// Buys when WPR crosses above oversold, sells when crosses below overbought.
/// </summary>
public class IfsFractalsStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _wprPeriod;
private readonly StrategyParam<decimal> _oversold;
private readonly StrategyParam<decimal> _overbought;
private readonly StrategyParam<int> _signalCooldownCandles;
private decimal _prevWpr;
private int _candlesSinceTrade;
private bool _hasPrev;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int WprPeriod { get => _wprPeriod.Value; set => _wprPeriod.Value = value; }
public decimal Oversold { get => _oversold.Value; set => _oversold.Value = value; }
public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public IfsFractalsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_wprPeriod = Param(nameof(WprPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("WPR Period", "Williams %R period", "Indicators");
_oversold = Param(nameof(Oversold), -85m)
.SetDisplay("Oversold", "WPR oversold level", "Signals");
_overbought = Param(nameof(Overbought), -15m)
.SetDisplay("Overbought", "WPR overbought level", "Signals");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevWpr = 0;
_candlesSinceTrade = SignalCooldownCandles;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevWpr = 0;
_candlesSinceTrade = SignalCooldownCandles;
_hasPrev = false;
var wpr = new WilliamsR { Length = WprPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(wpr, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal wprValue)
{
if (candle.State != CandleStates.Finished) return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
if (_hasPrev)
{
if (_prevWpr < Oversold && wprValue >= Oversold && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (_prevWpr > Overbought && wprValue <= Overbought && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
_prevWpr = wprValue;
_hasPrev = true;
}
}
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.Indicators import WilliamsR
from StockSharp.Algo.Strategies import Strategy
class ifs_fractals_strategy(Strategy):
def __init__(self):
super(ifs_fractals_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._wpr_period = self.Param("WprPeriod", 14)
self._oversold = self.Param("Oversold", -85.0)
self._overbought = self.Param("Overbought", -15.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 4)
self._prev_wpr = 0.0
self._candles_since_trade = 4
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def WprPeriod(self):
return self._wpr_period.Value
@WprPeriod.setter
def WprPeriod(self, value):
self._wpr_period.Value = value
@property
def Oversold(self):
return self._oversold.Value
@Oversold.setter
def Oversold(self, value):
self._oversold.Value = value
@property
def Overbought(self):
return self._overbought.Value
@Overbought.setter
def Overbought(self, value):
self._overbought.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(ifs_fractals_strategy, self).OnReseted()
self._prev_wpr = 0.0
self._candles_since_trade = self.SignalCooldownCandles
self._has_prev = False
def OnStarted2(self, time):
super(ifs_fractals_strategy, self).OnStarted2(time)
self._prev_wpr = 0.0
self._candles_since_trade = self.SignalCooldownCandles
self._has_prev = False
wpr = WilliamsR()
wpr.Length = self.WprPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(wpr, self._process_candle).Start()
def _process_candle(self, candle, wpr_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
wpr_val = float(wpr_value)
if self._has_prev:
if self._prev_wpr < self.Oversold and wpr_val >= self.Oversold and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif self._prev_wpr > self.Overbought and wpr_val <= self.Overbought and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
self._prev_wpr = wpr_val
self._has_prev = True
def CreateClone(self):
return ifs_fractals_strategy()