93e148a3e7
Share focus_chart templates and APIs across four instances; align chart Y-axis, price lines and meta bar with exchange symbol precision and live unrealized PnL. Co-authored-by: Cursor <cursoragent@cursor.com>
188 lines
6.0 KiB
Python
188 lines
6.0 KiB
Python
"""实盘/关键位放大 K 线:订单元数据与交易所浮盈、价格展示精度。"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Callable, Optional
|
|
|
|
from hub_ohlcv_lib import (
|
|
normalize_price_tick,
|
|
price_tick_from_market,
|
|
round_ohlcv_bars_to_tick,
|
|
)
|
|
from order_monitor_display_lib import (
|
|
apply_order_live_price_display,
|
|
apply_order_price_display_fields,
|
|
)
|
|
|
|
|
|
def resolve_kline_price_tick(
|
|
exchange: Any,
|
|
exchange_symbol: str,
|
|
*,
|
|
ensure_markets_fn: Callable[[], None],
|
|
) -> Optional[float]:
|
|
"""交易所最小价格变动单位,供 lightweight-charts 右侧刻度与标记线对齐。"""
|
|
if not exchange_symbol:
|
|
return None
|
|
try:
|
|
ensure_markets_fn()
|
|
return normalize_price_tick(price_tick_from_market(exchange, exchange_symbol))
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def align_candles_to_price_tick(
|
|
candles: list[dict[str, Any]],
|
|
price_tick: Optional[float],
|
|
) -> None:
|
|
if price_tick is not None and candles:
|
|
round_ohlcv_bars_to_tick(candles, price_tick)
|
|
|
|
|
|
def kline_api_price_fields(
|
|
exchange: Any,
|
|
exchange_symbol: str,
|
|
candles: list[dict[str, Any]],
|
|
*,
|
|
ensure_markets_fn: Callable[[], None],
|
|
) -> dict[str, Any]:
|
|
tick = resolve_kline_price_tick(
|
|
exchange, exchange_symbol, ensure_markets_fn=ensure_markets_fn
|
|
)
|
|
align_candles_to_price_tick(candles, tick)
|
|
return {"price_tick": tick}
|
|
|
|
|
|
def load_swap_positions_for_order_kline(
|
|
exchange: Any,
|
|
*,
|
|
private_configured: bool,
|
|
ensure_markets_fn: Callable[[], None],
|
|
settle: str = "usdt",
|
|
) -> list:
|
|
if not private_configured:
|
|
return []
|
|
try:
|
|
ensure_markets_fn()
|
|
try:
|
|
return exchange.fetch_positions(None, {"settle": settle}) or []
|
|
except Exception:
|
|
return exchange.fetch_positions() or []
|
|
except Exception:
|
|
return []
|
|
|
|
|
|
def metrics_for_order_item(
|
|
order_item: dict[str, Any],
|
|
positions: list,
|
|
*,
|
|
resolve_ex_sym_fn: Callable[[Any], str],
|
|
select_live_fn: Callable[[list, str, str], Any],
|
|
parse_metrics_fn: Callable[..., Optional[dict]],
|
|
) -> Optional[dict]:
|
|
if not positions:
|
|
return None
|
|
ex_sym = resolve_ex_sym_fn(order_item)
|
|
direction = order_item.get("direction") or "long"
|
|
prow = select_live_fn(positions, ex_sym, direction)
|
|
if not prow:
|
|
return None
|
|
lev = order_item.get("leverage")
|
|
return parse_metrics_fn(prow, order_leverage=lev)
|
|
|
|
|
|
def build_order_kline_order_payload(
|
|
order_item: dict[str, Any],
|
|
*,
|
|
ticker_price: Any,
|
|
format_price_fn: Callable[[Any, Any], str],
|
|
calc_pnl_fn: Callable[..., float],
|
|
calc_rr_ratio_fn: Callable[..., Optional[float]],
|
|
ex_metrics: Optional[dict] = None,
|
|
) -> dict[str, Any]:
|
|
sym = order_item.get("symbol") or ""
|
|
direction = order_item.get("direction") or "long"
|
|
margin = float(order_item.get("margin_capital") or 0)
|
|
leverage = float(order_item.get("leverage") or 0)
|
|
entry = float(order_item.get("trigger_price") or 0)
|
|
|
|
float_pnl = 0.0
|
|
float_pct = 0.0
|
|
if ticker_price and entry > 0:
|
|
float_pnl = float(
|
|
calc_pnl_fn(direction, entry, ticker_price, margin, leverage)
|
|
)
|
|
float_pct = round((float_pnl / margin * 100), 4) if margin > 0 else 0.0
|
|
|
|
px_for_fmt = ticker_price
|
|
mark_raw = None
|
|
if ex_metrics and ex_metrics.get("mark_price") is not None:
|
|
mark_raw = ex_metrics["mark_price"]
|
|
try:
|
|
px_for_fmt = float(mark_raw)
|
|
except (TypeError, ValueError):
|
|
pass
|
|
|
|
if ex_metrics and ex_metrics.get("unrealized_pnl") is not None:
|
|
float_pnl = round(float(ex_metrics["unrealized_pnl"]), 2)
|
|
denom = ex_metrics.get("initial_margin") or margin
|
|
float_pct = (
|
|
round((float_pnl / float(denom)) * 100, 4)
|
|
if denom and float(denom) > 0
|
|
else float_pct
|
|
)
|
|
|
|
payload: dict[str, Any] = {
|
|
"id": order_item["id"],
|
|
"symbol": sym,
|
|
"direction": direction,
|
|
"trigger_price": order_item.get("trigger_price"),
|
|
"stop_loss": order_item.get("stop_loss"),
|
|
"take_profit": order_item.get("take_profit"),
|
|
"trigger_price_display": format_price_fn(sym, order_item.get("trigger_price")),
|
|
"stop_loss_display": format_price_fn(sym, order_item.get("stop_loss")),
|
|
"take_profit_display": format_price_fn(sym, order_item.get("take_profit")),
|
|
"margin_capital": order_item.get("margin_capital"),
|
|
"leverage": order_item.get("leverage"),
|
|
"position_ratio": order_item.get("position_ratio"),
|
|
"breakeven_enabled": bool(int(order_item.get("breakeven_enabled") or 0)),
|
|
"current_price": round(float(px_for_fmt), 8) if px_for_fmt is not None else None,
|
|
"float_pnl": round(float(float_pnl), 2),
|
|
"float_pct": float_pct,
|
|
}
|
|
apply_order_price_display_fields(
|
|
payload,
|
|
direction=direction,
|
|
entry_price=order_item.get("trigger_price"),
|
|
initial_stop_loss=order_item.get("initial_stop_loss"),
|
|
stop_loss=order_item.get("stop_loss"),
|
|
take_profit=order_item.get("take_profit"),
|
|
calc_rr_ratio_fn=calc_rr_ratio_fn,
|
|
)
|
|
apply_order_live_price_display(
|
|
payload,
|
|
sym,
|
|
ticker_price,
|
|
mark_raw,
|
|
format_price_fn,
|
|
)
|
|
payload["current_price_display"] = payload.get("price_display") or (
|
|
format_price_fn(sym, px_for_fmt) if px_for_fmt is not None else None
|
|
)
|
|
return payload
|
|
|
|
|
|
def enrich_key_kline_response(
|
|
*,
|
|
symbol: str,
|
|
current_price: Any,
|
|
key_info: Optional[dict[str, Any]],
|
|
format_price_fn: Callable[[Any, Any], str],
|
|
) -> tuple[Any, Optional[dict[str, Any]]]:
|
|
price_display = format_price_fn(symbol, current_price) if current_price is not None else None
|
|
if key_info is None:
|
|
return price_display, None
|
|
enriched = dict(key_info)
|
|
enriched["upper_display"] = format_price_fn(symbol, key_info.get("upper"))
|
|
enriched["lower_display"] = format_price_fn(symbol, key_info.get("lower"))
|
|
return price_display, enriched
|