Fix roll position display: live contracts, TP profit, and 2-decimal qty precision.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-08 17:06:33 +08:00
parent 5ef6affabf
commit e3e53ff7f5
15 changed files with 217 additions and 33 deletions
+3 -1
View File
@@ -55,6 +55,8 @@ def build_strategy_config(
return float(row["current_capital"])
def get_position(ex_sym, direction):
from lib.hub.hub_position_metrics import normalize_contracts_qty
qty = m.get_live_position_contracts(ex_sym, direction)
entry = None
try:
@@ -77,7 +79,7 @@ def build_strategy_config(
break
except Exception:
pass
return {"contracts": float(qty or 0), "entry_price": entry}
return {"contracts": normalize_contracts_qty(qty or 0), "entry_price": entry}
def amount_to_precision(ex_sym, amount):
try:
+16 -2
View File
@@ -492,9 +492,23 @@ def _roll_execute(cfg: dict, data: dict) -> tuple[bool, str]:
"UPDATE roll_groups SET leg_count=?, current_stop_loss=?, updated_at=? WHERE id=?",
(legs_done + 1, new_sl, cfg["app_now_str"](), rg["id"]),
)
live_qty = float(mon.get("order_amount") or 0) + float(amount)
try:
from lib.hub.hub_position_metrics import contracts_qty_is_open, normalize_contracts_qty
pos2 = cfg["get_position"](ex_sym, direction) or {}
q2 = normalize_contracts_qty(pos2.get("contracts") or 0)
if contracts_qty_is_open(q2):
live_qty = q2
else:
live_qty = normalize_contracts_qty(live_qty)
except Exception:
from lib.hub.hub_position_metrics import normalize_contracts_qty
live_qty = normalize_contracts_qty(live_qty)
conn.execute(
"UPDATE order_monitors SET stop_loss=? WHERE id=?",
(new_sl, mon["id"]),
"UPDATE order_monitors SET stop_loss=?, order_amount=? WHERE id=?",
(new_sl, live_qty, mon["id"]),
)
conn.commit()
_maybe_notify_roll_started(cfg, rg, mon, symbol, direction, tp0, new_sl, roll_is_new=roll_is_new)
+28 -9
View File
@@ -205,6 +205,8 @@ def _close_roll_group(conn, cfg: dict, group: dict, *, reason: str = "下单监
def _reconcile_roll_groups(conn, cfg: dict) -> None:
from lib.hub.hub_position_metrics import contracts_qty_is_open, normalize_contracts_qty
rows = conn.execute(
"""SELECT g.*, m.status AS monitor_status
FROM roll_groups g
@@ -217,9 +219,24 @@ def _reconcile_roll_groups(conn, cfg: dict) -> None:
direction = (g.get("direction") or "long").strip().lower()
ex_sym = g.get("exchange_symbol") or cfg["normalize_exchange_symbol"](symbol)
mon_ok = (row["monitor_status"] or "").strip().lower() == "active"
pos = cfg["get_position"](ex_sym, direction)
qty = float(pos.get("contracts") or 0)
if not mon_ok or qty <= 0:
if not mon_ok:
_close_roll_group(conn, cfg, g, reason="下单监控已结案")
continue
pos = None
try:
pos = cfg["get_position"](ex_sym, direction)
except Exception:
pos = None
if pos is None:
continue
qty = normalize_contracts_qty(pos.get("contracts") or 0)
if not contracts_qty_is_open(qty):
try:
pos2 = cfg["get_position"](ex_sym, direction) or {}
qty = normalize_contracts_qty(pos2.get("contracts") or 0)
except Exception:
continue
if not contracts_qty_is_open(qty):
_close_roll_group(conn, cfg, g)
@@ -342,6 +359,8 @@ def _execute_pending_roll_leg(
direction: str,
mark: float,
) -> None:
from lib.hub.hub_position_metrics import contracts_qty_is_open, normalize_contracts_qty
leg_id = int(leg["id"])
gid = int(group["roll_group_id"]) if "roll_group_id" in leg else int(group["id"])
mon_id = group.get("order_monitor_id")
@@ -354,9 +373,9 @@ def _execute_pending_roll_leg(
return
pos = cfg["get_position"](ex_sym, direction) or {}
qty = float(pos.get("contracts") or 0)
qty = normalize_contracts_qty(pos.get("contracts") or 0)
entry = float(pos.get("entry_price") or mon.get("trigger_price") or 0)
if qty <= 0 or entry <= 0:
if not contracts_qty_is_open(qty) or entry <= 0:
_invalidate_roll_leg(conn, cfg, group, leg, mark, reason="无持仓")
return
@@ -431,17 +450,17 @@ def _execute_pending_roll_leg(
"UPDATE roll_groups SET leg_count=?, current_stop_loss=?, updated_at=? WHERE id=?",
(filled + 1, sl, _now(cfg), gid),
)
live_qty = qty + float(amount)
live_qty = normalize_contracts_qty(qty + float(amount))
try:
pos2 = cfg["get_position"](ex_sym, direction) or {}
q2 = float(pos2.get("contracts") or 0)
if q2 > 0:
q2 = normalize_contracts_qty(pos2.get("contracts") or 0)
if contracts_qty_is_open(q2):
live_qty = q2
except Exception:
pass
conn.execute(
"UPDATE order_monitors SET stop_loss=?, order_amount=? WHERE id=? AND status='active'",
(sl, float(live_qty), mon["id"]),
(sl, live_qty, mon["id"]),
)
notify = cfg.get("send_wechat")
+14 -7
View File
@@ -63,13 +63,18 @@ def infer_initial_position(
entry_live = float(entry_live)
except (TypeError, ValueError):
qty_live = entry_live = 0.0
from lib.hub.hub_position_metrics import normalize_contracts_qty
qty_live = normalize_contracts_qty(qty_live)
legs = [
lg
for lg in filled_legs or []
if isinstance(lg, dict) and leg_is_filled(lg) and leg_fill_price(lg) and float(lg.get("amount") or 0) > 0
]
add_sum = sum(float(lg.get("amount") or 0) for lg in legs)
leg_notional = sum(float(lg.get("amount") or 0) * float(leg_fill_price(lg) or 0) for lg in legs)
add_sum = sum(normalize_contracts_qty(lg.get("amount") or 0) for lg in legs)
leg_notional = sum(
normalize_contracts_qty(lg.get("amount") or 0) * float(leg_fill_price(lg) or 0) for lg in legs
)
q0 = qty_live - add_sum
if q0 > 1e-12 and entry_live > 0 and qty_live > 0:
e0 = (entry_live * qty_live - leg_notional) / q0
@@ -131,8 +136,8 @@ def compute_roll_chain_metrics(
return per_leg, group_out
qty = float(q0)
avg = float(e0)
group_out["initial_qty"] = round(qty, 4)
group_out["current_qty"] = round(qty, 4)
group_out["initial_qty"] = round(qty, 2)
group_out["current_qty"] = round(qty, 2)
if tp > 0:
group_out["avg_entry"] = avg
group_out["reward_at_tp_usdt"] = reward_at_tp_usdt(
@@ -161,12 +166,12 @@ def compute_roll_chain_metrics(
}
group_out["avg_entry"] = round(avg, 10)
group_out["reward_at_tp_usdt"] = round(reward, 4) if reward is not None else None
group_out["current_qty"] = round(qty, 4)
group_out["current_qty"] = round(qty, 2)
if qty_live is not None:
try:
live_qty = float(qty_live)
if live_qty > 0:
group_out["current_qty"] = round(live_qty, 4)
group_out["current_qty"] = round(live_qty, 2)
except (TypeError, ValueError):
pass
return per_leg, group_out
@@ -208,7 +213,9 @@ def _resolve_roll_live(cfg: dict, group: dict, monitor: dict | None) -> tuple[Op
return None, None, cs
try:
pos = get_pos(ex_sym or sym, direction) or {}
qty = float(pos.get("contracts") or 0)
from lib.hub.hub_position_metrics import normalize_contracts_qty
qty = normalize_contracts_qty(pos.get("contracts") or 0)
entry = float(pos.get("entry_price") or 0)
if qty > 0 and entry > 0:
return qty, entry, cs
@@ -63,8 +63,8 @@
<td>{{ g.symbol }}</td>
<td>{{ g.direction }}</td>
<td>{{ g.leg_count }}</td>
<td>{% if g.initial_qty is not none %}{{ '%.4g'|format(g.initial_qty) }}{% else %}—{% endif %}</td>
<td>{% if g.current_qty is not none %}{{ '%.4g'|format(g.current_qty) }}{% else %}—{% endif %}</td>
<td>{% if g.initial_qty is not none %}{{ '%.2f'|format(g.initial_qty) }}{% else %}—{% endif %}</td>
<td>{% if g.current_qty is not none %}{{ '%.2f'|format(g.current_qty) }}{% else %}—{% endif %}</td>
<td>{% if price_fmt %}{{ price_fmt(g.symbol, g.initial_take_profit) }}{% else %}{{ g.initial_take_profit }}{% endif %}</td>
<td>{% if price_fmt %}{{ price_fmt(g.symbol, g.current_stop_loss) }}{% else %}{{ g.current_stop_loss }}{% endif %}</td>
<td>{% if g.avg_entry_display %}{{ g.avg_entry_display }}{% elif g.avg_entry is not none %}{{ g.avg_entry }}{% else %}—{% endif %}</td>