Files
xiaobai-review/backend/data/providers/tushare_helpers.py
T

125 lines
4.3 KiB
Python

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"