Spaces:
Running
Running
Download utils/normalizer.py from SKaush/forex-scanner: direct link, hf CLI and curl.
- Browser
- Download file 8.79 kB
-
https://huggingface.co/spaces/SKaush/forex-scanner/resolve/main/utils/normalizer.py
- Command line
-
hf download hf://spaces/SKaush/forex-scanner/utils/normalizer.py
-
curl -L -o normalizer.py https://huggingface.co/spaces/SKaush/forex-scanner/resolve/main/utils/normalizer.py
8.79 kB
| """ | |
| utils/normalizer.py | |
| Central payload normalizer for live scanner and PA scanner outputs. | |
| Purpose: | |
| - prevent UI crashes from mixed payload shapes | |
| - normalize dict/string/list fields | |
| - fill missing nested keys with safe defaults | |
| """ | |
| from __future__ import annotations | |
| from copy import deepcopy | |
| from typing import Any, Dict, List | |
| def _safe_dict(v: Any) -> Dict[str, Any]: | |
| return v if isinstance(v, dict) else {} | |
| def _safe_list(v: Any) -> List[Any]: | |
| return v if isinstance(v, list) else [] | |
| def _safe_float(v: Any, default: float | None = None) -> float | None: | |
| try: | |
| if v is None or v == "": | |
| return default | |
| return float(v) | |
| except Exception: | |
| return default | |
| def _safe_int(v: Any, default: int = 0) -> int: | |
| try: | |
| if v is None or v == "": | |
| return default | |
| return int(v) | |
| except Exception: | |
| return default | |
| def _normalize_regime(v: Any) -> Dict[str, Any]: | |
| if isinstance(v, dict): | |
| return { | |
| "regime": v.get("regime", v.get("label", "unknown")), | |
| "label": v.get("label", v.get("regime", "unknown")), | |
| } | |
| if isinstance(v, str): | |
| return {"regime": v, "label": v} | |
| return {"regime": "unknown", "label": "unknown"} | |
| def _normalize_setup(v: Any) -> Dict[str, Any]: | |
| if isinstance(v, dict): | |
| return {"name": v.get("name", "No clear setup"), **v} | |
| if isinstance(v, str): | |
| return {"name": v} | |
| return {"name": "No clear setup"} | |
| def _normalize_levels(v: Any) -> Dict[str, Any]: | |
| d = _safe_dict(v) | |
| return { | |
| "entry": d.get("entry"), | |
| "sl": d.get("sl"), | |
| "tp": d.get("tp"), | |
| "tp1": d.get("tp1"), | |
| "tp2": d.get("tp2"), | |
| "tp3": d.get("tp3"), | |
| "be": d.get("be"), | |
| "support": d.get("support"), | |
| "resistance": d.get("resistance"), | |
| "rr_ratio": d.get("rr_ratio"), | |
| "good_rr": bool(d.get("good_rr", False)), | |
| "room_pips": d.get("room_pips"), | |
| "sl_pips": d.get("sl_pips"), | |
| "tp_pips": d.get("tp_pips"), | |
| "tp1_pips": d.get("tp1_pips"), | |
| "tp2_pips": d.get("tp2_pips"), | |
| "tp3_pips": d.get("tp3_pips"), | |
| "risk_pips": d.get("risk_pips"), | |
| "reward_pips": d.get("reward_pips"), | |
| "sl_source": d.get("sl_source", "n/a"), | |
| "tp_source": d.get("tp_source", "n/a"), | |
| "digits": _safe_int(d.get("digits"), 5), | |
| "atr_pips": d.get("atr_pips"), | |
| "spread": d.get("spread"), | |
| **d, | |
| } | |
| def _normalize_patterns(v: Any) -> Dict[str, Any]: | |
| d = _safe_dict(v) | |
| return { | |
| "patterns": _safe_dict(d.get("patterns")), | |
| "pattern_score": _safe_float(d.get("pattern_score"), 0.0), | |
| "any_pattern": bool(d.get("any_pattern", False)), | |
| "best": d.get("best"), | |
| **d, | |
| } | |
| def _normalize_session(v: Any) -> Dict[str, Any]: | |
| d = _safe_dict(v) | |
| return { | |
| "allowed": bool(d.get("allowed", False)), | |
| "sessions": _safe_list(d.get("sessions")), | |
| "active": _safe_list(d.get("active")), | |
| "overlaps": _safe_list(d.get("overlaps")), | |
| "score": d.get("score", 0), | |
| "reason": d.get("reason", d.get("note", "")), | |
| "note": d.get("note", d.get("reason", "")), | |
| **d, | |
| } | |
| def _normalize_news(v: Any) -> Dict[str, Any]: | |
| d = _safe_dict(v) | |
| blocking = d.get("blocking", []) | |
| if not isinstance(blocking, list): | |
| blocking = [blocking] | |
| return { | |
| "allowed": bool(d.get("allowed", True)), | |
| "ok": bool(d.get("ok", d.get("allowed", True))), | |
| "note": d.get("note", "Clear"), | |
| "events": _safe_list(d.get("events")), | |
| "blocking": blocking, | |
| "score": d.get("score", 1.0), | |
| **d, | |
| } | |
| def _normalize_tf_block(v: Any, include_bias: bool = True) -> Dict[str, Any]: | |
| d = _safe_dict(v) | |
| out = { | |
| "rsi": d.get("rsi"), | |
| "adx": d.get("adx"), | |
| "zone": d.get("zone", "mixed"), | |
| } | |
| if include_bias: | |
| out.update( | |
| { | |
| "bias": d.get("bias", 0), | |
| "label": d.get("label", "neutral"), | |
| "stack_bull": bool(d.get("stack_bull", False)), | |
| "stack_bear": bool(d.get("stack_bear", False)), | |
| "ema20": d.get("ema20"), | |
| "ema50": d.get("ema50"), | |
| "sma200": d.get("sma200"), | |
| "sma_slope": d.get("sma_slope", "?"), | |
| } | |
| ) | |
| out.update(d) | |
| return out | |
| def _normalize_ltf(v: Any) -> Dict[str, Any]: | |
| d = _safe_dict(v) | |
| return { | |
| "ltf": d.get("ltf", "H1"), | |
| "rsi": d.get("rsi"), | |
| "atr": d.get("atr"), | |
| "adx": d.get("adx"), | |
| "triggered": bool(d.get("triggered", False)), | |
| "signal": d.get("signal", 0), | |
| "cross_ok": bool(d.get("cross_ok", False)), | |
| "rsi_ok": bool(d.get("rsi_ok", False)), | |
| "zone": d.get("zone", "mixed"), | |
| "rsi_mode": d.get("rsi_mode", "pullback"), | |
| "trigger_mode": d.get("trigger_mode", "none"), | |
| "trigger_note": d.get("trigger_note", ""), | |
| "entry_value_ok": bool(d.get("entry_value_ok", True)), | |
| "entry_value_note": d.get("entry_value_note", ""), | |
| "rsi_phase": d.get("rsi_phase", "unknown"), | |
| "value_score": d.get("value_score"), | |
| "value_score_note": d.get("value_score_note", ""), | |
| "entry_timing_score": d.get("entry_timing_score"), | |
| "entry_timing_note": d.get("entry_timing_note", ""), | |
| **d, | |
| } | |
| def _normalize_explanation(v: Any) -> Dict[str, Any]: | |
| d = _safe_dict(v) | |
| return { | |
| "summary": d.get("summary", ""), | |
| "state": d.get("state", ""), | |
| "bias_story": d.get("bias_story", ""), | |
| "location_story": d.get("location_story", ""), | |
| "mtf_story": d.get("mtf_story", ""), | |
| "mtf_checks_verbose": _safe_dict(d.get("mtf_checks_verbose")), | |
| "ltf_story": d.get("ltf_story", ""), | |
| "trigger_expectation": d.get("trigger_expectation", ""), | |
| "confirmation_story": d.get("confirmation_story", ""), | |
| "momentum_story": d.get("momentum_story", ""), | |
| "risk_story": d.get("risk_story", ""), | |
| "session_story": d.get("session_story", ""), | |
| "news_story": d.get("news_story", ""), | |
| "blockers_story": d.get("blockers_story", ""), | |
| "waiting_story": d.get("waiting_story", ""), | |
| "next_step": d.get("next_step", ""), | |
| **d, | |
| } | |
| def normalize_signal_payload(result: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| Normalize one scanner result payload into a stable shape for UI/audit. | |
| """ | |
| r = deepcopy(result) if isinstance(result, dict) else {} | |
| r["setup"] = _normalize_setup(r.get("setup")) | |
| r["regime"] = _normalize_regime(r.get("regime")) | |
| if "regime_v2" in r: | |
| r["regime_v2"] = _normalize_regime(r.get("regime_v2")) | |
| r["levels"] = _normalize_levels(r.get("levels")) | |
| r["patterns"] = _normalize_patterns(r.get("patterns")) | |
| r["session"] = _normalize_session(r.get("session")) | |
| r["news"] = _normalize_news(r.get("news")) | |
| r["wtf"] = _normalize_tf_block(r.get("wtf"), include_bias=True) | |
| r["htf"] = _normalize_tf_block(r.get("htf"), include_bias=True) | |
| mtf = _safe_dict(r.get("mtf")) | |
| r["mtf"] = { | |
| "zone": mtf.get("zone", "mixed"), | |
| "zone_label": mtf.get("zone_label", "mixed"), | |
| "sr_levels": _safe_list(mtf.get("sr_levels")), | |
| "score": _safe_int(mtf.get("score"), 0), | |
| "max": _safe_int(mtf.get("max"), 0), | |
| "confirmed": bool(mtf.get("confirmed", False)), | |
| "rsi": mtf.get("rsi"), | |
| "adx": mtf.get("adx"), | |
| "checks": _safe_dict(mtf.get("checks")), | |
| **mtf, | |
| } | |
| r["ltf"] = _normalize_ltf(r.get("ltf")) | |
| r["explanation"] = _normalize_explanation(r.get("explanation")) | |
| r["blockers"] = [str(x) for x in _safe_list(r.get("blockers"))] | |
| r["waiting_for"] = [str(x) for x in _safe_list(r.get("waiting_for"))] | |
| r["signal"] = _safe_int(r.get("signal"), 0) | |
| r["tradeable"] = bool(r.get("tradeable", False)) | |
| r["grade"] = str(r.get("grade", "NO TRADE")) | |
| r["direction"] = str(r.get("direction", "—")) | |
| r["decision"] = str(r.get("decision", "NO TRADE")) | |
| r["instrument"] = str(r.get("instrument", "—")) | |
| r["ltf_tf"] = str(r.get("ltf_tf", "H1")) | |
| # PA-specific optional blocks | |
| r["context"] = _safe_dict(r.get("context")) | |
| r["confirmed_setups"] = [str(x) for x in _safe_list(r.get("confirmed_setups"))] | |
| r["near_levels"] = _safe_dict(r.get("near_levels")) | |
| r["key_levels"] = _safe_dict(r.get("key_levels")) | |
| r["pa_setups"] = _safe_dict(r.get("pa_setups")) | |
| return r | |
| def normalize_signal_list(results: List[Dict[str, Any]]) -> List[Dict[str, Any]]: | |
| return [normalize_signal_payload(x) for x in results if isinstance(x, dict)] | |