feat: strategie 1-10, framework analisi frattale, download dati storici BTC/ETH
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
"""Fractal indicators: Hurst exponent, fractal dimension, self-similarity."""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
from scipy.stats import linregress
|
||||
|
||||
|
||||
def hurst_exponent(series: np.ndarray, max_lag: int | None = None) -> float:
|
||||
"""Compute Hurst exponent via R/S analysis.
|
||||
H > 0.5: trending (persistent), H < 0.5: mean-reverting, H ≈ 0.5: random walk.
|
||||
"""
|
||||
n = len(series)
|
||||
if n < 20:
|
||||
return 0.5
|
||||
|
||||
if max_lag is None:
|
||||
max_lag = min(n // 4, 100)
|
||||
|
||||
lags = range(10, max_lag + 1)
|
||||
rs_values = []
|
||||
lag_values = []
|
||||
|
||||
for lag in lags:
|
||||
rs_list = []
|
||||
for start in range(0, n - lag, lag):
|
||||
chunk = series[start : start + lag]
|
||||
if len(chunk) < lag:
|
||||
continue
|
||||
mean = np.mean(chunk)
|
||||
deviations = np.cumsum(chunk - mean)
|
||||
r = np.max(deviations) - np.min(deviations)
|
||||
s = np.std(chunk, ddof=1)
|
||||
if s > 0:
|
||||
rs_list.append(r / s)
|
||||
|
||||
if rs_list:
|
||||
rs_values.append(np.mean(rs_list))
|
||||
lag_values.append(lag)
|
||||
|
||||
if len(lag_values) < 3:
|
||||
return 0.5
|
||||
|
||||
log_lags = np.log(lag_values)
|
||||
log_rs = np.log(rs_values)
|
||||
slope, _, _, _, _ = linregress(log_lags, log_rs)
|
||||
return float(np.clip(slope, 0, 1))
|
||||
|
||||
|
||||
def rolling_hurst(close: np.ndarray, window: int = 100, step: int = 1) -> np.ndarray:
|
||||
"""Compute rolling Hurst exponent."""
|
||||
n = len(close)
|
||||
result = np.full(n, 0.5)
|
||||
returns = np.diff(np.log(np.where(close == 0, 1e-10, close)))
|
||||
|
||||
for i in range(window, n, step):
|
||||
h = hurst_exponent(returns[i - window : i])
|
||||
result[i] = h
|
||||
for j in range(1, min(step, n - i)):
|
||||
result[i + j] = h
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def fractal_dimension_higuchi(series: np.ndarray, k_max: int = 10) -> float:
|
||||
"""Higuchi fractal dimension of a time series."""
|
||||
n = len(series)
|
||||
if n < k_max * 2:
|
||||
return 1.5
|
||||
|
||||
lk = []
|
||||
x = np.arange(1, k_max + 1)
|
||||
|
||||
for k in range(1, k_max + 1):
|
||||
lm_list = []
|
||||
for m in range(1, k + 1):
|
||||
indices = np.arange(m - 1, n, k)
|
||||
if len(indices) < 2:
|
||||
continue
|
||||
vals = series[indices]
|
||||
length = np.sum(np.abs(np.diff(vals)))
|
||||
norm = (n - 1) / (k * ((n - m) // k) * k)
|
||||
lm_list.append(length * norm)
|
||||
|
||||
if lm_list:
|
||||
lk.append(np.mean(lm_list))
|
||||
|
||||
if len(lk) < 3:
|
||||
return 1.5
|
||||
|
||||
log_k = np.log(1.0 / x[: len(lk)])
|
||||
log_lk = np.log(np.array(lk))
|
||||
slope, _, _, _, _ = linregress(log_k, log_lk)
|
||||
return float(np.clip(slope, 1.0, 2.0))
|
||||
|
||||
|
||||
def self_similarity_score(close: np.ndarray, window: int, scales: list[int] | None = None) -> float:
|
||||
"""Measure self-similarity across multiple time scales.
|
||||
Higher score = more fractal (self-similar) structure.
|
||||
"""
|
||||
if scales is None:
|
||||
scales = [2, 3, 4, 6]
|
||||
|
||||
if len(close) < window:
|
||||
return 0.0
|
||||
|
||||
base = close[-window:]
|
||||
base_returns = np.diff(np.log(np.where(base == 0, 1e-10, base)))
|
||||
if np.std(base_returns) == 0:
|
||||
return 0.0
|
||||
|
||||
similarities = []
|
||||
for scale in scales:
|
||||
scaled_window = window * scale
|
||||
if scaled_window > len(close):
|
||||
continue
|
||||
|
||||
scaled = close[-scaled_window:]
|
||||
step = scale
|
||||
downsampled = scaled[::step][:window]
|
||||
|
||||
if len(downsampled) != len(base):
|
||||
downsampled = np.interp(
|
||||
np.linspace(0, 1, window),
|
||||
np.linspace(0, 1, len(downsampled)),
|
||||
downsampled,
|
||||
)
|
||||
|
||||
ds_returns = np.diff(np.log(np.where(downsampled == 0, 1e-10, downsampled)))
|
||||
|
||||
if len(ds_returns) != len(base_returns):
|
||||
ds_returns = np.interp(
|
||||
np.linspace(0, 1, len(base_returns)),
|
||||
np.linspace(0, 1, len(ds_returns)),
|
||||
ds_returns,
|
||||
)
|
||||
|
||||
std_ds = np.std(ds_returns)
|
||||
if std_ds == 0:
|
||||
continue
|
||||
|
||||
corr = np.corrcoef(base_returns, ds_returns)[0, 1]
|
||||
if np.isfinite(corr):
|
||||
similarities.append(abs(corr))
|
||||
|
||||
if not similarities:
|
||||
return 0.0
|
||||
return float(np.mean(similarities))
|
||||
|
||||
|
||||
def volatility_ratio(close: np.ndarray, fast: int = 12, slow: int = 48) -> float:
|
||||
"""Ratio of short-term to long-term volatility."""
|
||||
returns = np.diff(np.log(np.where(close == 0, 1e-10, close)))
|
||||
if len(returns) < slow:
|
||||
return 1.0
|
||||
fast_vol = np.std(returns[-fast:])
|
||||
slow_vol = np.std(returns[-slow:])
|
||||
if slow_vol == 0:
|
||||
return 1.0
|
||||
return float(fast_vol / slow_vol)
|
||||
Reference in New Issue
Block a user