""" general_analysis 공통 유틸 (슬라이스·피벗·컬럼 접두사). """ from __future__ import annotations from typing import Any import numpy as np import pandas as pd from deepcoin.analysis.general_analysis_config import GA_COL_PREFIX, LOOKBACK_BARS def ga_col(name: str) -> str: """general_analysis 출력 컬럼명.""" return f"{GA_COL_PREFIX}{name}" def interval_tf_prefix(interval: int) -> str: """간격 접두사 (m3, d1).""" from deepcoin.analysis.general_analysis_config import INTERVAL_PREFIX return INTERVAL_PREFIX.get(interval, f"m{interval}") def prefixed_snapshot( row: pd.Series, interval: int, keys: list[str] | tuple[str, ...], ) -> dict[str, Any]: """한 봉의 ga_ 컬럼을 {m3_ga_rsi: ...} 형태로 변환.""" p = interval_tf_prefix(interval) out: dict[str, Any] = {} for k in keys: col = ga_col(k) if col in row.index: v = row[col] out[f"{p}_{col}"] = None if pd.isna(v) else v return out def slice_to_timestamp(df: pd.DataFrame, ts: pd.Timestamp) -> pd.DataFrame: """타점 시각 이전 완성봉만 (해당 시각 봉 미포함).""" if df.empty: return df if not isinstance(df.index, pd.DatetimeIndex): df = df.copy() df.index = pd.to_datetime(df.index) ts = pd.Timestamp(ts) if ts.tzinfo is not None and df.index.tz is None: ts = ts.tz_localize(None) return df[df.index < ts].copy() def lookback_slice(df: pd.DataFrame, interval: int, end_ts: pd.Timestamp) -> pd.DataFrame: """타점 직전 lookback 구간.""" sliced = slice_to_timestamp(df, end_ts) n = LOOKBACK_BARS.get(interval, 80) if len(sliced) > n: return sliced.iloc[-n:].copy() return sliced def find_pivots( highs: np.ndarray, lows: np.ndarray, order: int = 3, ) -> tuple[list[int], list[int]]: """국소 고점·저점 인덱스 (양쪽 order 봉보다 극값).""" peak_idx: list[int] = [] trough_idx: list[int] = [] n = len(highs) if n < order * 2 + 1: return peak_idx, trough_idx for i in range(order, n - order): if highs[i] >= highs[i - order : i + order + 1].max(): peak_idx.append(i) if lows[i] <= lows[i - order : i + order + 1].min(): trough_idx.append(i) return peak_idx, trough_idx def last_row_dict(df: pd.DataFrame, cols: list[str]) -> dict[str, Any]: """마지막 봉의 지정 컬럼 dict.""" if df.empty: return {ga_col(c): None for c in cols} row = df.iloc[-1] return { ga_col(c): (None if c not in row.index or pd.isna(row[c]) else row[c]) for c in cols }