Label night-session products and hide day-only symbols at night.

Mark tradable varieties with a night tag; during 21:00-02:30 filter out index futures and other products without night sessions from symbol picker and recommend list.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-26 22:27:47 +08:00
parent f2940d41e9
commit 7f8b4cfefd
11 changed files with 193 additions and 13 deletions
+62 -5
View File
@@ -87,6 +87,38 @@ PRODUCT_CATEGORIES = ["贵金属", "有色金属", "黑色金属", "能源化工
for _p in PRODUCTS:
_p["category"] = PRODUCT_CATEGORY_MAP.get(_p["ths"], "其他")
# 无夜盘品种(日盘-only):中金所股指、大商所鸡蛋/生猪等
NO_NIGHT_SESSION_THS = frozenset({"IF", "IH", "IC", "IM", "jd", "lh"})
def product_has_night_session(ths_or_product) -> bool:
"""品种是否参与夜盘交易。"""
if isinstance(ths_or_product, dict):
ths = (ths_or_product.get("ths") or "").strip()
else:
ths = (ths_or_product or "").strip()
if not ths:
return True
m = re.match(r"^([A-Za-z]+)", ths)
letters = m.group(1) if m else ths
return letters not in NO_NIGHT_SESSION_THS and letters.upper() not in NO_NIGHT_SESSION_THS
def filter_for_trading_session(rows: list[dict]) -> list[dict]:
"""夜盘时段隐藏无夜盘品种。"""
from market_sessions import is_night_trading_session
if not is_night_trading_session():
return rows
out: list[dict] = []
for row in rows:
if row.get("has_night_session") is False:
continue
ths = row.get("ths") or row.get("ths_code") or ""
if row.get("has_night_session") is True or product_has_night_session(ths):
out.append(row)
return out
def product_category(ths: str) -> str:
return PRODUCT_CATEGORY_MAP.get((ths or "").strip(), "其他")
@@ -341,15 +373,22 @@ def resolve_main_contract(product: dict) -> Optional[dict]:
}
if best:
best = _enrich_item(best, product)
_MAIN_CACHE[cache_key] = (now, best)
return best
def _enrich_item(item: dict) -> dict:
def _enrich_item(item: dict, product: Optional[dict] = None) -> dict:
out = dict(item)
if not out.get("input_label"):
out["input_label"] = f"{out.get('name', '')} {out.get('ths_code', '')}".strip()
out["near_expiry"] = is_near_expiry_main(out.get("ths_code", ""))
if product is None and out.get("ths_code"):
product = _product_for_contract_code(out["ths_code"])
if product is not None:
out["has_night_session"] = product_has_night_session(product)
elif "has_night_session" not in out:
out["has_night_session"] = product_has_night_session(out.get("ths_code") or "")
return out
@@ -365,7 +404,7 @@ def refresh_main_index():
try:
main = fut.result()
if main:
new_idx[product["sina"]] = _enrich_item(main)
new_idx[product["sina"]] = _enrich_item(main, product)
except Exception:
pass
with _main_index_lock:
@@ -389,7 +428,7 @@ def _start_warm_thread():
def _stub_main_contract(product: dict) -> dict:
"""缓存未就绪时的快速占位(当月合约),避免首次打开搜索为空。"""
today = date.today()
return _enrich_item(_make_symbol_item(product, today.year, today.month, 0))
return _enrich_item(_make_symbol_item(product, today.year, today.month, 0), product)
def _product_matches(product: dict, q_lower: str) -> bool:
@@ -428,12 +467,16 @@ def search_symbols(query: str) -> list:
return []
q_lower = q.lower()
from market_sessions import is_night_trading_session
night_only = is_night_trading_session()
with _main_index_lock:
index = dict(_main_index)
index_ready = bool(index)
scored: list[tuple[int, dict]] = []
for p in PRODUCTS:
if night_only and not product_has_night_session(p):
continue
if not _product_matches(p, q_lower):
continue
main = index.get(p["sina"])
@@ -444,6 +487,7 @@ def search_symbols(query: str) -> list:
scored.sort(key=lambda x: -x[0])
results = [item for _, item in scored[:12]]
results = filter_for_trading_session(results)
if not results and len(q) >= 3:
codes = ths_to_codes(q)
@@ -460,10 +504,19 @@ def search_symbols(query: str) -> list:
"display": f"{name} ({codes['ths_code']})",
"volume": raw.get("volume", 0) if raw else 0,
}))
results = filter_for_trading_session(results)
return results
def enrich_recommend_row(row: dict) -> dict:
"""补全推荐行字段(含是否夜盘)。"""
out = dict(row)
ths = out.get("ths") or ""
out["has_night_session"] = product_has_night_session(ths)
return out
_THS_TO_PRODUCT = {p["ths"]: p for p in PRODUCTS}
for _p in PRODUCTS:
_THS_TO_PRODUCT.setdefault(_p["ths"].lower(), _p)
@@ -531,7 +584,7 @@ def _item_from_recommend_row(row: dict, product: dict) -> Optional[dict]:
}
if max_lots is not None:
item["max_lots"] = max_lots
return _enrich_item(item)
return _enrich_item(item, product)
with _main_index_lock:
main = _main_index.get(product["sina"])
@@ -539,7 +592,7 @@ def _item_from_recommend_row(row: dict, product: dict) -> Optional[dict]:
item = dict(main)
if max_lots is not None:
item["max_lots"] = max_lots
return _enrich_item(item)
return _enrich_item(item, product)
item = _stub_main_contract(product)
if max_lots is not None:
@@ -563,6 +616,10 @@ def list_recommended_symbols_grouped(recommend_rows: list[dict]) -> list[dict]:
product = _product_for_ths(ths_key)
if not product:
continue
if not product_has_night_session(product):
from market_sessions import is_night_trading_session
if is_night_trading_session():
continue
seen.add(ths_key)
item = _item_from_recommend_row(row, product)
if not item: