b733e551a0
Add scripts/normalize_ambiguous_unicode.py; fix corrupted patch_instance_theme_templates.py. Preserves curly quotes in string literals; removes Git homoglyph warnings on .env.example. Co-authored-by: Cursor <cursoragent@cursor.com>
125 lines
3.9 KiB
Python
125 lines
3.9 KiB
Python
"""price_snapshot 共用:订单行情价兜底,避免 get_price 失败时整单不入 order_prices."""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Callable, Mapping, Optional, Sequence
|
|
|
|
from lib.hub.hub_position_metrics import parse_position_mark_price
|
|
|
|
|
|
def resolve_order_snapshot_price(
|
|
symbol: str,
|
|
prices: Mapping[str, float],
|
|
*,
|
|
position_row: Optional[dict[str, Any]] = None,
|
|
order_leverage=None,
|
|
parse_position_metrics_fn: Callable[..., dict[str, Any] | None] | None = None,
|
|
get_mark_price_fn: Callable[[str], float | None] | None = None,
|
|
fallback_entry: float | None = None,
|
|
) -> float | None:
|
|
"""
|
|
解析下单监控轮询用的现价/标记价,优先级:
|
|
1. 已批量拉取的 ticker last
|
|
2. get_symbol_mark_price(含 mark)
|
|
3. 交易所持仓 mark(parse_ccxt_position_metrics / parse_position_mark_price)
|
|
4. 计划成交价 trigger_price
|
|
"""
|
|
sym = (symbol or "").strip()
|
|
if not sym:
|
|
return None
|
|
|
|
cached = prices.get(sym)
|
|
if cached is not None:
|
|
try:
|
|
v = float(cached)
|
|
if v > 0:
|
|
return v
|
|
except (TypeError, ValueError):
|
|
pass
|
|
|
|
if get_mark_price_fn is not None:
|
|
try:
|
|
mp = get_mark_price_fn(sym)
|
|
if mp is not None and float(mp) > 0:
|
|
return float(mp)
|
|
except Exception:
|
|
pass
|
|
|
|
if position_row:
|
|
mark = None
|
|
if parse_position_metrics_fn is not None:
|
|
try:
|
|
metrics = parse_position_metrics_fn(
|
|
position_row, order_leverage=order_leverage
|
|
)
|
|
if isinstance(metrics, dict) and metrics.get("mark_price") is not None:
|
|
mark = float(metrics["mark_price"])
|
|
except Exception:
|
|
mark = None
|
|
if mark is None or mark <= 0:
|
|
try:
|
|
mp = parse_position_mark_price(position_row)
|
|
if mp is not None and mp > 0:
|
|
mark = float(mp)
|
|
except Exception:
|
|
mark = None
|
|
if mark is not None and mark > 0:
|
|
return mark
|
|
|
|
if fallback_entry is not None:
|
|
try:
|
|
entry = float(fallback_entry)
|
|
if entry > 0:
|
|
return entry
|
|
except (TypeError, ValueError):
|
|
pass
|
|
return None
|
|
|
|
|
|
def seed_prices_from_positions(
|
|
prices: dict[str, float],
|
|
order_rows: Sequence[Any],
|
|
all_positions: Sequence[dict[str, Any]],
|
|
*,
|
|
resolve_ex_sym_fn: Callable[[Any], str],
|
|
) -> None:
|
|
"""用持仓标记价补全 prices 字典(symbol 与 order_monitors 行对齐)."""
|
|
if not all_positions or not order_rows:
|
|
return
|
|
try:
|
|
from lib.hub.hub_symbol_lib import symbols_match
|
|
except Exception:
|
|
symbols_match = None
|
|
for r in order_rows:
|
|
try:
|
|
sym = str(r["symbol"] or "").strip()
|
|
except (KeyError, TypeError, IndexError):
|
|
sym = ""
|
|
if not sym or sym in prices:
|
|
continue
|
|
try:
|
|
ex_sym = resolve_ex_sym_fn(r)
|
|
except Exception:
|
|
ex_sym = sym
|
|
try:
|
|
direction = str(r["direction"] or "long").lower()
|
|
except (KeyError, TypeError, IndexError):
|
|
direction = "long"
|
|
for p in all_positions:
|
|
if not isinstance(p, dict):
|
|
continue
|
|
ps = p.get("symbol") or ""
|
|
if not ps:
|
|
continue
|
|
matched = ps == sym or ps == ex_sym
|
|
if not matched and symbols_match is not None:
|
|
matched = symbols_match(sym, ps) or symbols_match(ex_sym, ps)
|
|
if not matched:
|
|
continue
|
|
side = (p.get("side") or "").lower()
|
|
if side and side != direction:
|
|
continue
|
|
mp = parse_position_mark_price(p)
|
|
if mp is not None and mp > 0:
|
|
prices[sym] = float(mp)
|
|
break
|