""" general_analysis 캔들·차트 변환 (Heikin-Ashi, 복수봉 패턴). """ from __future__ import annotations import numpy as np import pandas as pd from deepcoin.analysis.general_analysis_core import ga_col def general_analysis_apply_candles(df: pd.DataFrame) -> pd.DataFrame: """ 단일·복수 봉 캔들 패턴 및 Heikin-Ashi 컬럼 추가. Args: df: OHLCV. Returns: ga_* 캔들 컬럼이 추가된 DataFrame. """ out = df.copy() o = out["Open"].astype(float) h = out["High"].astype(float) l = out["Low"].astype(float) c = out["Close"].astype(float) rng = (h - l).replace(0, np.nan) body = (c - o).abs() out[ga_col("body_ratio")] = (body / rng).fillna(0).clip(0, 1) upper_wick = h - np.maximum(o, c) lower_wick = np.minimum(o, c) - l out[ga_col("upper_wick_ratio")] = (upper_wick / rng).fillna(0).clip(0, 1) out[ga_col("lower_wick_ratio")] = (lower_wick / rng).fillna(0).clip(0, 1) out[ga_col("bullish")] = (c > o).astype(int) out[ga_col("bearish")] = (c < o).astype(int) out[ga_col("hammer")] = ( (out[ga_col("lower_wick_ratio")] > 0.45) & (out[ga_col("body_ratio")] < 0.35) ).astype(int) out[ga_col("shooting_star")] = ( (out[ga_col("upper_wick_ratio")] > 0.45) & (out[ga_col("body_ratio")] < 0.35) ).astype(int) out[ga_col("doji")] = (out[ga_col("body_ratio")] < 0.1).astype(int) prev_o, prev_c = o.shift(1), c.shift(1) out[ga_col("bullish_engulfing")] = ( (c > o) & (prev_c < prev_o) & (c >= prev_o) & (o <= prev_c) ).astype(int) out[ga_col("bearish_engulfing")] = ( (c < o) & (prev_c > prev_o) & (c <= prev_o) & (o >= prev_c) ).astype(int) o2, c2 = o.shift(2), c.shift(2) mid1 = (o.shift(1) + c.shift(1)) / 2 out[ga_col("morning_star")] = ( (c2 < o2) & (abs(c.shift(1) - o.shift(1)) < rng.shift(1) * 0.15) & (c > o) & (c > mid1) ).astype(int) out[ga_col("evening_star")] = ( (c2 > o2) & (abs(c.shift(1) - o.shift(1)) < rng.shift(1) * 0.15) & (c < o) & (c < mid1) ).astype(int) out[ga_col("three_white_soldiers")] = ( (c > o) & (c.shift(1) > o.shift(1)) & (c.shift(2) > o.shift(2)) & (c > c.shift(1)) & (c.shift(1) > c.shift(2)) ).astype(int) out[ga_col("three_black_crows")] = ( (c < o) & (c.shift(1) < o.shift(1)) & (c.shift(2) < o.shift(2)) & (c < c.shift(1)) & (c.shift(1) < c.shift(2)) ).astype(int) # Heikin-Ashi ha_close = (o + h + l + c) / 4 ha_open = ha_close.copy() ha_open.iloc[0] = (o.iloc[0] + c.iloc[0]) / 2 for i in range(1, len(out)): ha_open.iloc[i] = (ha_open.iloc[i - 1] + ha_close.iloc[i - 1]) / 2 out[ga_col("ha_close")] = ha_close out[ga_col("ha_open")] = ha_open out[ga_col("ha_bull")] = (ha_close > ha_open).astype(int) out[ga_col("ha_trend_up")] = ( (ha_close > ha_close.shift(1)) & (ha_close.shift(1) > ha_close.shift(2)) ).astype(int) return out def general_analysis_candle_columns() -> list[str]: return [ "body_ratio", "hammer", "shooting_star", "doji", "bullish_engulfing", "bearish_engulfing", "morning_star", "evening_star", "three_white_soldiers", "three_black_crows", "ha_bull", "ha_trend_up", ]