fix: K线新浪历史补齐与手续费页布局及CTP批量同步
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -32,6 +32,8 @@ from fee_specs import (
|
|||||||
get_fee_multiplier,
|
get_fee_multiplier,
|
||||||
get_fee_source_mode,
|
get_fee_source_mode,
|
||||||
list_all_fee_rates,
|
list_all_fee_rates,
|
||||||
|
list_fee_rates_for_ui,
|
||||||
|
count_fee_rates_by_source,
|
||||||
load_fee_rates_from_json,
|
load_fee_rates_from_json,
|
||||||
upsert_fee_rate,
|
upsert_fee_rate,
|
||||||
)
|
)
|
||||||
@@ -1612,13 +1614,15 @@ def fees():
|
|||||||
flash(f"已保存 {product} 费率")
|
flash(f"已保存 {product} 费率")
|
||||||
return redirect(url_for("fees"))
|
return redirect(url_for("fees"))
|
||||||
|
|
||||||
rates = list_all_fee_rates()
|
rates = list_fee_rates_for_ui()
|
||||||
|
fee_counts = count_fee_rates_by_source()
|
||||||
multiplier = get_setting("fee_multiplier", "2")
|
multiplier = get_setting("fee_multiplier", "2")
|
||||||
fee_source_mode = get_fee_source_mode()
|
fee_source_mode = get_fee_source_mode()
|
||||||
ctp_st = ctp_status(mode)
|
ctp_st = ctp_status(mode)
|
||||||
return render_template(
|
return render_template(
|
||||||
"fees.html",
|
"fees.html",
|
||||||
rates=rates,
|
rates=rates,
|
||||||
|
fee_counts=fee_counts,
|
||||||
multiplier=multiplier,
|
multiplier=multiplier,
|
||||||
fee_source_mode=fee_source_mode,
|
fee_source_mode=fee_source_mode,
|
||||||
ctp_connected=bool(ctp_st.get("connected")),
|
ctp_connected=bool(ctp_st.get("connected")),
|
||||||
|
|||||||
+25
-3
@@ -67,14 +67,36 @@ def sync_fees_from_ctp(mode: str, *, max_symbols: int = 80) -> tuple[int, str]:
|
|||||||
if not bridge.ping():
|
if not bridge.ping():
|
||||||
return 0, "CTP 连接无效,请重连"
|
return 0, "CTP 连接无效,请重连"
|
||||||
|
|
||||||
|
seen: set[str] = set()
|
||||||
|
ok = 0
|
||||||
|
errors = 0
|
||||||
|
|
||||||
|
batch = bridge.query_all_commissions(mode=mode)
|
||||||
|
if batch:
|
||||||
|
for raw in batch:
|
||||||
|
inst = str(raw.get("InstrumentID") or "").strip()
|
||||||
|
product = _product_from_instrument(inst)
|
||||||
|
if not product or product in seen:
|
||||||
|
continue
|
||||||
|
seen.add(product)
|
||||||
|
try:
|
||||||
|
fields = ctp_commission_to_fee_fields(raw, inst or product)
|
||||||
|
upsert_fee_rate(product, fields)
|
||||||
|
ok += 1
|
||||||
|
except Exception as exc:
|
||||||
|
logger.debug("CTP fee batch %s: %s", inst, exc)
|
||||||
|
errors += 1
|
||||||
|
if ok > 0:
|
||||||
|
msg = f"已从 CTP 批量同步 {ok} 个品种手续费"
|
||||||
|
if errors:
|
||||||
|
msg += f"({errors} 个跳过)"
|
||||||
|
return ok, msg
|
||||||
|
|
||||||
symbols = _collect_main_ths_codes()[:max_symbols]
|
symbols = _collect_main_ths_codes()[:max_symbols]
|
||||||
|
|
||||||
if not symbols:
|
if not symbols:
|
||||||
return 0, "无主力合约列表"
|
return 0, "无主力合约列表"
|
||||||
|
|
||||||
seen: set[str] = set()
|
|
||||||
ok = 0
|
|
||||||
errors = 0
|
|
||||||
for ths in symbols:
|
for ths in symbols:
|
||||||
product = _product_from_instrument(ths)
|
product = _product_from_instrument(ths)
|
||||||
if not product or product in seen:
|
if not product or product in seen:
|
||||||
|
|||||||
@@ -296,6 +296,31 @@ def list_all_fee_rates() -> list:
|
|||||||
return [dict(r) for r in rows]
|
return [dict(r) for r in rows]
|
||||||
|
|
||||||
|
|
||||||
|
def list_fee_rates_for_ui() -> list:
|
||||||
|
"""手续费页展示:CTP 模式下 ctp 来源优先排前。"""
|
||||||
|
rows = list_all_fee_rates()
|
||||||
|
if get_fee_source_mode() == "ctp":
|
||||||
|
rows.sort(
|
||||||
|
key=lambda r: (
|
||||||
|
0 if (r.get("source") or "") == "ctp" else 1,
|
||||||
|
r.get("product") or "",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def count_fee_rates_by_source() -> dict[str, int]:
|
||||||
|
conn = _get_db()
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT source, COUNT(*) AS n FROM fee_rates GROUP BY source"
|
||||||
|
).fetchall()
|
||||||
|
conn.close()
|
||||||
|
out: dict[str, int] = {}
|
||||||
|
for row in rows:
|
||||||
|
out[str(row["source"] or "local")] = int(row["n"] or 0)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def upsert_fee_rate(product: str, fields: dict) -> None:
|
def upsert_fee_rate(product: str, fields: dict) -> None:
|
||||||
product = product.lower().strip()
|
product = product.lower().strip()
|
||||||
conn = _get_db()
|
conn = _get_db()
|
||||||
|
|||||||
+39
-6
@@ -17,6 +17,9 @@ from kline_store import ensure_kline_tables, get_cached_entry, save_bars
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
TZ = ZoneInfo("Asia/Shanghai")
|
TZ = ZoneInfo("Asia/Shanghai")
|
||||||
|
|
||||||
|
# CTP tick 聚合 bar 少于此数时,用新浪历史补齐走势
|
||||||
|
MIN_CTP_KLINE_BARS = 15
|
||||||
|
|
||||||
PERIOD_MINUTES = {
|
PERIOD_MINUTES = {
|
||||||
"1m": "1",
|
"1m": "1",
|
||||||
"3m": "3",
|
"3m": "3",
|
||||||
@@ -165,6 +168,24 @@ def _merge_bars(chunk: list) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_kline_bars(history: list, live: list) -> list:
|
||||||
|
"""新浪历史 + CTP 实时尾部(去重叠)。"""
|
||||||
|
if not history:
|
||||||
|
return list(live or [])
|
||||||
|
if not live:
|
||||||
|
return list(history)
|
||||||
|
first_live = _bar_datetime(live[0])
|
||||||
|
if not first_live:
|
||||||
|
return history + live
|
||||||
|
trimmed = []
|
||||||
|
for bar in history:
|
||||||
|
dt = _bar_datetime(bar)
|
||||||
|
if dt and dt < first_live:
|
||||||
|
trimmed.append(bar)
|
||||||
|
merged = trimmed + list(live)
|
||||||
|
return merged if merged else list(history)
|
||||||
|
|
||||||
|
|
||||||
def _weekly_from_daily(daily: list) -> list:
|
def _weekly_from_daily(daily: list) -> list:
|
||||||
if not daily:
|
if not daily:
|
||||||
return []
|
return []
|
||||||
@@ -236,6 +257,7 @@ def fetch_market_klines(
|
|||||||
source = "remote"
|
source = "remote"
|
||||||
cached_at = None
|
cached_at = None
|
||||||
ctp_connected = False
|
ctp_connected = False
|
||||||
|
ctp_bars: list = []
|
||||||
|
|
||||||
if prefer_ctp:
|
if prefer_ctp:
|
||||||
try:
|
try:
|
||||||
@@ -253,14 +275,21 @@ def fetch_market_klines(
|
|||||||
mode = "simulation"
|
mode = "simulation"
|
||||||
ctp_connected = bool(ctp_status(mode).get("connected"))
|
ctp_connected = bool(ctp_status(mode).get("connected"))
|
||||||
if ctp_connected:
|
if ctp_connected:
|
||||||
ctp_bars = fetch_ctp_klines(symbol, p, mode)
|
ctp_bars = fetch_ctp_klines(symbol, p, mode) or []
|
||||||
if ctp_bars:
|
|
||||||
bars = ctp_bars
|
|
||||||
source = "ctp"
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.debug("ctp kline fetch failed %s %s: %s", symbol, p, exc)
|
logger.debug("ctp kline fetch failed %s %s: %s", symbol, p, exc)
|
||||||
|
|
||||||
if not bars and db_path and chart_sym and not force_remote:
|
need_sina = (
|
||||||
|
force_remote
|
||||||
|
or not ctp_bars
|
||||||
|
or len(ctp_bars) < MIN_CTP_KLINE_BARS
|
||||||
|
)
|
||||||
|
|
||||||
|
if ctp_bars and len(ctp_bars) >= MIN_CTP_KLINE_BARS:
|
||||||
|
bars = ctp_bars
|
||||||
|
source = "ctp"
|
||||||
|
|
||||||
|
if not bars and db_path and chart_sym and not force_remote and need_sina:
|
||||||
try:
|
try:
|
||||||
conn = connect_db(db_path)
|
conn = connect_db(db_path)
|
||||||
cached = get_cached_entry(conn, chart_sym, p)
|
cached = get_cached_entry(conn, chart_sym, p)
|
||||||
@@ -272,9 +301,13 @@ def fetch_market_klines(
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("kline cache read failed %s %s: %s", chart_sym, p, exc)
|
logger.warning("kline cache read failed %s %s: %s", chart_sym, p, exc)
|
||||||
|
|
||||||
if force_remote or not bars:
|
if not bars or len(ctp_bars) < MIN_CTP_KLINE_BARS:
|
||||||
remote_bars = fetch_sina_klines(symbol, p)
|
remote_bars = fetch_sina_klines(symbol, p)
|
||||||
if remote_bars:
|
if remote_bars:
|
||||||
|
if ctp_bars and ctp_connected:
|
||||||
|
bars = _merge_kline_bars(remote_bars, ctp_bars)
|
||||||
|
source = "ctp+remote"
|
||||||
|
else:
|
||||||
bars = remote_bars
|
bars = remote_bars
|
||||||
source = "remote"
|
source = "remote"
|
||||||
if db_path and chart_sym and not ctp_connected:
|
if db_path and chart_sym and not ctp_connected:
|
||||||
|
|||||||
@@ -576,6 +576,7 @@
|
|||||||
|
|
||||||
function klineSourceLabel(src) {
|
function klineSourceLabel(src) {
|
||||||
if (src === 'ctp') return 'CTP';
|
if (src === 'ctp') return 'CTP';
|
||||||
|
if (src === 'ctp+remote') return '新浪+CTP';
|
||||||
if (src === 'local') return '本地缓存';
|
if (src === 'local') return '本地缓存';
|
||||||
return '新浪';
|
return '新浪';
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-17
@@ -4,6 +4,15 @@
|
|||||||
<style>
|
<style>
|
||||||
.fees-split{margin-bottom:1.25rem}
|
.fees-split{margin-bottom:1.25rem}
|
||||||
.fees-split .card{margin-bottom:0;min-height:auto}
|
.fees-split .card{margin-bottom:0;min-height:auto}
|
||||||
|
.fees-table-card .trade-table-wrap{
|
||||||
|
max-height:min(70vh,560px);
|
||||||
|
width:100%;
|
||||||
|
border:none;
|
||||||
|
border-radius:10px;
|
||||||
|
}
|
||||||
|
.fees-table-card .trade-table{min-width:1100px}
|
||||||
|
.fees-table-card .card-body{padding:.75rem 1rem 1rem}
|
||||||
|
.fees-source-stats{font-size:.78rem;margin-top:.35rem}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -36,6 +45,15 @@
|
|||||||
<span class="badge planned">CTP 未连接</span>
|
<span class="badge planned">CTP 未连接</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if fee_counts %}
|
||||||
|
<p class="fees-source-stats text-muted">
|
||||||
|
已缓存:
|
||||||
|
{% if fee_counts.get('ctp') %}<span class="badge profit">CTP {{ fee_counts.ctp }}</span>{% endif %}
|
||||||
|
{% if fee_counts.get('local') %}<span class="badge planned">local {{ fee_counts.local }}</span>{% endif %}
|
||||||
|
{% if fee_counts.get('json') %}<span class="badge planned">json {{ fee_counts.json }}</span>{% endif %}
|
||||||
|
{% if fee_counts.get('manual') %}<span class="badge planned">manual {{ fee_counts.manual }}</span>{% endif %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -63,10 +81,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card fees-table-card">
|
||||||
<h2>品种费率表</h2>
|
<h2>品种费率表</h2>
|
||||||
<div class="card-body card-scroll">
|
<div class="card-body">
|
||||||
<div class="table-responsive">
|
<div class="trade-table-wrap">
|
||||||
<table class="trade-table">
|
<table class="trade-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -79,23 +97,20 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for r in rates %}
|
{% for r in rates %}
|
||||||
|
{% set fid = 'fee-row-' ~ r.product %}
|
||||||
<tr>
|
<tr>
|
||||||
<form action="{{ url_for('fees') }}" method="post" style="display:contents">
|
|
||||||
<input type="hidden" name="action" value="save_row">
|
|
||||||
<input type="hidden" name="product" value="{{ r.product }}">
|
|
||||||
<td><strong>{{ r.product }}</strong></td>
|
<td><strong>{{ r.product }}</strong></td>
|
||||||
<td><span class="badge {% if r.source == 'ctp' %}profit{% else %}planned{% endif %}">{{ r.source or 'local' }}</span></td>
|
<td><span class="badge {% if r.source == 'ctp' %}profit{% else %}planned{% endif %}">{{ r.source or 'local' }}</span></td>
|
||||||
<td><input name="exchange" value="{{ r.exchange or '' }}" style="width:72px;padding:.3rem"></td>
|
<td><input name="exchange" form="{{ fid }}" value="{{ r.exchange or '' }}" style="width:72px;padding:.3rem"></td>
|
||||||
<td><input name="mult" type="number" value="{{ r.mult }}" style="width:56px;padding:.3rem"></td>
|
<td><input name="mult" form="{{ fid }}" type="number" value="{{ r.mult }}" style="width:56px;padding:.3rem"></td>
|
||||||
<td><input name="open_fixed" type="number" step="0.0001" value="{{ r.open_fixed }}" style="width:72px;padding:.3rem"></td>
|
<td><input name="open_fixed" form="{{ fid }}" type="number" step="0.0001" value="{{ r.open_fixed }}" style="width:72px;padding:.3rem"></td>
|
||||||
<td><input name="open_ratio" type="number" step="0.0000001" value="{{ r.open_ratio }}" style="width:88px;padding:.3rem"></td>
|
<td><input name="open_ratio" form="{{ fid }}" type="number" step="0.0000001" value="{{ r.open_ratio }}" style="width:88px;padding:.3rem"></td>
|
||||||
<td><input name="close_yesterday_fixed" type="number" step="0.0001" value="{{ r.close_yesterday_fixed }}" style="width:72px;padding:.3rem"></td>
|
<td><input name="close_yesterday_fixed" form="{{ fid }}" type="number" step="0.0001" value="{{ r.close_yesterday_fixed }}" style="width:72px;padding:.3rem"></td>
|
||||||
<td><input name="close_yesterday_ratio" type="number" step="0.0000001" value="{{ r.close_yesterday_ratio }}" style="width:88px;padding:.3rem"></td>
|
<td><input name="close_yesterday_ratio" form="{{ fid }}" type="number" step="0.0000001" value="{{ r.close_yesterday_ratio }}" style="width:88px;padding:.3rem"></td>
|
||||||
<td><input name="close_today_fixed" type="number" step="0.0001" value="{{ r.close_today_fixed }}" style="width:72px;padding:.3rem"></td>
|
<td><input name="close_today_fixed" form="{{ fid }}" type="number" step="0.0001" value="{{ r.close_today_fixed }}" style="width:72px;padding:.3rem"></td>
|
||||||
<td><input name="close_today_ratio" type="number" step="0.0000001" value="{{ r.close_today_ratio }}" style="width:88px;padding:.3rem"></td>
|
<td><input name="close_today_ratio" form="{{ fid }}" type="number" step="0.0000001" value="{{ r.close_today_ratio }}" style="width:88px;padding:.3rem"></td>
|
||||||
<td class="text-muted" style="font-size:.72rem">{{ (r.updated_at or '')[:16] }}</td>
|
<td class="text-muted" style="font-size:.72rem">{{ (r.updated_at or '')[:16] }}</td>
|
||||||
<td><button type="submit" class="btn-link">保存</button></td>
|
<td><button type="submit" form="{{ fid }}" class="btn-link">保存</button></td>
|
||||||
</form>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr><td colspan="12" class="text-muted">暂无费率,请连接 CTP 后同步</td></tr>
|
<tr><td colspan="12" class="text-muted">暂无费率,请连接 CTP 后同步</td></tr>
|
||||||
@@ -103,9 +118,18 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
{% for r in rates %}
|
||||||
|
<form id="fee-row-{{ r.product }}" action="{{ url_for('fees') }}" method="post" hidden>
|
||||||
|
<input type="hidden" name="action" value="save_row">
|
||||||
|
<input type="hidden" name="product" value="{{ r.product }}">
|
||||||
|
</form>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<p class="hint" style="margin-top:.75rem">
|
<p class="hint" style="margin-top:.75rem;padding:0 1rem 1rem">
|
||||||
公式:单边 = 固定(元/手)×手数 + 比例×价格×乘数×手数;往返 = 开仓 + 平仓(平今/平昨自动判断)。
|
公式:单边 = 固定(元/手)×手数 + 比例×价格×乘数×手数;往返 = 开仓 + 平仓(平今/平昨自动判断)。
|
||||||
|
{% if fee_source_mode == 'ctp' and ctp_connected and not fee_counts.get('ctp') %}
|
||||||
|
<br><strong class="text-loss">当前无 CTP 费率缓存,请点击「从 CTP 同步费率」。</strong>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
<div class="market-chart-empty" id="market-chart-empty">请选择合约并点击「查看」</div>
|
<div class="market-chart-empty" id="market-chart-empty">请选择合约并点击「查看」</div>
|
||||||
<div class="market-chart-loading" id="market-chart-loading">连接中…</div>
|
<div class="market-chart-loading" id="market-chart-loading">连接中…</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="hint">数据来源:{% if ctp_connected %}CTP 柜台 tick 聚合(实时价与 K 线){% else %}CTP 未连接时 K 线与报价回退新浪{% endif %}。拖拽左右平移、滚轮缩放;按住图表上下拖动可平移价格轴。可视区内自动标注最高/最低价。</p>
|
<p class="hint">数据来源:{% if ctp_connected %}报价来自 CTP;K 线历史由新浪补齐、最新 bar 由 CTP tick 更新{% else %}CTP 未连接时 K 线与报价回退新浪{% endif %}。拖拽左右平移、滚轮缩放;按住图表上下拖动可平移价格轴。可视区内自动标注最高/最低价。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
+36
-14
@@ -88,7 +88,7 @@ class CtpBridge:
|
|||||||
self._last_error: str = ""
|
self._last_error: str = ""
|
||||||
self._connect_lock = threading.Lock()
|
self._connect_lock = threading.Lock()
|
||||||
self._commission_waiters: dict[int, threading.Event] = {}
|
self._commission_waiters: dict[int, threading.Event] = {}
|
||||||
self._commission_results: dict[int, dict] = {}
|
self._commission_lists: dict[int, list] = {}
|
||||||
self._commission_hooked = False
|
self._commission_hooked = False
|
||||||
self._subscribed: set[str] = set()
|
self._subscribed: set[str] = set()
|
||||||
self._tick_hooked = False
|
self._tick_hooked = False
|
||||||
@@ -251,7 +251,7 @@ class CtpBridge:
|
|||||||
error.get("ErrorMsg") or error,
|
error.get("ErrorMsg") or error,
|
||||||
)
|
)
|
||||||
if data and data.get("InstrumentID"):
|
if data and data.get("InstrumentID"):
|
||||||
bridge._commission_results[reqid] = dict(data)
|
bridge._commission_lists.setdefault(reqid, []).append(dict(data))
|
||||||
ev = bridge._commission_waiters.get(reqid)
|
ev = bridge._commission_waiters.get(reqid)
|
||||||
if last and ev:
|
if last and ev:
|
||||||
ev.set()
|
ev.set()
|
||||||
@@ -259,21 +259,26 @@ class CtpBridge:
|
|||||||
td.onRspQryInstrumentCommissionRate = on_rsp # type: ignore[method-assign]
|
td.onRspQryInstrumentCommissionRate = on_rsp # type: ignore[method-assign]
|
||||||
self._commission_hooked = True
|
self._commission_hooked = True
|
||||||
|
|
||||||
def query_instrument_commission(self, ths_code: str, *, mode: str) -> dict:
|
def _query_commission(
|
||||||
"""查询单合约 CTP 手续费率(需已连接)。"""
|
self,
|
||||||
|
*,
|
||||||
|
mode: str,
|
||||||
|
instrument_id: str = "",
|
||||||
|
exchange_id: str = "",
|
||||||
|
timeout: float = 8,
|
||||||
|
) -> list[dict]:
|
||||||
if self._connected_mode != mode or not self._engine:
|
if self._connected_mode != mode or not self._engine:
|
||||||
return {}
|
return []
|
||||||
try:
|
try:
|
||||||
sym, ex_name = ths_to_vnpy_symbol(ths_code)
|
|
||||||
gw = self._engine.get_gateway(GATEWAY_NAME)
|
gw = self._engine.get_gateway(GATEWAY_NAME)
|
||||||
td = gw.td_api
|
td = gw.td_api
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.debug("commission query init: %s", exc)
|
logger.debug("commission query init: %s", exc)
|
||||||
return {}
|
return []
|
||||||
if not getattr(td, "login_status", False):
|
if not getattr(td, "login_status", False):
|
||||||
return {}
|
return []
|
||||||
if not hasattr(td, "reqQryInstrumentCommissionRate"):
|
if not hasattr(td, "reqQryInstrumentCommissionRate"):
|
||||||
return {}
|
return []
|
||||||
self._ensure_commission_callback()
|
self._ensure_commission_callback()
|
||||||
reqid = int(getattr(td, "reqid", 0)) + 1
|
reqid = int(getattr(td, "reqid", 0)) + 1
|
||||||
td.reqid = reqid
|
td.reqid = reqid
|
||||||
@@ -282,16 +287,33 @@ class CtpBridge:
|
|||||||
req = {
|
req = {
|
||||||
"BrokerID": td.brokerid,
|
"BrokerID": td.brokerid,
|
||||||
"InvestorID": td.userid,
|
"InvestorID": td.userid,
|
||||||
"InstrumentID": sym,
|
"InstrumentID": instrument_id or "",
|
||||||
"ExchangeID": ex_name,
|
"ExchangeID": exchange_id or "",
|
||||||
}
|
}
|
||||||
ret = td.reqQryInstrumentCommissionRate(req, reqid)
|
ret = td.reqQryInstrumentCommissionRate(req, reqid)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
self._commission_waiters.pop(reqid, None)
|
self._commission_waiters.pop(reqid, None)
|
||||||
return {}
|
return []
|
||||||
ev.wait(timeout=8)
|
ev.wait(timeout=timeout)
|
||||||
self._commission_waiters.pop(reqid, None)
|
self._commission_waiters.pop(reqid, None)
|
||||||
return self._commission_results.pop(reqid, {})
|
return self._commission_lists.pop(reqid, [])
|
||||||
|
|
||||||
|
def query_instrument_commission(self, ths_code: str, *, mode: str) -> dict:
|
||||||
|
"""查询单合约 CTP 手续费率(需已连接)。"""
|
||||||
|
try:
|
||||||
|
sym, ex_name = ths_to_vnpy_symbol(ths_code)
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
rows = self._query_commission(
|
||||||
|
mode=mode,
|
||||||
|
instrument_id=sym,
|
||||||
|
exchange_id=ex_name,
|
||||||
|
)
|
||||||
|
return rows[-1] if rows else {}
|
||||||
|
|
||||||
|
def query_all_commissions(self, *, mode: str) -> list[dict]:
|
||||||
|
"""批量查询全部合约手续费(InstrumentID 留空)。"""
|
||||||
|
return self._query_commission(mode=mode, timeout=45)
|
||||||
|
|
||||||
def _tick_key(self, symbol: str, ex_name: str) -> str:
|
def _tick_key(self, symbol: str, ex_name: str) -> str:
|
||||||
return f"{symbol.lower()}:{ex_name.upper()}"
|
return f"{symbol.lower()}:{ex_name.upper()}"
|
||||||
|
|||||||
Reference in New Issue
Block a user