"""
Модуль признаков на основе цен OHLCV.

Функции:
    add_price_features — добавление признаков на основе цены (доходности, отношения OHLC, лог-доходности)
    add_returns — доходности за различные периоды
    add_log_returns — логарифмические доходности
    add_ohlc_ratios — отношения OHLC (high/low, close/open, тени)
    add_price_position — позиция цены в диапазоне свечи
"""

from typing import Optional

import numpy as np
import pandas as pd


def add_returns(
    df: pd.DataFrame,
    horizons: Optional[list[int]] = None,
) -> pd.DataFrame:
    """
    Добавить признаки доходности за различные периоды.

    Args:
        df: DataFrame с колонкой 'Close'.
        horizons: список периодов для расчёта доходности (по умолчанию [1, 5, 10, 21]).

    Returns:
        DataFrame с добавленными колонками 'ret_{N}'.
    """
    if horizons is None:
        horizons = [1, 5, 10, 21]

    df = df.copy()
    for h in horizons:
        df[f'ret_{h}'] = df['Close'].pct_change(h)
    return df


def add_log_returns(
    df: pd.DataFrame,
    horizon: int = 1,
) -> pd.DataFrame:
    """
    Добавить логарифмические доходности (более нормальное распределение).

    Args:
        df: DataFrame с колонкой 'Close'.
        horizon: период для лог-доходности (по умолчанию 1).

    Returns:
        DataFrame с колонкой 'log_ret_{horizon}'.
    """
    df = df.copy()
    df[f'log_ret_{horizon}'] = np.log(df['Close'] / df['Close'].shift(horizon))
    return df


def add_ohlc_ratios(
    df: pd.DataFrame,
) -> pd.DataFrame:
    """
    Добавить отношения OHLC-цен.

    Рассчитываются:
        - high_low_ratio: отношение High к Low
        - close_open_ratio: отношение Close к Open
        - upper_shadow: относительный размер верхней тени
        - lower_shadow: относительный размер нижней тени
        - spread: спред (High - Low) / Open

    Args:
        df: DataFrame с колонками Open, High, Low, Close.

    Returns:
        DataFrame с добавленными колонками отношений.
    """
    df = df.copy()
    eps = 1e-8  # защита от деления на ноль

    df['high_low_ratio'] = df['High'] / (df['Low'] + eps)
    df['close_open_ratio'] = df['Close'] / (df['Open'] + eps)
    df['upper_shadow'] = (df['High'] - df[['Open', 'Close']].max(axis=1)) / (df['High'] - df['Low'] + eps)
    df['lower_shadow'] = (df[['Open', 'Close']].min(axis=1) - df['Low']) / (df['High'] - df['Low'] + eps)
    df['spread_pct'] = (df['High'] - df['Low']) / (df['Open'] + eps)
    return df


def add_price_position(
    df: pd.DataFrame,
) -> pd.DataFrame:
    """
    Добавить позицию цены закрытия относительно диапазона свечи.

    close_position = (Close - Low) / (High - Low)
    Значение 0.0 = Close на минимуме, 1.0 = Close на максимуме.

    Args:
        df: DataFrame с колонками High, Low, Close.

    Returns:
        DataFrame с колонкой 'close_position'.
    """
    df = df.copy()
    eps = 1e-8
    df['close_position'] = (df['Close'] - df['Low']) / (df['High'] - df['Low'] + eps)
    return df


def add_price_features(
    df: pd.DataFrame,
    return_horizons: Optional[list[int]] = None,
) -> pd.DataFrame:
    """
    Добавить все признаки на основе цены.

    Включает:
        - Доходности за периоды [1, 5, 10, 21]
        - Лог-доходность за 1 период
        - OHLC отношения (high_low_ratio, close_open_ratio, тени, спред)
        - Позицию цены в свече

    Args:
        df: DataFrame с колонками Open, High, Low, Close.
        return_horizons: периоды для доходности (по умолчанию [1, 5, 10, 21]).

    Returns:
        DataFrame со всеми price-признаками.
    """
    df = add_returns(df, horizons=return_horizons)
    df = add_log_returns(df, horizon=1)
    df = add_ohlc_ratios(df)
    df = add_price_position(df)
    return df
