from __future__ import annotations from datetime import time as dt_time from typing import Any from backend.data.numbers import finite_number as _number def calendar_is_open(value: Any) -> bool: if value in (True, 1, "1", "Y", "y"): return True if value in (False, 0, "0", "N", "n", None, ""): return False try: return int(value) == 1 except (TypeError, ValueError): return False def _text(value: Any) -> str: if isinstance(value, (list, tuple, set)): return "、".join(str(item).strip() for item in value if str(item).strip()) return str(value or "").strip() def _optional_number(value: Any) -> float | None: if value in (None, "", "-"): return None number = _number(value, default=float("nan")) if number != number: return None return number def _moneyflow_payload(flow: dict[str, Any] | None) -> dict[str, Any]: if not flow: return { "available": False, "net_million": None, "large_million": None, "medium_million": None, "small_million": None, } net = _optional_number(flow.get("net_mf_amount")) buy_lg = _optional_number(flow.get("buy_lg_amount")) sell_lg = _optional_number(flow.get("sell_lg_amount")) buy_elg = _optional_number(flow.get("buy_elg_amount")) sell_elg = _optional_number(flow.get("sell_elg_amount")) buy_md = _optional_number(flow.get("buy_md_amount")) sell_md = _optional_number(flow.get("sell_md_amount")) buy_sm = _optional_number(flow.get("buy_sm_amount")) sell_sm = _optional_number(flow.get("sell_sm_amount")) large = None if None not in (buy_lg, sell_lg, buy_elg, sell_elg): large = (buy_lg + buy_elg - sell_lg - sell_elg) elif _optional_number(flow.get("large_amount")) is not None: large = _optional_number(flow.get("large_amount")) medium = None if None in (buy_md, sell_md) else (buy_md - sell_md) if medium is None: medium = _optional_number(flow.get("medium_amount")) small = None if None in (buy_sm, sell_sm) else (buy_sm - sell_sm) if small is None: small = _optional_number(flow.get("small_amount")) if net is None and large is None and medium is None and small is None: return _moneyflow_payload(None) return { "available": True, "net_million": None if net is None else round(net / 100, 2), "large_million": None if large is None else round(large / 100, 2), "medium_million": None if medium is None else round(medium / 100, 2), "small_million": None if small is None else round(small / 100, 2), } def _prices_equal(left: Any, right: Any) -> bool: if left is None or right is None: return False return abs(_number(left) - _number(right)) < 0.005 def _value_percentile(value: float, population: list[float]) -> float: valid = sorted(item for item in population if item >= 0) if not valid: return 0.0 below = sum(item < value for item in valid) equal = sum(item == value for item in valid) return (below + equal * 0.5) / len(valid) def _trading_session_progress(current_time: dt_time) -> float: morning_start = dt_time(9, 30) morning_end = dt_time(11, 30) afternoon_start = dt_time(13, 0) afternoon_end = dt_time(15, 0) if current_time <= morning_start: return 0.05 if current_time <= morning_end: minutes = (current_time.hour * 60 + current_time.minute) - (9 * 60 + 30) return max(0.05, min(0.5, minutes / 240)) if current_time < afternoon_start: return 0.5 if current_time <= afternoon_end: minutes = (current_time.hour * 60 + current_time.minute) - 13 * 60 return max(0.5, min(1.0, 0.5 + minutes / 240)) return 1.0 def _display_time(value: Any) -> str: raw = str(value or "").replace(":", "").zfill(6) if not raw.strip("0"): return "--" return f"{raw[:2]}:{raw[2:4]}:{raw[4:6]}" def _realtime_market_status(current_time: dt_time) -> str: if current_time < dt_time(9, 25): return "pre_open" if current_time < dt_time(9, 30): return "auction" if current_time <= dt_time(11, 30) or dt_time(13, 0) <= current_time <= dt_time(15, 0): return "trading" if current_time < dt_time(13, 0): return "lunch_break" return "closed"