Files
eth_hedge_sim/backend/app/exchange/binance/parse.py
T
2026-07-25 12:02:36 +08:00

98 lines
2.9 KiB
Python

"""币安期权 / 永续符号解析 → 中性合约行。"""
from __future__ import annotations
import re
from typing import Any
from ..expiry import expiry_ms_from_ymd, ymd_from_expiry_ms
_OPT_RE = re.compile(
r"^(?P<under>[A-Z0-9]+)-(?P<ymd>\d{6})-(?P<strike>\d+(?:\.\d+)?)-(?P<side>[CP])$",
re.IGNORECASE,
)
def safe_float(v: Any) -> float | None:
if v is None or v == "":
return None
try:
return float(v)
except (TypeError, ValueError):
return None
def parse_option_symbol(symbol: str) -> tuple[str | None, float | None, str | None]:
"""ETH-250726-1860-C → (YYMMDD, strike, C|P)."""
m = _OPT_RE.match((symbol or "").strip())
if not m:
return None, None, None
ymd = m.group("ymd")
strike = safe_float(m.group("strike"))
side = m.group("side").upper()
return ymd, strike, side
def is_option_symbol(symbol: str) -> bool:
y, s, o = parse_option_symbol(symbol)
return y is not None and s is not None and o in ("C", "P")
def rows_to_option_contracts(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""
归一化:
{inst_id, expiry_ymd, expiry_ms, strike, side, ct_mult}
"""
out: list[dict[str, Any]] = []
for row in rows:
if not isinstance(row, dict):
continue
status = str(row.get("status") or "TRADING").upper()
if status and status not in ("TRADING", "LIVE", ""):
continue
inst_id = str(row.get("symbol") or row.get("inst_id") or "")
y, stk, opt = parse_option_symbol(inst_id)
exp_ms = None
raw_exp = row.get("expiryDate") or row.get("expiration") or row.get("expiry_ms")
if raw_exp is not None:
try:
exp_ms = int(float(raw_exp))
if exp_ms < 10_000_000_000: # seconds
exp_ms *= 1000
except (TypeError, ValueError):
exp_ms = None
if y is None and exp_ms is not None:
y = ymd_from_expiry_ms(exp_ms)
if stk is None:
stk = safe_float(row.get("strikePrice") or row.get("strike"))
if opt is None:
side_raw = str(row.get("side") or row.get("optionSide") or "").upper()
if side_raw in ("CALL", "C"):
opt = "C"
elif side_raw in ("PUT", "P"):
opt = "P"
if not inst_id or not y or stk is None or opt not in ("C", "P"):
continue
if exp_ms is None:
try:
exp_ms = expiry_ms_from_ymd(y)
except ValueError:
continue
unit = safe_float(row.get("unit") or row.get("ct_mult"))
out.append(
{
"inst_id": inst_id,
"expiry_ymd": y,
"expiry_ms": int(exp_ms),
"strike": float(stk),
"side": opt,
"ct_mult": float(unit) if unit and unit > 0 else 1.0,
}
)
return out