Fix dashboard position limit flicker by unifying active count across APIs.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -274,6 +274,58 @@ def count_active_trade_monitors(conn) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _position_keys_from_rows(rows: list) -> set[tuple[str, str]]:
|
||||
keys: set[tuple[str, str]] = set()
|
||||
for p in rows or []:
|
||||
lots = int(p.get("lots") or 0)
|
||||
if lots <= 0:
|
||||
continue
|
||||
sym = (
|
||||
p.get("symbol")
|
||||
or p.get("symbol_code")
|
||||
or p.get("ths_code")
|
||||
or ""
|
||||
).strip().lower()
|
||||
direction = (p.get("direction") or "long").strip().lower()
|
||||
if sym:
|
||||
keys.add((sym, direction))
|
||||
return keys
|
||||
|
||||
|
||||
def effective_active_position_count(
|
||||
conn,
|
||||
mode: str,
|
||||
*,
|
||||
ctp_connected: Optional[bool] = None,
|
||||
) -> int:
|
||||
"""风控持仓数:柜台/快照实际持仓优先,本地监控作兜底。"""
|
||||
monitor_count = count_active_trade_monitors(conn)
|
||||
if ctp_connected is None:
|
||||
try:
|
||||
from modules.ctp.vnpy_bridge import ctp_status
|
||||
|
||||
ctp_connected = bool(ctp_status(mode).get("connected"))
|
||||
except Exception:
|
||||
ctp_connected = False
|
||||
if not ctp_connected:
|
||||
return monitor_count
|
||||
keys: set[tuple[str, str]] = set()
|
||||
try:
|
||||
from modules.ctp.ctp_trading_state import trading_state
|
||||
|
||||
keys |= _position_keys_from_rows(trading_state.get_positions())
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
from modules.trading.position_stream import position_hub
|
||||
|
||||
snap = position_hub.get_snapshot() or {}
|
||||
keys |= _position_keys_from_rows(snap.get("rows"))
|
||||
except Exception:
|
||||
pass
|
||||
return max(monitor_count, len(keys))
|
||||
|
||||
|
||||
def parse_mood_issues(raw: Any) -> list[str]:
|
||||
if raw is None:
|
||||
return []
|
||||
|
||||
@@ -62,6 +62,7 @@ def build_risk_overview(
|
||||
daily_position_limit,
|
||||
daily_trading_risk_pct_limit,
|
||||
daily_trading_risk_used_pct,
|
||||
effective_active_position_count,
|
||||
ensure_account_risk_schema,
|
||||
get_risk_status,
|
||||
manual_close_daily_limit,
|
||||
@@ -76,10 +77,22 @@ def build_risk_overview(
|
||||
get_max_margin_pct,
|
||||
get_roll_max_margin_pct,
|
||||
get_sizing_mode,
|
||||
get_trading_mode,
|
||||
)
|
||||
|
||||
ensure_account_risk_schema(conn)
|
||||
risk = dict(get_risk_status(conn, equity=equity) or {})
|
||||
mode = get_trading_mode(get_setting)
|
||||
ctp_connected = False
|
||||
try:
|
||||
from modules.ctp.vnpy_bridge import ctp_status
|
||||
|
||||
ctp_connected = bool(ctp_status(mode).get("connected"))
|
||||
except Exception:
|
||||
pass
|
||||
active_n = effective_active_position_count(
|
||||
conn, mode, ctp_connected=ctp_connected,
|
||||
)
|
||||
risk = dict(get_risk_status(conn, equity=equity, active_count=active_n) or {})
|
||||
row = conn.execute("SELECT * FROM account_risk_state WHERE id=1").fetchone()
|
||||
td = trading_day_label()
|
||||
stored_td = str(row["trading_day"] or "") if row else ""
|
||||
|
||||
@@ -69,6 +69,7 @@ from modules.trading.sl_tp_guard import (
|
||||
from risk.account_risk_lib import (
|
||||
assert_can_open,
|
||||
count_active_trade_monitors,
|
||||
effective_active_position_count,
|
||||
get_risk_status,
|
||||
on_mood_journal_freeze,
|
||||
on_user_initiated_close,
|
||||
@@ -972,27 +973,11 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
*,
|
||||
ctp_connected: Optional[bool] = None,
|
||||
) -> int:
|
||||
"""风控持仓数以柜台/快照实际持仓优先,本地监控作兜底。"""
|
||||
monitor_count = count_active_trade_monitors(conn)
|
||||
if ctp_connected is None:
|
||||
ctp_connected = bool(_cached_ctp_status(mode).get("connected"))
|
||||
if not ctp_connected:
|
||||
return monitor_count
|
||||
keys: set[tuple[str, str]] = set()
|
||||
for p in _positions_for_monitor_restore(mode, allow_ctp=False):
|
||||
lots = int(p.get("lots") or 0)
|
||||
if lots <= 0:
|
||||
continue
|
||||
sym = (
|
||||
p.get("symbol")
|
||||
or p.get("symbol_code")
|
||||
or p.get("ths_code")
|
||||
or ""
|
||||
).strip().lower()
|
||||
direction = (p.get("direction") or "long").strip().lower()
|
||||
if sym:
|
||||
keys.add((sym, direction))
|
||||
return max(monitor_count, len(keys))
|
||||
return effective_active_position_count(
|
||||
conn, mode, ctp_connected=ctp_connected,
|
||||
)
|
||||
|
||||
def _build_pending_orders(conn, mode: str) -> list[dict]:
|
||||
pending: list[dict] = []
|
||||
@@ -2175,7 +2160,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
||||
capital = _capital(conn)
|
||||
risk = get_risk_status(
|
||||
conn,
|
||||
active_count=count_active_trade_monitors(conn),
|
||||
active_count=_effective_active_position_count(conn, mode),
|
||||
equity=capital,
|
||||
)
|
||||
syncing = bool(ctp_st.get("connected") or ctp_st.get("connecting"))
|
||||
|
||||
@@ -352,16 +352,41 @@
|
||||
|
||||
function applyRisk(risk, account) {
|
||||
if (!riskGridEl || !risk) return;
|
||||
if (risk.limits && Object.keys(risk.limits).length) {
|
||||
var isFullRisk = risk.limits && Object.keys(risk.limits).length;
|
||||
if (isFullRisk) {
|
||||
lastRiskPayload = risk;
|
||||
} else if (lastRiskPayload) {
|
||||
var incomingSt = risk.status || {};
|
||||
var prevSt = lastRiskPayload.status || {};
|
||||
risk = {
|
||||
enabled: lastRiskPayload.enabled,
|
||||
limits: lastRiskPayload.limits,
|
||||
manual_close_count_today: lastRiskPayload.manual_close_count_today,
|
||||
margin_pct_used: lastRiskPayload.margin_pct_used,
|
||||
status: risk.status || lastRiskPayload.status,
|
||||
daily_open_count: risk.daily_open_count != null
|
||||
? risk.daily_open_count
|
||||
: lastRiskPayload.daily_open_count,
|
||||
daily_risk_used_pct: risk.daily_risk_used_pct != null
|
||||
? risk.daily_risk_used_pct
|
||||
: lastRiskPayload.daily_risk_used_pct,
|
||||
status: Object.assign({}, prevSt, incomingSt),
|
||||
};
|
||||
var incActive = incomingSt.active_count;
|
||||
var prevActive = prevSt.active_count;
|
||||
if (incActive != null && prevActive != null) {
|
||||
var incN = Number(incActive);
|
||||
var prevN = Number(prevActive);
|
||||
var marginUsed = account && account.margin_used != null
|
||||
? Number(account.margin_used)
|
||||
: 0;
|
||||
if (
|
||||
!isNaN(incN) && !isNaN(prevN)
|
||||
&& incN < prevN
|
||||
&& (marginUsed > 0 || prevN > 0)
|
||||
) {
|
||||
risk.status.active_count = prevN;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (account && account.equity > 0 && account.margin_used != null) {
|
||||
risk.margin_pct_used = Math.round(account.margin_used / account.equity * 10000) / 100;
|
||||
@@ -850,7 +875,10 @@
|
||||
if (!data) return;
|
||||
if (data.ctp_status) updateCtpBadge(data.ctp_status);
|
||||
if (data.risk_status) {
|
||||
applyRisk({ status: data.risk_status });
|
||||
applyRisk(
|
||||
{ status: data.risk_status },
|
||||
{ equity: data.capital, margin_used: data.margin_used },
|
||||
);
|
||||
}
|
||||
if (data.trading_mode_label && modeBadge) {
|
||||
modeBadge.textContent = data.trading_mode_label;
|
||||
|
||||
Reference in New Issue
Block a user