"""Indicatori tecnici condivisi tra tutte le strategie."""
from __future__ import annotations

import numpy as np


def keltner_ratio(close: np.ndarray, high: np.ndarray, low: np.ndarray,
                  window: int = 14) -> np.ndarray:
    """Rapporto Bollinger / Keltner. Sotto 1 = squeeze (BB dentro KC)."""
    n = len(close)
    r = np.full(n, np.nan)
    for i in range(window, n):
        wc = close[i - window:i]
        wh = high[i - window:i]
        wl = low[i - window:i]
        ma = np.mean(wc)
        bb_std = np.std(wc)
        tr = np.maximum(
            wh - wl,
            np.maximum(np.abs(wh - np.roll(wc, 1)), np.abs(wl - np.roll(wc, 1))),
        )
        atr = np.mean(tr[1:])
        kc = (ma + 1.5 * atr) - (ma - 1.5 * atr)
        bb = (ma + 2 * bb_std) - (ma - 2 * bb_std)
        if kc > 0:
            r[i] = bb / kc
    return r


def detect_squeezes(close: np.ndarray, high: np.ndarray, low: np.ndarray,
                    kcr: np.ndarray, sq_thr: float = 0.8,
                    min_dur: int = 5) -> list[dict]:
    """Rileva squeeze events: periodi dove BB sta dentro KC."""
    events: list[dict] = []
    in_sq = False
    sq_start = 0
    for i in range(1, len(close)):
        if np.isnan(kcr[i]):
            continue
        is_sq = kcr[i] < sq_thr
        if is_sq and not in_sq:
            in_sq = True
            sq_start = i
        elif not is_sq and in_sq:
            in_sq = False
            dur = i - sq_start
            if dur < min_dur:
                continue
            events.append({
                "idx": i, "dur": dur, "sq_start": sq_start,
                "kcr_at_release": kcr[i],
            })
    return events


def ema(arr: np.ndarray, period: int) -> np.ndarray:
    """Exponential Moving Average."""
    r = np.full(len(arr), np.nan)
    k = 2 / (period + 1)
    r[period - 1] = np.mean(arr[:period])
    for i in range(period, len(arr)):
        r[i] = arr[i] * k + r[i - 1] * (1 - k)
    return r


def atr(high: np.ndarray, low: np.ndarray, close: np.ndarray,
        period: int = 14) -> np.ndarray:
    """Average True Range (EMA-smoothed)."""
    tr = np.maximum(
        high - low,
        np.maximum(np.abs(high - np.roll(close, 1)), np.abs(low - np.roll(close, 1))),
    )
    tr[0] = high[0] - low[0]
    r = np.full(len(close), np.nan)
    r[period - 1] = np.mean(tr[:period])
    k = 2 / (period + 1)
    for i in range(period, len(close)):
        r[i] = tr[i] * k + r[i - 1] * (1 - k)
    return r


def rv_annualized(close: np.ndarray, window: int) -> np.ndarray:
    """Realized volatility annualizzata (hourly data assumed)."""
    lr = np.diff(np.log(np.where(close == 0, 1e-10, close)))
    r = np.full(len(close), np.nan)
    for i in range(window, len(lr)):
        r[i + 1] = np.std(lr[i - window:i]) * np.sqrt(24 * 365)
    return r


def rolling_correlation(close_a: np.ndarray, close_b: np.ndarray,
                        window: int = 48) -> np.ndarray:
    """Correlazione rolling tra rendimenti logaritmici di due asset."""
    n = max(len(close_a), len(close_b))
    ret_a = np.diff(np.log(np.where(close_a == 0, 1e-10, close_a)))
    ret_b = np.diff(np.log(np.where(close_b[:len(close_a)] == 0, 1e-10, close_b[:len(close_a)])))
    min_len = min(len(ret_a), len(ret_b))
    corr = np.full(n, np.nan)
    for i in range(window, min_len):
        cv = np.corrcoef(ret_a[i - window:i], ret_b[i - window:i])[0, 1]
        corr[i + 1] = cv if np.isfinite(cv) else 0
    return corr
