feat: CTP/SimNow 配置迁入系统设置,登录失败即时报错
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-1
@@ -22,7 +22,7 @@ RISK_PERCENT=1
|
|||||||
# CTP 断线后后台自动重连(true/false)
|
# CTP 断线后后台自动重连(true/false)
|
||||||
CTP_AUTO_RECONNECT=true
|
CTP_AUTO_RECONNECT=true
|
||||||
|
|
||||||
# —— SimNow 模拟盘(注册见 docs/SIMNOW.md)——
|
# —— SimNow 模拟盘(也可在「系统设置 → CTP 连接」配置,优先于本文件)——
|
||||||
SIMNOW_USER=
|
SIMNOW_USER=
|
||||||
SIMNOW_PASSWORD=
|
SIMNOW_PASSWORD=
|
||||||
SIMNOW_BROKER_ID=9999
|
SIMNOW_BROKER_ID=9999
|
||||||
|
|||||||
@@ -366,6 +366,9 @@ def init_db():
|
|||||||
if not get_setting("ths_refresh_token") and os.getenv("THS_REFRESH_TOKEN"):
|
if not get_setting("ths_refresh_token") and os.getenv("THS_REFRESH_TOKEN"):
|
||||||
set_setting("ths_refresh_token", os.getenv("THS_REFRESH_TOKEN"))
|
set_setting("ths_refresh_token", os.getenv("THS_REFRESH_TOKEN"))
|
||||||
|
|
||||||
|
from ctp_settings import seed_ctp_settings_from_env
|
||||||
|
seed_ctp_settings_from_env(set_setting)
|
||||||
|
|
||||||
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
||||||
expire_old_plans()
|
expire_old_plans()
|
||||||
|
|
||||||
@@ -1706,6 +1709,17 @@ def settings():
|
|||||||
flash("移动保本缓冲无效")
|
flash("移动保本缓冲无效")
|
||||||
return redirect(url_for("settings"))
|
return redirect(url_for("settings"))
|
||||||
flash("交易模式已保存")
|
flash("交易模式已保存")
|
||||||
|
elif action == "ctp":
|
||||||
|
from ctp_settings import save_ctp_settings_from_form
|
||||||
|
|
||||||
|
save_ctp_settings_from_form(request.form, set_setting)
|
||||||
|
try:
|
||||||
|
from vnpy_bridge import get_bridge
|
||||||
|
|
||||||
|
get_bridge().mark_disconnected()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
flash("CTP 配置已保存,请在持仓监控页重连 CTP")
|
||||||
elif action == "nav":
|
elif action == "nav":
|
||||||
items = {k: request.form.get(f"nav_{k}") == "on" for k in NAV_TOGGLES}
|
items = {k: request.form.get(f"nav_{k}") == "on" for k in NAV_TOGGLES}
|
||||||
save_nav_items(set_setting, items)
|
save_nav_items(set_setting, items)
|
||||||
@@ -1736,11 +1750,15 @@ def settings():
|
|||||||
ctp_st = ctp_status(get_trading_mode(get_setting))
|
ctp_st = ctp_status(get_trading_mode(get_setting))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
from ctp_settings import get_ctp_settings_for_ui
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"settings.html",
|
"settings.html",
|
||||||
webhook=webhook,
|
webhook=webhook,
|
||||||
username=username,
|
username=username,
|
||||||
quote_label=get_quote_source_label(ctp_connected=bool(ctp_st.get("connected"))),
|
quote_label=get_quote_source_label(ctp_connected=bool(ctp_st.get("connected"))),
|
||||||
|
ctp_status=ctp_st,
|
||||||
|
ctp_cfg=get_ctp_settings_for_ui(),
|
||||||
trading_mode=get_setting("trading_mode", "simulation"),
|
trading_mode=get_setting("trading_mode", "simulation"),
|
||||||
position_sizing_mode=get_setting("position_sizing_mode", "fixed"),
|
position_sizing_mode=get_setting("position_sizing_mode", "fixed"),
|
||||||
fixed_lots=get_setting("fixed_lots", "1"),
|
fixed_lots=get_setting("fixed_lots", "1"),
|
||||||
|
|||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
"""CTP / SimNow 配置:系统设置优先,.env 作兜底。"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
# (db_key, env_key, vnpy字段名, 默认值)
|
||||||
|
SIMNOW_FIELDS: tuple[tuple[str, str, str, str], ...] = (
|
||||||
|
("simnow_user", "SIMNOW_USER", "用户名", ""),
|
||||||
|
("simnow_password", "SIMNOW_PASSWORD", "密码", ""),
|
||||||
|
("simnow_broker_id", "SIMNOW_BROKER_ID", "经纪商代码", "9999"),
|
||||||
|
("simnow_td_address", "SIMNOW_TD_ADDRESS", "交易服务器", "tcp://180.168.146.187:10201"),
|
||||||
|
("simnow_md_address", "SIMNOW_MD_ADDRESS", "行情服务器", "tcp://180.168.146.187:10211"),
|
||||||
|
("simnow_app_id", "SIMNOW_APP_ID", "产品名称", "simnow_client_test"),
|
||||||
|
("simnow_auth_code", "SIMNOW_AUTH_CODE", "授权编码", "0000000000000000"),
|
||||||
|
("simnow_env", "SIMNOW_ENV", "柜台环境", "实盘"),
|
||||||
|
)
|
||||||
|
|
||||||
|
LIVE_FIELDS: tuple[tuple[str, str, str, str], ...] = (
|
||||||
|
("ctp_live_user", "CTP_LIVE_USER", "用户名", ""),
|
||||||
|
("ctp_live_password", "CTP_LIVE_PASSWORD", "密码", ""),
|
||||||
|
("ctp_live_broker_id", "CTP_LIVE_BROKER_ID", "经纪商代码", ""),
|
||||||
|
("ctp_live_td_address", "CTP_LIVE_TD_ADDRESS", "交易服务器", ""),
|
||||||
|
("ctp_live_md_address", "CTP_LIVE_MD_ADDRESS", "行情服务器", ""),
|
||||||
|
("ctp_live_app_id", "CTP_LIVE_APP_ID", "产品名称", ""),
|
||||||
|
("ctp_live_auth_code", "CTP_LIVE_AUTH_CODE", "授权编码", ""),
|
||||||
|
("ctp_live_env", "CTP_LIVE_ENV", "柜台环境", "实盘"),
|
||||||
|
)
|
||||||
|
|
||||||
|
PASSWORD_DB_KEYS = frozenset({"simnow_password", "ctp_live_password"})
|
||||||
|
|
||||||
|
|
||||||
|
def _get_db_setting(key: str, default: str = "") -> str:
|
||||||
|
from fee_specs import get_setting
|
||||||
|
|
||||||
|
return (get_setting(key, default) or default).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_ctp_value(db_key: str, env_key: str, default: str = "") -> str:
|
||||||
|
v = _get_db_setting(db_key, "")
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
return (os.getenv(env_key) or default).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _build_setting_dict(fields: tuple[tuple[str, str, str, str], ...]) -> dict[str, str]:
|
||||||
|
out: dict[str, str] = {}
|
||||||
|
for db_key, env_key, vnpy_key, default in fields:
|
||||||
|
out[vnpy_key] = resolve_ctp_value(db_key, env_key, default)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def simnow_setting_dict() -> dict[str, str]:
|
||||||
|
return _build_setting_dict(SIMNOW_FIELDS)
|
||||||
|
|
||||||
|
|
||||||
|
def live_setting_dict() -> dict[str, str]:
|
||||||
|
return _build_setting_dict(LIVE_FIELDS)
|
||||||
|
|
||||||
|
|
||||||
|
def seed_ctp_settings_from_env(set_setting: Callable[[str, str], None]) -> None:
|
||||||
|
"""首次启动:将 .env 中已有 CTP 配置写入 settings 表。"""
|
||||||
|
for db_key, env_key, _, _ in (*SIMNOW_FIELDS, *LIVE_FIELDS):
|
||||||
|
if _get_db_setting(db_key, ""):
|
||||||
|
continue
|
||||||
|
env_val = (os.getenv(env_key) or "").strip()
|
||||||
|
if env_val:
|
||||||
|
set_setting(db_key, env_val)
|
||||||
|
|
||||||
|
|
||||||
|
def get_ctp_settings_for_ui() -> dict[str, Any]:
|
||||||
|
ui: dict[str, Any] = {}
|
||||||
|
for db_key, env_key, _, default in SIMNOW_FIELDS:
|
||||||
|
ui[db_key] = resolve_ctp_value(db_key, env_key, default)
|
||||||
|
if db_key in PASSWORD_DB_KEYS:
|
||||||
|
ui[f"{db_key}_set"] = bool(ui[db_key])
|
||||||
|
ui[db_key] = ""
|
||||||
|
for db_key, env_key, _, default in LIVE_FIELDS:
|
||||||
|
ui[db_key] = resolve_ctp_value(db_key, env_key, default)
|
||||||
|
if db_key in PASSWORD_DB_KEYS:
|
||||||
|
ui[f"{db_key}_set"] = bool(ui[db_key])
|
||||||
|
ui[db_key] = ""
|
||||||
|
return ui
|
||||||
|
|
||||||
|
|
||||||
|
def save_ctp_settings_from_form(
|
||||||
|
form: Any,
|
||||||
|
set_setting: Callable[[str, str], None],
|
||||||
|
) -> None:
|
||||||
|
"""保存 CTP 配置;密码留空表示不修改。"""
|
||||||
|
for db_key, _, _, default in SIMNOW_FIELDS:
|
||||||
|
if db_key in PASSWORD_DB_KEYS:
|
||||||
|
val = (form.get(db_key) or "").strip()
|
||||||
|
if val:
|
||||||
|
set_setting(db_key, val)
|
||||||
|
continue
|
||||||
|
val = (form.get(db_key) or "").strip()
|
||||||
|
set_setting(db_key, val or default)
|
||||||
|
|
||||||
|
for db_key, _, _, default in LIVE_FIELDS:
|
||||||
|
if db_key in PASSWORD_DB_KEYS:
|
||||||
|
val = (form.get(db_key) or "").strip()
|
||||||
|
if val:
|
||||||
|
set_setting(db_key, val)
|
||||||
|
continue
|
||||||
|
val = (form.get(db_key) or "").strip()
|
||||||
|
if default or val:
|
||||||
|
set_setting(db_key, val or default)
|
||||||
+1
-1
@@ -133,7 +133,7 @@ SimNow 提供多种仿真环境,**IP 与端口会随官网公告调整**,部
|
|||||||
tcp://IP:端口
|
tcp://IP:端口
|
||||||
```
|
```
|
||||||
|
|
||||||
修改 `.env` 后需重启应用:
|
修改 `.env` 后需重启应用;也可在 **系统设置 → CTP 连接** 中维护(优先于 `.env`)。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pm2 restart qihuo
|
pm2 restart qihuo
|
||||||
|
|||||||
+11
-8
@@ -33,18 +33,21 @@ def _probe(host_port: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
user = os.getenv("SIMNOW_USER", "")
|
from ctp_settings import resolve_ctp_value
|
||||||
td = os.getenv("SIMNOW_TD_ADDRESS", "tcp://180.168.146.187:10201")
|
|
||||||
md = os.getenv("SIMNOW_MD_ADDRESS", "tcp://180.168.146.187:10211")
|
|
||||||
env = os.getenv("SIMNOW_ENV", "实盘")
|
|
||||||
|
|
||||||
print("=== SimNow 配置 ===")
|
user = resolve_ctp_value("simnow_user", "SIMNOW_USER")
|
||||||
|
pwd = resolve_ctp_value("simnow_password", "SIMNOW_PASSWORD")
|
||||||
|
td = resolve_ctp_value("simnow_td_address", "SIMNOW_TD_ADDRESS", "tcp://180.168.146.187:10201")
|
||||||
|
md = resolve_ctp_value("simnow_md_address", "SIMNOW_MD_ADDRESS", "tcp://180.168.146.187:10211")
|
||||||
|
env = resolve_ctp_value("simnow_env", "SIMNOW_ENV", "实盘")
|
||||||
|
|
||||||
|
print("=== SimNow 配置(系统设置优先)===")
|
||||||
print(f"locale = {ensure_process_locale()}")
|
print(f"locale = {ensure_process_locale()}")
|
||||||
missing = missing_ctp_locales()
|
missing = missing_ctp_locales()
|
||||||
if missing:
|
if missing:
|
||||||
print(f"警告: 缺少 CTP 所需 locale: {', '.join(missing)}")
|
print(f"警告: 缺少 CTP 所需 locale: {', '.join(missing)}")
|
||||||
print(f"SIMNOW_USER = {user or '(未设置)'}")
|
print(f"SIMNOW_USER = {user or '(未设置)'}")
|
||||||
print(f"SIMNOW_PASSWORD = {'*' * 8 if os.getenv('SIMNOW_PASSWORD') else '(未设置)'}")
|
print(f"SIMNOW_PASSWORD = {'*' * 8 if pwd else '(未设置)'}")
|
||||||
print(f"SIMNOW_TD = {td}")
|
print(f"SIMNOW_TD = {td}")
|
||||||
print(f"SIMNOW_MD = {md}")
|
print(f"SIMNOW_MD = {md}")
|
||||||
print(f"SIMNOW_ENV = {env}")
|
print(f"SIMNOW_ENV = {env}")
|
||||||
@@ -54,8 +57,8 @@ def main() -> int:
|
|||||||
print(f"MD {md} -> {_probe(md)}")
|
print(f"MD {md} -> {_probe(md)}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
if not user or not os.getenv("SIMNOW_PASSWORD"):
|
if not user or not pwd:
|
||||||
print("错误:请在 .env 填写 SIMNOW_USER / SIMNOW_PASSWORD")
|
print("错误:请在系统设置或 .env 填写 SimNow 投资者代码与密码")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
print("=== CTP 登录测试 ===")
|
print("=== CTP 登录测试 ===")
|
||||||
|
|||||||
+116
-2
@@ -14,8 +14,14 @@
|
|||||||
.settings-tips{flex:1;display:flex;flex-direction:column;justify-content:center;gap:.5rem;margin:0;padding:0;list-style:none;font-size:.85rem;color:var(--text-muted);line-height:1.55}
|
.settings-tips{flex:1;display:flex;flex-direction:column;justify-content:center;gap:.5rem;margin:0;padding:0;list-style:none;font-size:.85rem;color:var(--text-muted);line-height:1.55}
|
||||||
.settings-tips li{padding-left:1rem;position:relative}
|
.settings-tips li{padding-left:1rem;position:relative}
|
||||||
.settings-tips li::before{content:"";position:absolute;left:0;top:.55em;width:5px;height:5px;border-radius:50%;background:var(--accent)}
|
.settings-tips li::before{content:"";position:absolute;left:0;top:.55em;width:5px;height:5px;border-radius:50%;background:var(--accent)}
|
||||||
|
.settings-ctp-grid{display:grid;grid-template-columns:1fr 1fr;gap:.65rem .75rem}
|
||||||
|
.settings-ctp-grid .field-full{grid-column:1/-1}
|
||||||
|
.settings-ctp-section{margin-bottom:1rem;padding-bottom:1rem;border-bottom:1px solid var(--border)}
|
||||||
|
.settings-ctp-section:last-of-type{border-bottom:none;margin-bottom:0;padding-bottom:0}
|
||||||
|
.settings-ctp-section h3{font-size:.9rem;margin:0 0 .65rem;color:var(--text-title)}
|
||||||
|
.settings-ctp-status{font-size:.82rem;color:var(--text-muted);margin-top:.75rem;line-height:1.5}
|
||||||
@media(max-width:900px){
|
@media(max-width:900px){
|
||||||
.settings-password-form{grid-template-columns:1fr}
|
.settings-ctp-grid{grid-template-columns:1fr}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -78,12 +84,120 @@
|
|||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary" style="margin-top:.75rem">保存交易设置</button>
|
<button type="submit" class="btn-primary" style="margin-top:.75rem">保存交易设置</button>
|
||||||
<p class="hint" style="margin-top:.75rem;margin-bottom:0">
|
<p class="hint" style="margin-top:.75rem;margin-bottom:0">
|
||||||
保证金上限用于开仓校验与品种最大手数估算(默认 30%)。<strong>移动保本</strong>:达 1R 后止损移至开仓价 ± N 跳(玉米 N=2 即 +2 点,棉花 N=2 即 +10 点);达 2R 移至 1R,依次类推。在 <code>.env</code> 配置 <code>SIMNOW_USER</code>,于「持仓监控」连接 CTP。
|
保证金上限用于开仓校验与品种最大手数估算(默认 30%)。<strong>移动保本</strong>:达 1R 后止损移至开仓价 ± N 跳。CTP 账号与前置在下方「CTP 连接」中配置。
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>CTP 连接</h2>
|
||||||
|
<form action="{{ url_for('settings') }}" method="post">
|
||||||
|
<input type="hidden" name="action" value="ctp">
|
||||||
|
<p class="hint" style="margin-bottom:.85rem">
|
||||||
|
投资者代码、密码、前置地址在此维护(优先于 <code>.env</code>)。保存后请在持仓监控页点击「重连 CTP」。
|
||||||
|
{% if ctp_status.connected %}
|
||||||
|
<span class="badge profit" style="margin-left:.35rem">已连接</span>
|
||||||
|
{% elif ctp_status.connecting %}
|
||||||
|
<span class="badge planned" style="margin-left:.35rem">连接中</span>
|
||||||
|
{% elif ctp_status.last_error %}
|
||||||
|
<span class="text-loss" style="display:block;margin-top:.35rem">{{ ctp_status.last_error }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="settings-ctp-section">
|
||||||
|
<h3>SimNow 模拟盘</h3>
|
||||||
|
<div class="settings-ctp-grid">
|
||||||
|
<div class="field">
|
||||||
|
<label>投资者代码</label>
|
||||||
|
<input name="simnow_user" value="{{ ctp_cfg.simnow_user }}" placeholder="非手机号">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>交易密码</label>
|
||||||
|
<input name="simnow_password" type="password" autocomplete="new-password"
|
||||||
|
placeholder="{% if ctp_cfg.simnow_password_set %}已设置,留空不修改{% else %}SimNow 密码{% endif %}">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>经纪商代码</label>
|
||||||
|
<input name="simnow_broker_id" value="{{ ctp_cfg.simnow_broker_id }}">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>柜台环境</label>
|
||||||
|
<select name="simnow_env">
|
||||||
|
<option value="实盘" {% if ctp_cfg.simnow_env == '实盘' %}selected{% endif %}>实盘(看穿式,推荐)</option>
|
||||||
|
<option value="测试" {% if ctp_cfg.simnow_env == '测试' %}selected{% endif %}>测试</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field field-full">
|
||||||
|
<label>交易前置</label>
|
||||||
|
<input name="simnow_td_address" value="{{ ctp_cfg.simnow_td_address }}" placeholder="tcp://180.168.146.187:10201">
|
||||||
|
</div>
|
||||||
|
<div class="field field-full">
|
||||||
|
<label>行情前置</label>
|
||||||
|
<input name="simnow_md_address" value="{{ ctp_cfg.simnow_md_address }}" placeholder="tcp://180.168.146.187:10211">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>AppID</label>
|
||||||
|
<input name="simnow_app_id" value="{{ ctp_cfg.simnow_app_id }}">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>授权编码</label>
|
||||||
|
<input name="simnow_auth_code" value="{{ ctp_cfg.simnow_auth_code }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-ctp-section">
|
||||||
|
<h3>期货公司实盘(后期)</h3>
|
||||||
|
<div class="settings-ctp-grid">
|
||||||
|
<div class="field">
|
||||||
|
<label>投资者代码</label>
|
||||||
|
<input name="ctp_live_user" value="{{ ctp_cfg.ctp_live_user }}">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>交易密码</label>
|
||||||
|
<input name="ctp_live_password" type="password" autocomplete="new-password"
|
||||||
|
placeholder="{% if ctp_cfg.ctp_live_password_set %}已设置,留空不修改{% else %}实盘密码{% endif %}">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>经纪商代码</label>
|
||||||
|
<input name="ctp_live_broker_id" value="{{ ctp_cfg.ctp_live_broker_id }}">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>柜台环境</label>
|
||||||
|
<select name="ctp_live_env">
|
||||||
|
<option value="实盘" {% if ctp_cfg.ctp_live_env == '实盘' %}selected{% endif %}>实盘</option>
|
||||||
|
<option value="测试" {% if ctp_cfg.ctp_live_env == '测试' %}selected{% endif %}>测试</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field field-full">
|
||||||
|
<label>交易前置</label>
|
||||||
|
<input name="ctp_live_td_address" value="{{ ctp_cfg.ctp_live_td_address }}" placeholder="tcp://...">
|
||||||
|
</div>
|
||||||
|
<div class="field field-full">
|
||||||
|
<label>行情前置</label>
|
||||||
|
<input name="ctp_live_md_address" value="{{ ctp_cfg.ctp_live_md_address }}" placeholder="tcp://...">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>AppID</label>
|
||||||
|
<input name="ctp_live_app_id" value="{{ ctp_cfg.ctp_live_app_id }}">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>授权编码</label>
|
||||||
|
<input name="ctp_live_auth_code" value="{{ ctp_cfg.ctp_live_auth_code }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn-primary">保存 CTP 配置</button>
|
||||||
|
<p class="settings-ctp-status">
|
||||||
|
官方第一套:<code>180.168.146.187:10201/10211</code>;
|
||||||
|
7×24:<code>182.254.243.31:40001/40011</code>(新账号可能需满 3 个交易日)。
|
||||||
|
详见 <code>docs/SIMNOW.md</code>。
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="split-grid">
|
<div class="split-grid">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>行情说明</h2>
|
<h2>行情说明</h2>
|
||||||
|
|||||||
+12
-27
@@ -12,6 +12,7 @@ from locale_fix import ensure_process_locale
|
|||||||
|
|
||||||
ensure_process_locale()
|
ensure_process_locale()
|
||||||
|
|
||||||
|
from ctp_settings import live_setting_dict, simnow_setting_dict
|
||||||
from ctp_symbol import ths_to_vnpy_symbol, to_vnpy_exchange
|
from ctp_symbol import ths_to_vnpy_symbol, to_vnpy_exchange
|
||||||
from contract_specs import get_contract_spec
|
from contract_specs import get_contract_spec
|
||||||
|
|
||||||
@@ -43,35 +44,13 @@ _bridge: Optional["CtpBridge"] = None
|
|||||||
_bridge_lock = threading.Lock()
|
_bridge_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
def _env(key: str, default: str = "") -> str:
|
|
||||||
return (os.getenv(key) or default).strip()
|
|
||||||
|
|
||||||
|
|
||||||
def _simnow_setting() -> dict[str, str]:
|
def _simnow_setting() -> dict[str, str]:
|
||||||
"""SimNow 仿真前置(可在 .env 覆盖)。看穿式前置需「柜台环境=实盘」。"""
|
"""SimNow 仿真前置(系统设置优先,.env 兜底)。"""
|
||||||
return {
|
return simnow_setting_dict()
|
||||||
"用户名": _env("SIMNOW_USER"),
|
|
||||||
"密码": _env("SIMNOW_PASSWORD"),
|
|
||||||
"经纪商代码": _env("SIMNOW_BROKER_ID", "9999"),
|
|
||||||
"交易服务器": _env("SIMNOW_TD_ADDRESS", "tcp://180.168.146.187:10201"),
|
|
||||||
"行情服务器": _env("SIMNOW_MD_ADDRESS", "tcp://180.168.146.187:10211"),
|
|
||||||
"产品名称": _env("SIMNOW_APP_ID", "simnow_client_test"),
|
|
||||||
"授权编码": _env("SIMNOW_AUTH_CODE", "0000000000000000"),
|
|
||||||
"柜台环境": _env("SIMNOW_ENV", "实盘"),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _live_setting() -> dict[str, str]:
|
def _live_setting() -> dict[str, str]:
|
||||||
return {
|
return live_setting_dict()
|
||||||
"用户名": _env("CTP_LIVE_USER"),
|
|
||||||
"密码": _env("CTP_LIVE_PASSWORD"),
|
|
||||||
"经纪商代码": _env("CTP_LIVE_BROKER_ID"),
|
|
||||||
"交易服务器": _env("CTP_LIVE_TD_ADDRESS"),
|
|
||||||
"行情服务器": _env("CTP_LIVE_MD_ADDRESS"),
|
|
||||||
"产品名称": _env("CTP_LIVE_APP_ID"),
|
|
||||||
"授权编码": _env("CTP_LIVE_AUTH_CODE"),
|
|
||||||
"柜台环境": _env("CTP_LIVE_ENV", "实盘"),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _setting_for_mode(mode: str) -> dict[str, str]:
|
def _setting_for_mode(mode: str) -> dict[str, str]:
|
||||||
@@ -206,12 +185,18 @@ class CtpBridge:
|
|||||||
self._connected_mode = None
|
self._connected_mode = None
|
||||||
time.sleep(0.6)
|
time.sleep(0.6)
|
||||||
|
|
||||||
def _wait_connected(self, mode: str) -> bool:
|
def _login_rejected(self, ctp_logs: list[str]) -> bool:
|
||||||
|
return any("登录失败" in m or "不合法的登录" in m for m in ctp_logs)
|
||||||
|
|
||||||
|
def _wait_connected(self, mode: str, ctp_logs: list[str] | None = None) -> bool:
|
||||||
"""等待账户回报或交易通道登录成功。"""
|
"""等待账户回报或交易通道登录成功。"""
|
||||||
if not self._engine:
|
if not self._engine:
|
||||||
return False
|
return False
|
||||||
|
logs = ctp_logs or []
|
||||||
loops = max(1, int(CONNECT_WAIT_SEC / CONNECT_POLL_INTERVAL_SEC))
|
loops = max(1, int(CONNECT_WAIT_SEC / CONNECT_POLL_INTERVAL_SEC))
|
||||||
for _ in range(loops):
|
for _ in range(loops):
|
||||||
|
if self._login_rejected(logs):
|
||||||
|
return False
|
||||||
try:
|
try:
|
||||||
if self._engine.get_all_accounts():
|
if self._engine.get_all_accounts():
|
||||||
return True
|
return True
|
||||||
@@ -302,7 +287,7 @@ class CtpBridge:
|
|||||||
"并在服务器执行 nc -zv 验证出网。"
|
"并在服务器执行 nc -zv 验证出网。"
|
||||||
)
|
)
|
||||||
self._engine.connect(setting, GATEWAY_NAME)
|
self._engine.connect(setting, GATEWAY_NAME)
|
||||||
if self._wait_connected(mode):
|
if self._wait_connected(mode, ctp_logs):
|
||||||
self._connected_mode = mode
|
self._connected_mode = mode
|
||||||
self._last_error = ""
|
self._last_error = ""
|
||||||
logger.info("CTP 已连接 [%s] td_login=%s accounts=%s",
|
logger.info("CTP 已连接 [%s] td_login=%s accounts=%s",
|
||||||
|
|||||||
Reference in New Issue
Block a user