Show product name, main contract badge, and exchange on position cards.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+16
-6
@@ -67,7 +67,7 @@ from strategy.strategy_roll_lib import preview_roll
|
|||||||
from strategy.strategy_snapshot_lib import list_snapshots, save_snapshot
|
from strategy.strategy_snapshot_lib import list_snapshots, save_snapshot
|
||||||
from strategy.strategy_trend_lib import compute_trend_plan_futures, trend_dca_level_reached
|
from strategy.strategy_trend_lib import compute_trend_plan_futures, trend_dca_level_reached
|
||||||
from strategy.strategy_snapshot_lib import STRATEGY_ROLL, STRATEGY_TREND
|
from strategy.strategy_snapshot_lib import STRATEGY_ROLL, STRATEGY_TREND
|
||||||
from symbols import ths_to_codes, resolve_main_contract, PRODUCTS, PRODUCT_CATEGORIES
|
from symbols import ths_to_codes, resolve_main_contract, PRODUCTS, PRODUCT_CATEGORIES, position_symbol_meta
|
||||||
from trading_context import (
|
from trading_context import (
|
||||||
TRADING_MODE_LIVE,
|
TRADING_MODE_LIVE,
|
||||||
TRADING_MODE_SIM,
|
TRADING_MODE_SIM,
|
||||||
@@ -112,6 +112,16 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
return "固定金额"
|
return "固定金额"
|
||||||
return "固定手数"
|
return "固定手数"
|
||||||
|
|
||||||
|
def _symbol_display_fields(sym: str) -> dict:
|
||||||
|
meta = position_symbol_meta(sym)
|
||||||
|
name = meta.get("name") or sym
|
||||||
|
return {
|
||||||
|
"symbol": name,
|
||||||
|
"symbol_name": name,
|
||||||
|
"symbol_exchange": meta.get("exchange") or "",
|
||||||
|
"symbol_is_main": bool(meta.get("is_main")),
|
||||||
|
}
|
||||||
|
|
||||||
def _schedule_recommend_refresh() -> None:
|
def _schedule_recommend_refresh() -> None:
|
||||||
from db_conn import DB_PATH
|
from db_conn import DB_PATH
|
||||||
|
|
||||||
@@ -338,12 +348,12 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
lots = int(mon.get("lots") or 0)
|
lots = int(mon.get("lots") or 0)
|
||||||
base = {
|
base = {
|
||||||
"symbol_code": sym,
|
"symbol_code": sym,
|
||||||
"symbol": mon.get("symbol_name") or sym,
|
|
||||||
"direction": direction,
|
"direction": direction,
|
||||||
"direction_label": "做多" if direction == "long" else "做空",
|
"direction_label": "做多" if direction == "long" else "做空",
|
||||||
"lots": lots,
|
"lots": lots,
|
||||||
"source": "monitor",
|
"source": "monitor",
|
||||||
"monitor_id": mon.get("id"),
|
"monitor_id": mon.get("id"),
|
||||||
|
**_symbol_display_fields(sym),
|
||||||
}
|
}
|
||||||
sl = mon.get("stop_loss")
|
sl = mon.get("stop_loss")
|
||||||
tp = mon.get("take_profit")
|
tp = mon.get("take_profit")
|
||||||
@@ -368,7 +378,6 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
sym = mon.get("symbol") or ""
|
sym = mon.get("symbol") or ""
|
||||||
pending.append({
|
pending.append({
|
||||||
"symbol_code": sym,
|
"symbol_code": sym,
|
||||||
"symbol": mon.get("symbol_name") or sym,
|
|
||||||
"direction": mon.get("direction") or "long",
|
"direction": mon.get("direction") or "long",
|
||||||
"direction_label": "做多" if (mon.get("direction") or "long") == "long" else "做空",
|
"direction_label": "做多" if (mon.get("direction") or "long") == "long" else "做空",
|
||||||
"lots": int(mon.get("lots") or 0),
|
"lots": int(mon.get("lots") or 0),
|
||||||
@@ -379,6 +388,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
"monitor_id": mon.get("id"),
|
"monitor_id": mon.get("id"),
|
||||||
"can_cancel_order": is_trading_session(),
|
"can_cancel_order": is_trading_session(),
|
||||||
"cancel_allowed": is_trading_session(),
|
"cancel_allowed": is_trading_session(),
|
||||||
|
**_symbol_display_fields(sym),
|
||||||
})
|
})
|
||||||
ctp_st = ctp_status(mode)
|
ctp_st = ctp_status(mode)
|
||||||
if ctp_st.get("connected"):
|
if ctp_st.get("connected"):
|
||||||
@@ -391,7 +401,6 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
label = "平仓委托"
|
label = "平仓委托"
|
||||||
pending.append({
|
pending.append({
|
||||||
"symbol_code": sym,
|
"symbol_code": sym,
|
||||||
"symbol": sym,
|
|
||||||
"direction": o.get("direction") or "long",
|
"direction": o.get("direction") or "long",
|
||||||
"direction_label": "做多" if o.get("direction") == "long" else "做空",
|
"direction_label": "做多" if o.get("direction") == "long" else "做空",
|
||||||
"lots": int(o.get("lots") or 0),
|
"lots": int(o.get("lots") or 0),
|
||||||
@@ -402,6 +411,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
"order_id": o.get("order_id"),
|
"order_id": o.get("order_id"),
|
||||||
"can_cancel_order": is_trading_session(),
|
"can_cancel_order": is_trading_session(),
|
||||||
"cancel_allowed": is_trading_session(),
|
"cancel_allowed": is_trading_session(),
|
||||||
|
**_symbol_display_fields(sym),
|
||||||
})
|
})
|
||||||
return pending
|
return pending
|
||||||
|
|
||||||
@@ -767,8 +777,8 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
"source_label": source_label,
|
"source_label": source_label,
|
||||||
"sync_pending": ctp is None and mon is not None,
|
"sync_pending": ctp is None and mon is not None,
|
||||||
"monitor_id": mon["id"] if mon else None,
|
"monitor_id": mon["id"] if mon else None,
|
||||||
"symbol": codes.get("name", sym) if codes else (mon.get("symbol_name") if mon else sym),
|
|
||||||
"symbol_code": sym,
|
"symbol_code": sym,
|
||||||
|
**_symbol_display_fields(sym),
|
||||||
"direction": direction,
|
"direction": direction,
|
||||||
"direction_label": "做多" if direction == "long" else "做空",
|
"direction_label": "做多" if direction == "long" else "做空",
|
||||||
"lots": lots,
|
"lots": lots,
|
||||||
@@ -838,8 +848,8 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
"source_label": "委托挂单中",
|
"source_label": "委托挂单中",
|
||||||
"sync_pending": True,
|
"sync_pending": True,
|
||||||
"monitor_id": mon.get("id"),
|
"monitor_id": mon.get("id"),
|
||||||
"symbol": codes.get("name", sym) if codes else (mon.get("symbol_name") or sym),
|
|
||||||
"symbol_code": sym,
|
"symbol_code": sym,
|
||||||
|
**_symbol_display_fields(sym),
|
||||||
"direction": direction,
|
"direction": direction,
|
||||||
"direction_label": "做多" if direction == "long" else "做空",
|
"direction_label": "做多" if direction == "long" else "做空",
|
||||||
"lots": lots,
|
"lots": lots,
|
||||||
|
|||||||
@@ -65,7 +65,9 @@
|
|||||||
.gap-badge{font-size:.72rem}
|
.gap-badge{font-size:.72rem}
|
||||||
.rec-market-link{color:inherit;text-decoration:none;display:inline-flex;flex-wrap:wrap;align-items:baseline;gap:.2rem .35rem}
|
.rec-market-link{color:inherit;text-decoration:none;display:inline-flex;flex-wrap:wrap;align-items:baseline;gap:.2rem .35rem}
|
||||||
.rec-market-link:hover strong,.rec-market-link:hover .text-accent{color:var(--accent);text-decoration:underline}
|
.rec-market-link:hover strong,.rec-market-link:hover .text-accent{color:var(--accent);text-decoration:underline}
|
||||||
.rec-change-up{color:var(--profit)}
|
.pos-symbol-sub{font-size:.72rem;line-height:1.35}
|
||||||
|
.pos-main-badge{font-size:.68rem;vertical-align:middle}
|
||||||
|
.pos-change-up{color:var(--profit)}
|
||||||
.rec-change-down{color:var(--loss)}
|
.rec-change-down{color:var(--loss)}
|
||||||
#recommend .trade-table-wrap{max-height:min(70vh,520px)}
|
#recommend .trade-table-wrap{max-height:min(70vh,520px)}
|
||||||
#positions .card-body.card-scroll{flex:1;max-height:none;overflow-y:auto}
|
#positions .card-body.card-scroll{flex:1;max-height:none;overflow-y:auto}
|
||||||
|
|||||||
+26
-6
@@ -806,6 +806,25 @@
|
|||||||
return '<span class="text-muted">未开启</span>';
|
return '<span class="text-muted">未开启</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function posSymbolTitleHtml(row, extraBadges) {
|
||||||
|
extraBadges = extraBadges || '';
|
||||||
|
var name = row.symbol_name || row.symbol || '';
|
||||||
|
var code = row.symbol_code || '';
|
||||||
|
var mainBadge = row.symbol_is_main ? ' <span class="badge planned pos-main-badge">主力</span>' : '';
|
||||||
|
var title = name + mainBadge;
|
||||||
|
if (code && String(name).toLowerCase() !== String(code).toLowerCase()) {
|
||||||
|
title += ' <span class="text-accent">' + code + '</span>';
|
||||||
|
} else if (!name && code) {
|
||||||
|
title = '<span class="text-accent">' + code + '</span>';
|
||||||
|
}
|
||||||
|
return title + extraBadges;
|
||||||
|
}
|
||||||
|
|
||||||
|
function posSymbolSubHtml(row) {
|
||||||
|
if (row.symbol_exchange) return row.symbol_exchange;
|
||||||
|
return row.symbol_code || '';
|
||||||
|
}
|
||||||
|
|
||||||
function buildPendingOrderCard(row) {
|
function buildPendingOrderCard(row) {
|
||||||
var dirBadge = row.direction_label || (row.direction === 'long' ? '做多' : '做空');
|
var dirBadge = row.direction_label || (row.direction === 'long' ? '做多' : '做空');
|
||||||
var openT = (row.open_time || '').replace('T', ' ').slice(0, 16);
|
var openT = (row.open_time || '').replace('T', ' ').slice(0, 16);
|
||||||
@@ -827,10 +846,10 @@
|
|||||||
' · <span class="text-muted">约 ' + remainMin + ' 分钟内未成交自动撤单</span>';
|
' · <span class="text-muted">约 ' + remainMin + ' 分钟内未成交自动撤单</span>';
|
||||||
return (
|
return (
|
||||||
'<div class="pos-card is-pending">' +
|
'<div class="pos-card is-pending">' +
|
||||||
'<div class="pos-card-head"><div><div class="title">' + row.symbol +
|
'<div class="pos-card-head"><div><div class="title">' + posSymbolTitleHtml(row,
|
||||||
' <span class="badge dir">' + dirBadge + '</span>' +
|
' <span class="badge dir">' + dirBadge + '</span>' +
|
||||||
' <span class="badge pending">挂单中</span></div>' +
|
' <span class="badge pending">挂单中</span>') + '</div>' +
|
||||||
'<div class="text-muted" style="font-size:.72rem">' + (row.symbol_code || '') + '</div></div>' +
|
'<div class="text-muted pos-symbol-sub">' + posSymbolSubHtml(row) + '</div></div>' +
|
||||||
'<div class="pos-card-actions">' + cancelBtn + '</div></div>' +
|
'<div class="pos-card-actions">' + cancelBtn + '</div></div>' +
|
||||||
'<div class="pos-card-meta pos-card-meta-line">' + metaLine + '</div>' +
|
'<div class="pos-card-meta pos-card-meta-line">' + metaLine + '</div>' +
|
||||||
'<div class="pos-metrics">' +
|
'<div class="pos-metrics">' +
|
||||||
@@ -897,8 +916,9 @@
|
|||||||
var openLabel = '开仓';
|
var openLabel = '开仓';
|
||||||
return (
|
return (
|
||||||
'<div class="pos-card">' +
|
'<div class="pos-card">' +
|
||||||
'<div class="pos-card-head"><div><div class="title">' + row.symbol + ' <span class="badge dir">' + dirBadge + '</span></div>' +
|
'<div class="pos-card-head"><div><div class="title">' + posSymbolTitleHtml(row,
|
||||||
'<div class="text-muted" style="font-size:.72rem">' + (row.symbol_code || '') + '</div></div>' +
|
' <span class="badge dir">' + dirBadge + '</span>') + '</div>' +
|
||||||
|
'<div class="text-muted pos-symbol-sub">' + posSymbolSubHtml(row) + '</div></div>' +
|
||||||
actionBtns + '</div>' +
|
actionBtns + '</div>' +
|
||||||
'<div class="pos-card-meta pos-card-meta-line">' + metaLine + '</div>' +
|
'<div class="pos-card-meta pos-card-meta-line">' + metaLine + '</div>' +
|
||||||
'<div class="pos-metrics">' +
|
'<div class="pos-metrics">' +
|
||||||
|
|||||||
+33
@@ -458,6 +458,39 @@ def _product_for_ths(ths: str) -> Optional[dict]:
|
|||||||
return _THS_TO_PRODUCT.get(key) or _THS_TO_PRODUCT.get(key.lower())
|
return _THS_TO_PRODUCT.get(key) or _THS_TO_PRODUCT.get(key.lower())
|
||||||
|
|
||||||
|
|
||||||
|
def _product_for_contract_code(ths_code: str) -> Optional[dict]:
|
||||||
|
sym = (ths_code or "").strip()
|
||||||
|
if not sym:
|
||||||
|
return None
|
||||||
|
m = re.match(r"^([A-Za-z]+)", sym)
|
||||||
|
if not m:
|
||||||
|
return None
|
||||||
|
return _find_product_by_letters(m.group(1))
|
||||||
|
|
||||||
|
|
||||||
|
def position_symbol_meta(ths_code: str) -> dict:
|
||||||
|
"""持仓/委托展示:品种名、交易所、是否主力合约。"""
|
||||||
|
sym = (ths_code or "").strip()
|
||||||
|
if not sym:
|
||||||
|
return {"name": "", "exchange": "", "is_main": False}
|
||||||
|
product = _product_for_contract_code(sym)
|
||||||
|
if not product:
|
||||||
|
return {"name": sym, "exchange": "", "is_main": False}
|
||||||
|
codes = ths_to_codes(sym)
|
||||||
|
norm = (codes["ths_code"] if codes else sym).strip().lower()
|
||||||
|
is_main = False
|
||||||
|
with _main_index_lock:
|
||||||
|
main_item = _main_index.get(product["sina"])
|
||||||
|
if main_item:
|
||||||
|
main_ths = (main_item.get("ths_code") or "").strip().lower()
|
||||||
|
is_main = main_ths == norm or main_ths == sym.lower()
|
||||||
|
return {
|
||||||
|
"name": product["name"],
|
||||||
|
"exchange": product.get("exchange") or "",
|
||||||
|
"is_main": is_main,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _item_from_recommend_row(row: dict, product: dict) -> Optional[dict]:
|
def _item_from_recommend_row(row: dict, product: dict) -> Optional[dict]:
|
||||||
"""由可开仓缓存行快速构造下拉项(不在 HTTP 请求中解析主力)。"""
|
"""由可开仓缓存行快速构造下拉项(不在 HTTP 请求中解析主力)。"""
|
||||||
name = row.get("name") or product["name"]
|
name = row.get("name") or product["name"]
|
||||||
|
|||||||
Reference in New Issue
Block a user