Fix daily risk and open count deduping by position slot.

Count each symbol+direction once per trading day; use realized loss for closed slots instead of summing duplicate monitor rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-03 09:42:00 +08:00
parent d46cd7c3e1
commit b6c3266a9e
+88 -28
View File
@@ -216,49 +216,109 @@ def _opened_in_trading_day(open_time: str, now: Optional[datetime] = None) -> bo
return oms >= int(trading_day_start(now).timestamp() * 1000)
def count_daily_opens(conn, now: Optional[datetime] = None) -> int:
rows = conn.execute(
"SELECT open_time FROM trade_order_monitors "
def _daily_open_slots(conn, now: Optional[datetime] = None) -> set[tuple[str, str]]:
"""当日开仓槽位:按品种+方向去重(含已平),避免监控复活产生重复计数。"""
slots: set[tuple[str, str]] = set()
for r in conn.execute(
"SELECT symbol, direction, open_time FROM trade_order_monitors "
"WHERE open_time IS NOT NULL AND trim(open_time) <> ''"
).fetchall()
return sum(1 for r in rows if _opened_in_trading_day(r["open_time"], now))
).fetchall():
if not _opened_in_trading_day(r["open_time"], now):
continue
sym = (r["symbol"] or "").strip()
if sym:
slots.add(_position_slot_key(sym, r["direction"] or "long"))
return slots
def count_daily_opens(conn, now: Optional[datetime] = None) -> int:
return len(_daily_open_slots(conn, now))
def _risk_amount_for_monitor_row(r, equity: float) -> float:
from modules.core.contract_specs import calc_position_metrics
entry = float(r["entry_price"] or 0)
if entry <= 0:
return 0.0
lots = int(r["lots"] or 0)
if lots <= 0:
return 0.0
sl = float(r["stop_loss"] if r["stop_loss"] is not None else entry)
tp = float(r["take_profit"] if r["take_profit"] is not None else entry)
m = calc_position_metrics(
r["direction"] or "long",
entry,
sl,
tp,
lots,
entry,
equity,
r["symbol"] or "",
)
return float(m.get("risk_amount") or 0)
def daily_trading_risk_used_pct(
conn, equity: float, now: Optional[datetime] = None,
) -> Optional[float]:
"""当日交易风险占权益(%):每品种槽位只计一次。
- 仍持仓:按止损距离算风险金额(以损定仓口径)
- 已平仓:按当日已实现亏损计(pnl_net<0),不再重复累加历史监控行
"""
if equity <= 0:
return None
from modules.core.contract_specs import calc_position_metrics
slots = _daily_open_slots(conn, now)
if not slots:
return 0.0
total = 0.0
rows = conn.execute(
active_risk: dict[tuple[str, str], float] = {}
for r in conn.execute(
"""SELECT symbol, direction, lots, entry_price, stop_loss, take_profit, open_time
FROM trade_order_monitors
WHERE open_time IS NOT NULL AND trim(open_time) <> ''"""
).fetchall()
for r in rows:
WHERE status='active' AND open_time IS NOT NULL AND trim(open_time) <> ''"""
).fetchall():
if not _opened_in_trading_day(r["open_time"], now):
continue
entry = float(r["entry_price"] or 0)
if entry <= 0:
key = _position_slot_key(r["symbol"], r["direction"])
if key not in slots:
continue
sl = float(r["stop_loss"] if r["stop_loss"] is not None else entry)
tp = float(r["take_profit"] if r["take_profit"] is not None else entry)
lots = int(r["lots"] or 0)
if lots <= 0:
amt = _risk_amount_for_monitor_row(r, equity)
if amt > 0:
active_risk[key] = amt
closed_risk: dict[tuple[str, str], float] = {}
for r in conn.execute(
"""SELECT symbol, direction, pnl_net, open_time
FROM trade_logs
WHERE open_time IS NOT NULL AND trim(open_time) <> ''"""
).fetchall():
if not _opened_in_trading_day(r["open_time"], now):
continue
m = calc_position_metrics(
r["direction"] or "long",
entry,
sl,
tp,
lots,
entry,
equity,
r["symbol"] or "",
)
total += float(m.get("risk_amount") or 0)
key = _position_slot_key(r["symbol"], r["direction"])
if key not in slots or key in active_risk:
continue
loss = max(0.0, -float(r["pnl_net"] or 0))
if loss > 0:
closed_risk[key] = max(closed_risk.get(key, 0.0), loss)
for r in conn.execute(
"""SELECT symbol, direction, lots, entry_price, stop_loss, take_profit, open_time
FROM trade_order_monitors
WHERE status='closed' AND open_time IS NOT NULL AND trim(open_time) <> ''
ORDER BY id DESC"""
).fetchall():
if not _opened_in_trading_day(r["open_time"], now):
continue
key = _position_slot_key(r["symbol"], r["direction"])
if key not in slots or key in active_risk or key in closed_risk:
continue
amt = _risk_amount_for_monitor_row(r, equity)
if amt > 0:
closed_risk[key] = amt
total = sum(active_risk.values()) + sum(closed_risk.values())
if total <= 0:
return 0.0
return round(total / equity * 100, 2)