开单计划增加决策理由;品种联想加速;复盘支持品种匹配

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-15 13:15:06 +08:00
parent db2443273f
commit eaf72a13fc
5 changed files with 196 additions and 43 deletions
+108 -13
View File
@@ -3,7 +3,9 @@
展示同花顺合约代码(ag2608);行情默认新浪,机构用户可通过环境变量启用同花顺 iFinD。
"""
import re
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import date
from typing import Optional
@@ -64,6 +66,9 @@ PRODUCTS = [
_MAIN_CACHE: dict[str, tuple[float, dict]] = {}
_CACHE_TTL = 300
_main_index_lock = threading.Lock()
_main_index: dict[str, dict] = {}
_main_index_ts = 0.0
def build_ths_code(product: dict, year: int, month: int) -> str:
@@ -164,14 +169,16 @@ def ths_to_sina_code(ths_code: str) -> Optional[str]:
def _make_symbol_item(product: dict, year: int, month: int, volume: float) -> dict:
ths = build_ths_code(product, year, month)
name = product["name"]
return {
"name": product["name"],
"name": name,
"ths_code": ths,
"market_code": build_ths_full_code(product, year, month),
"sina_code": build_sina_code(product, year, month),
"exchange": product["exchange"],
"contract": f"主力 {ths}",
"display": f"{product['name']} 主力 {ths}",
"display": f"{name} 主力 {ths}",
"input_label": f"{name} {ths}",
"volume": volume,
}
@@ -218,6 +225,7 @@ def resolve_main_contract(product: dict) -> Optional[dict]:
"exchange": product["exchange"],
"contract": f"主力连续 {ths_main}",
"display": f"{product['name']} 主力连续 {ths_main}",
"input_label": f"{product['name']} {ths_main}",
"volume": raw.get("volume", 0),
}
@@ -226,26 +234,110 @@ def resolve_main_contract(product: dict) -> Optional[dict]:
return best
def _enrich_item(item: dict) -> dict:
out = dict(item)
if not out.get("input_label"):
out["input_label"] = f"{out.get('name', '')} {out.get('ths_code', '')}".strip()
return out
def refresh_main_index():
"""后台预热全部品种主力合约,搜索时只读本地缓存。"""
global _main_index, _main_index_ts
new_idx: dict[str, dict] = {}
with ThreadPoolExecutor(max_workers=10) as pool:
futures = {pool.submit(resolve_main_contract, p): p for p in PRODUCTS}
for fut in as_completed(futures):
product = futures[fut]
try:
main = fut.result()
if main:
new_idx[product["sina"]] = _enrich_item(main)
except Exception:
pass
with _main_index_lock:
_main_index = new_idx
_main_index_ts = time.time()
def _warm_loop():
while True:
try:
refresh_main_index()
except Exception:
pass
time.sleep(_CACHE_TTL)
def _start_warm_thread():
threading.Thread(target=_warm_loop, daemon=True).start()
def _stub_main_contract(product: dict) -> dict:
"""缓存未就绪时的快速占位(当月合约),避免首次打开搜索为空。"""
today = date.today()
return _enrich_item(_make_symbol_item(product, today.year, today.month, 0))
def _product_matches(product: dict, q_lower: str) -> bool:
name_lower = product["name"].lower()
if q_lower in name_lower:
return True
if len(q_lower) >= 2:
ths_lower = product["ths"].lower()
sina_lower = product["sina"].lower()
if q_lower in ths_lower or q_lower in sina_lower:
return True
return False
def _match_score(product: dict, q_lower: str) -> int:
name_lower = product["name"].lower()
if name_lower == q_lower:
return 200
if name_lower.startswith(q_lower):
return 150
if q_lower in name_lower:
return 100
ths_lower = product["ths"].lower()
if ths_lower == q_lower:
return 90
if ths_lower.startswith(q_lower):
return 70
if product["sina"].lower() == q_lower:
return 80
return 10
def search_symbols(query: str) -> list:
q = query.strip().lower()
q = query.strip()
if not q:
return []
results = []
q_lower = q.lower()
with _main_index_lock:
index = dict(_main_index)
index_ready = bool(index)
scored: list[tuple[int, dict]] = []
for p in PRODUCTS:
name = p["name"]
if q not in name.lower() and q not in p["ths"].lower() and q not in p["sina"].lower():
if not _product_matches(p, q_lower):
continue
main = resolve_main_contract(p)
main = index.get(p["sina"])
if not main and not index_ready:
main = _stub_main_contract(p)
if main:
results.append(main)
scored.append((_match_score(p, q_lower), main))
scored.sort(key=lambda x: -x[0])
results = [item for _, item in scored[:12]]
if not results and len(q) >= 3:
codes = ths_to_codes(query.strip())
codes = ths_to_codes(q)
if codes:
raw = fetch_raw_for_volume(codes["sina_code"])
name = raw["name"] if raw else query.strip()
results.append({
name = raw["name"] if raw else q
results.append(_enrich_item({
"name": name,
"ths_code": codes["ths_code"],
"market_code": codes["market_code"],
@@ -254,9 +346,12 @@ def search_symbols(query: str) -> list:
"contract": codes["ths_code"],
"display": f"{name} ({codes['ths_code']})",
"volume": raw.get("volume", 0) if raw else 0,
})
}))
return results[:12]
return results
_start_warm_thread()
def get_price(market_code: str, sina_code: str = "") -> Optional[float]: