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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user