独立 wyckoff 引擎 + 按需 include_wyckoff;主站 Lightweight 绘制区间/阶段/事件/VP。 Co-authored-by: Cursor <cursoragent@cursor.com>
81 lines
2.0 KiB
Python
81 lines
2.0 KiB
Python
"""威科夫分析入口。"""
|
|
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,
|
|
}
|