feat: 导航开关与 CTP 柜台手续费
系统设置可开关五类导航;手续费默认从 CTP 查询同步,本地/AKShare 作离线兜底;补充 FEES.md。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -86,6 +86,9 @@ class CtpBridge:
|
||||
self._connected_mode: Optional[str] = None
|
||||
self._last_error: str = ""
|
||||
self._connect_lock = threading.Lock()
|
||||
self._commission_waiters: dict[int, threading.Event] = {}
|
||||
self._commission_results: dict[int, dict] = {}
|
||||
self._commission_hooked = False
|
||||
self._init_engine()
|
||||
|
||||
def _init_engine(self) -> None:
|
||||
@@ -184,6 +187,7 @@ class CtpBridge:
|
||||
self._connected_mode = mode
|
||||
self._last_error = ""
|
||||
logger.info("CTP 已连接 [%s] account=%s", mode, len(accounts))
|
||||
self._schedule_fee_sync(mode)
|
||||
return
|
||||
time.sleep(0.5)
|
||||
finally:
|
||||
@@ -213,6 +217,71 @@ class CtpBridge:
|
||||
def mark_disconnected(self) -> None:
|
||||
self._connected_mode = None
|
||||
|
||||
def _schedule_fee_sync(self, mode: str) -> None:
|
||||
def _run() -> None:
|
||||
try:
|
||||
from ctp_fee_sync import sync_fees_from_ctp
|
||||
n, msg = sync_fees_from_ctp(mode, max_symbols=60)
|
||||
logger.info("CTP 手续费同步: %s", msg if n else msg)
|
||||
except Exception as exc:
|
||||
logger.debug("CTP 手续费后台同步: %s", exc)
|
||||
|
||||
threading.Thread(target=_run, daemon=True, name="ctp-fee-sync").start()
|
||||
|
||||
def _ensure_commission_callback(self) -> None:
|
||||
if self._commission_hooked or not self._engine:
|
||||
return
|
||||
try:
|
||||
gw = self._engine.get_gateway(GATEWAY_NAME)
|
||||
td = gw.td_api
|
||||
except Exception:
|
||||
return
|
||||
bridge = self
|
||||
|
||||
def on_rsp(data: dict, error: dict, reqid: int, last: bool) -> None:
|
||||
if data and data.get("InstrumentID"):
|
||||
bridge._commission_results[reqid] = dict(data)
|
||||
ev = bridge._commission_waiters.get(reqid)
|
||||
if last and ev:
|
||||
ev.set()
|
||||
|
||||
td.onRspQryInstrumentCommissionRate = on_rsp # type: ignore[method-assign]
|
||||
self._commission_hooked = True
|
||||
|
||||
def query_instrument_commission(self, ths_code: str, *, mode: str) -> dict:
|
||||
"""查询单合约 CTP 手续费率(需已连接)。"""
|
||||
if self._connected_mode != mode or not self._engine:
|
||||
return {}
|
||||
try:
|
||||
from ctp_symbol import ths_to_vnpy_symbol
|
||||
sym, _ = ths_to_vnpy_symbol(ths_code)
|
||||
gw = self._engine.get_gateway(GATEWAY_NAME)
|
||||
td = gw.td_api
|
||||
except Exception as exc:
|
||||
logger.debug("commission query init: %s", exc)
|
||||
return {}
|
||||
if not getattr(td, "login_status", False):
|
||||
return {}
|
||||
if not hasattr(td, "reqQryInstrumentCommissionRate"):
|
||||
return {}
|
||||
self._ensure_commission_callback()
|
||||
reqid = int(getattr(td, "reqid", 0)) + 1
|
||||
td.reqid = reqid
|
||||
ev = threading.Event()
|
||||
self._commission_waiters[reqid] = ev
|
||||
req = {
|
||||
"BrokerID": td.brokerid,
|
||||
"InvestorID": td.userid,
|
||||
"InstrumentID": sym,
|
||||
}
|
||||
ret = td.reqQryInstrumentCommissionRate(req, reqid)
|
||||
if ret != 0:
|
||||
self._commission_waiters.pop(reqid, None)
|
||||
return {}
|
||||
ev.wait(timeout=8)
|
||||
self._commission_waiters.pop(reqid, None)
|
||||
return self._commission_results.pop(reqid, {})
|
||||
|
||||
def get_account(self) -> dict[str, Any]:
|
||||
if not self._engine:
|
||||
return {}
|
||||
|
||||
Reference in New Issue
Block a user