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)
|
return oms >= int(trading_day_start(now).timestamp() * 1000)
|
||||||
|
|
||||||
|
|
||||||
def count_daily_opens(conn, now: Optional[datetime] = None) -> int:
|
def _daily_open_slots(conn, now: Optional[datetime] = None) -> set[tuple[str, str]]:
|
||||||
rows = conn.execute(
|
"""当日开仓槽位:按品种+方向去重(含已平),避免监控复活产生重复计数。"""
|
||||||
"SELECT open_time FROM trade_order_monitors "
|
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) <> ''"
|
"WHERE open_time IS NOT NULL AND trim(open_time) <> ''"
|
||||||
).fetchall()
|
).fetchall():
|
||||||
return sum(1 for r in rows if _opened_in_trading_day(r["open_time"], now))
|
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(
|
def daily_trading_risk_used_pct(
|
||||||
conn, equity: float, now: Optional[datetime] = None,
|
conn, equity: float, now: Optional[datetime] = None,
|
||||||
) -> Optional[float]:
|
) -> Optional[float]:
|
||||||
|
"""当日交易风险占权益(%):每品种槽位只计一次。
|
||||||
|
|
||||||
|
- 仍持仓:按止损距离算风险金额(以损定仓口径)
|
||||||
|
- 已平仓:按当日已实现亏损计(pnl_net<0),不再重复累加历史监控行
|
||||||
|
"""
|
||||||
if equity <= 0:
|
if equity <= 0:
|
||||||
return None
|
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
|
active_risk: dict[tuple[str, str], float] = {}
|
||||||
rows = conn.execute(
|
for r in conn.execute(
|
||||||
"""SELECT symbol, direction, lots, entry_price, stop_loss, take_profit, open_time
|
"""SELECT symbol, direction, lots, entry_price, stop_loss, take_profit, open_time
|
||||||
FROM trade_order_monitors
|
FROM trade_order_monitors
|
||||||
WHERE open_time IS NOT NULL AND trim(open_time) <> ''"""
|
WHERE status='active' AND open_time IS NOT NULL AND trim(open_time) <> ''"""
|
||||||
).fetchall()
|
).fetchall():
|
||||||
for r in rows:
|
|
||||||
if not _opened_in_trading_day(r["open_time"], now):
|
if not _opened_in_trading_day(r["open_time"], now):
|
||||||
continue
|
continue
|
||||||
entry = float(r["entry_price"] or 0)
|
key = _position_slot_key(r["symbol"], r["direction"])
|
||||||
if entry <= 0:
|
if key not in slots:
|
||||||
continue
|
continue
|
||||||
sl = float(r["stop_loss"] if r["stop_loss"] is not None else entry)
|
amt = _risk_amount_for_monitor_row(r, equity)
|
||||||
tp = float(r["take_profit"] if r["take_profit"] is not None else entry)
|
if amt > 0:
|
||||||
lots = int(r["lots"] or 0)
|
active_risk[key] = amt
|
||||||
if lots <= 0:
|
|
||||||
|
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
|
continue
|
||||||
m = calc_position_metrics(
|
key = _position_slot_key(r["symbol"], r["direction"])
|
||||||
r["direction"] or "long",
|
if key not in slots or key in active_risk:
|
||||||
entry,
|
continue
|
||||||
sl,
|
loss = max(0.0, -float(r["pnl_net"] or 0))
|
||||||
tp,
|
if loss > 0:
|
||||||
lots,
|
closed_risk[key] = max(closed_risk.get(key, 0.0), loss)
|
||||||
entry,
|
|
||||||
equity,
|
for r in conn.execute(
|
||||||
r["symbol"] or "",
|
"""SELECT symbol, direction, lots, entry_price, stop_loss, take_profit, open_time
|
||||||
)
|
FROM trade_order_monitors
|
||||||
total += float(m.get("risk_amount") or 0)
|
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:
|
if total <= 0:
|
||||||
return 0.0
|
return 0.0
|
||||||
return round(total / equity * 100, 2)
|
return round(total / equity * 100, 2)
|
||||||
|
|||||||
Reference in New Issue
Block a user