"""威科夫分析入口。""" from __future__ import annotations from typing import Any, Dict, Optional import pandas as pd from .events import build_phases, detect_bias_and_events from .range import detect_trading_range from .volume_profile import compute_volume_profile def _fmt_time(v) -> Optional[str]: if v is None: return None if hasattr(v, "isoformat"): try: return v.isoformat() except Exception: pass return str(v) def analyze_wyckoff(df: pd.DataFrame, lookback: int = 120, vp_bins: int = 50) -> Dict[str, Any]: """ 对主周期 OHLCV DataFrame 做威科夫启发式分析。 需要列: open, high, low, close, volume;建议有 date 或 timestamp。 """ empty = { "trading_range": None, "bias": "unknown", "phases": [], "events": [], "volume_profile": {"bins": [], "poc": None, "vah": None, "val": None, "bin_count": vp_bins}, "volume_confirm": {"avg_volume": 0.0, "event_checks": {}}, } if df is None or len(df) < 30: return empty if not all(c in df.columns for c in ("open", "high", "low", "close")): return empty work = df.copy() if "volume" not in work.columns: work["volume"] = 1.0 tr = detect_trading_range(work, lookback=lookback) if tr is None: return empty bias, events, volume_confirm = detect_bias_and_events(work, tr) phases = build_phases(work, tr, bias, events) vp = compute_volume_profile( work, int(tr["abs_start_idx"]), int(tr["abs_end_idx"]), bin_count=vp_bins, ) trading_range = { "start_time": _fmt_time(tr.get("start_time")), "end_time": _fmt_time(tr.get("end_time")), "high": float(tr["high"]), "low": float(tr["low"]), "mid": float(tr["mid"]), "active": bool(tr.get("active", True)), "bars": int(tr.get("bars", 0)), } for ev in events: ev["time"] = _fmt_time(ev.get("time")) for ph in phases: ph["start_time"] = _fmt_time(ph.get("start_time")) ph["end_time"] = _fmt_time(ph.get("end_time")) return { "trading_range": trading_range, "bias": bias, "phases": phases, "events": events, "volume_profile": vp, "volume_confirm": volume_confirm, }