Skip to content

FracDiff (Fractional Differentiation)

Intuição

Séries de preços costumam ser não-estacionárias (tendem a ter raiz unitária), o que dificulta o uso direto em muitos modelos estatísticos e de machine learning.

A diferenciação inteira (d = 1) ajuda a tornar a série mais estacionária, mas pode remover informação de longo prazo. A diferenciação fracionária busca um meio-termo: reduzir a não-estacionariedade preservando mais memory do sinal.

Definição

A diferenciação fracionária de ordem d pode ser escrita como a expansão de:

  • (1 - L)^d x_t = sum_{k=0..∞} w_k x_{t-k}

onde L é o operador de defasagem e os pesos são dados recursivamente por:

  • w_0 = 1
  • w_k = -w_{k-1} * (d - k + 1) / k

Na prática, truncamos a soma quando |w_k| < thresh (ou usando max_lags).

Uso

from quantmaster.features.statistical import fracdiff

df["fracdiff_0.5"] = fracdiff(df, d=0.5, thresh=1e-5)

API

Source code in src/quantmaster/features/statistical.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def fracdiff(
    data: pd.DataFrame | pd.Series,
    *,
    d: float = 0.4,
    thresh: float = 1e-5,
    max_lags: int | None = None,
    price_col: str = "close",
) -> pd.Series:
    try:
        d = float(d)
    except (TypeError, ValueError) as exc:
        raise TypeError(f"d must be float, got {type(d).__name__}") from exc

    if not (0.0 <= d <= 1.0):
        raise ValueError(f"d must be between 0 and 1 (inclusive), got {d}")

    try:
        thresh = float(thresh)
    except (TypeError, ValueError) as exc:
        raise TypeError(f"thresh must be float, got {type(thresh).__name__}") from exc

    if thresh <= 0:
        raise ValueError(f"thresh must be > 0, got {thresh}")

    if max_lags is not None:
        max_lags = validate_positive_int(max_lags, name="max_lags")

    x = get_price_series(data, price_col=price_col).astype(float)

    out_name = f"fracdiff_{d:g}"

    if len(x) < 2:
        out = pd.Series(np.nan, index=x.index, name=out_name, dtype=float)
        if d == 0.0:
            out = x.astype(float).copy()
            out.name = out_name
        return out

    x_arr = x.to_numpy(dtype=float)
    finite = np.isfinite(x_arr)

    y = np.full(x_arr.shape[0], np.nan, dtype=float)

    if finite.any():
        idxs = np.flatnonzero(finite)
        start = int(idxs[0])
        prev = int(idxs[0])
        for i in idxs[1:]:
            i = int(i)
            if i != prev + 1:
                _apply_fracdiff_segment(y, x_arr, start=start, end=prev + 1, d=d, thresh=thresh, max_lags=max_lags)
                start = i
            prev = i
        _apply_fracdiff_segment(y, x_arr, start=start, end=prev + 1, d=d, thresh=thresh, max_lags=max_lags)

    out = pd.Series(y, index=x.index)
    out.name = out_name
    return out