开单计划增加决策理由;品种联想加速;复盘支持品种匹配
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+108
-13
@@ -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]:
|
||||
|
||||
Reference in New Issue
Block a user