""" general_analysis MTF 합성·정렬 점수. """ from __future__ import annotations from typing import Any import pandas as pd from config import ( ALIGN_BB_POS_HIGH, ALIGN_BB_POS_LOW, ALIGN_RSI_CONFLICT_TIMING_HIGH, ALIGN_RSI_CONFLICT_TIMING_LOW, ALIGN_RSI_CONFLICT_TREND_HIGH, ALIGN_RSI_CONFLICT_TREND_LOW, ALIGN_RSI_OVERBOUGHT, ALIGN_RSI_OVERSOLD, ) from deepcoin.analysis.general_analysis_config import TIMING_INTERVALS, TREND_INTERVALS from deepcoin.analysis.general_analysis_core import ga_col, interval_tf_prefix def general_analysis_mtf_scores( prefixed_row: dict[str, Any], ) -> dict[str, float | int | str]: """ 간격 접두사가 붙은 스냅샷 행에서 MTF 합성 점수 계산. Args: prefixed_row: m3_ga_rsi 형태 flat dict. Returns: ga_align_* 점수. """ rsi_oversold = 0 rsi_overbought = 0 trend_up = 0 trend_down = 0 n_timing = 0 n_trend = 0 conflict = 0 for interval in TIMING_INTERVALS: p = interval_tf_prefix(interval) rk = f"{p}_RSI" if rk in prefixed_row and prefixed_row[rk] is not None: n_timing += 1 rsi = float(prefixed_row[rk]) if rsi < ALIGN_RSI_OVERSOLD: rsi_oversold += 1 if rsi > ALIGN_RSI_OVERBOUGHT: rsi_overbought += 1 for interval in TREND_INTERVALS: p = interval_tf_prefix(interval) sk = f"{p}_{ga_col('struct_trend')}" if sk in prefixed_row: n_trend += 1 t = prefixed_row[sk] if t == "up": trend_up += 1 elif t == "down": trend_down += 1 m3_rsi = prefixed_row.get("m3_RSI") d1_rsi = prefixed_row.get("d1_RSI") if m3_rsi is not None and d1_rsi is not None: if ( float(m3_rsi) < ALIGN_RSI_CONFLICT_TIMING_LOW and float(d1_rsi) > ALIGN_RSI_CONFLICT_TREND_HIGH ): conflict = 1 if ( float(m3_rsi) > ALIGN_RSI_CONFLICT_TIMING_HIGH and float(d1_rsi) < ALIGN_RSI_CONFLICT_TREND_LOW ): conflict = 1 timing_buy_align = rsi_oversold / max(len(TIMING_INTERVALS), 1) timing_sell_align = rsi_overbought / max(len(TIMING_INTERVALS), 1) return { "ga_align_rsi_oversold_tf": rsi_oversold, "ga_align_rsi_overbought_tf": rsi_overbought, "ga_align_trend_up_tf": trend_up, "ga_align_trend_down_tf": trend_down, "ga_align_timing_buy_score": round(timing_buy_align, 3), "ga_align_timing_sell_score": round(timing_sell_align, 3), "ga_align_trend_score": round( (trend_up - trend_down) / max(n_trend, 1), 3 ), "ga_align_mtf_conflict": conflict, } def general_analysis_mtf_vote_latest( frames_enriched: dict[int, pd.DataFrame], ) -> dict[str, float | int | str]: """ 각 TF 최신 완성봉 지표로 TF 가중 투표·필터 점수 산출. Args: frames_enriched: interval → enrich된 DataFrame. Returns: ga_vote_* 점수 (접두사 없음, ga_col로 감쌀 것). """ votes_buy = 0 votes_sell = 0 trend_ok = 0 n = 0 for interval in TIMING_INTERVALS: df = frames_enriched.get(interval) if df is None or df.empty: continue row = df.iloc[-1] n += 1 rsi = row.get("RSI") if rsi is not None and not pd.isna(rsi): if float(rsi) < ALIGN_RSI_OVERSOLD: votes_buy += 1 if float(rsi) > ALIGN_RSI_OVERBOUGHT: votes_sell += 1 bb_pos = row.get("bb_pos") if bb_pos is not None and float(bb_pos) < ALIGN_BB_POS_LOW: votes_buy += 1 if bb_pos is not None and float(bb_pos) > ALIGN_BB_POS_HIGH: votes_sell += 1 for interval in TREND_INTERVALS: df = frames_enriched.get(interval) if df is None or df.empty: continue row = df.iloc[-1] st = row.get(ga_col("struct_trend"), "range") if st == "up": trend_ok += 1 elif st == "down": trend_ok -= 1 return { "vote_timing_buy": votes_buy, "vote_timing_sell": votes_sell, "vote_trend_score": trend_ok, "vote_tf_used": n, } def general_analysis_vote_columns() -> list[str]: return ["vote_timing_buy", "vote_timing_sell", "vote_trend_score", "vote_tf_used"]