Restructure into modules/ with single-process CTP and config/ layout.
Move business code under modules/, env template to config/, PM2 single qihuo process, and _legacy shims for old imports. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+2
-61
@@ -1,61 +1,2 @@
|
||||
# 服务配置
|
||||
HOST=0.0.0.0
|
||||
PORT=6600
|
||||
DEBUG=false
|
||||
|
||||
SECRET_KEY=change-this-to-a-random-secret-key
|
||||
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=change-me-on-first-login
|
||||
ADMIN_SYNC_FROM_ENV=false
|
||||
|
||||
WECHAT_WEBHOOK=
|
||||
|
||||
QUOTE_SOURCE=sina
|
||||
THS_REFRESH_TOKEN=
|
||||
|
||||
# 交易模式:simulation=SimNow,live=期货公司(系统设置页可改)
|
||||
TRADING_MODE=simulation
|
||||
POSITION_SIZING_MODE=risk
|
||||
RISK_PERCENT=1
|
||||
|
||||
# CTP 断线后后台自动重连(true/false)
|
||||
CTP_AUTO_RECONNECT=true
|
||||
|
||||
# —— SimNow 模拟盘(也可在「系统设置 → CTP 连接」配置,优先于本文件)——
|
||||
SIMNOW_USER=
|
||||
SIMNOW_PASSWORD=
|
||||
SIMNOW_BROKER_ID=9999
|
||||
# 7×24 / 日盘前置(deploy.sh 会自动 nc 探测并写入可用线路)
|
||||
SIMNOW_TD_ADDRESS=tcp://180.168.146.187:10201
|
||||
SIMNOW_MD_ADDRESS=tcp://180.168.146.187:10211
|
||||
SIMNOW_APP_ID=simnow_client_test
|
||||
SIMNOW_AUTH_CODE=0000000000000000
|
||||
# SimNow 看穿式前置固定用「实盘」;仅穿透式测评才用「测试」
|
||||
SIMNOW_ENV=实盘
|
||||
|
||||
# —— 期货公司实盘(后期接入)——
|
||||
CTP_LIVE_USER=
|
||||
CTP_LIVE_PASSWORD=
|
||||
CTP_LIVE_BROKER_ID=
|
||||
CTP_LIVE_TD_ADDRESS=
|
||||
CTP_LIVE_MD_ADDRESS=
|
||||
CTP_LIVE_APP_ID=
|
||||
CTP_LIVE_AUTH_CODE=
|
||||
CTP_LIVE_PRODUCT_INFO=
|
||||
|
||||
# 账户冷静期
|
||||
RISK_CONTROL_ENABLED=true
|
||||
RISK_COOLING_HOURS_MANUAL=4
|
||||
RISK_COOLING_HOURS_MANUAL_JOURNAL=1
|
||||
RISK_MANUAL_CLOSE_DAILY_LIMIT=2
|
||||
MAX_ACTIVE_POSITIONS=1
|
||||
RISK_DAILY_POSITION_LIMIT=5
|
||||
RISK_DAILY_TRADING_RISK_PCT=2
|
||||
TRADING_DAY_RESET_HOUR=8
|
||||
|
||||
# —— 数据库(生产推荐 PostgreSQL,见 docs/POSTGRES.md)——
|
||||
# 未配置 DATABASE_URL 时使用本地 SQLite futures.db
|
||||
# DATABASE_URL=postgresql://qihuo:your_password@127.0.0.1:5432/qihuo
|
||||
# PG_POOL_MIN=2
|
||||
# PG_POOL_MAX=20
|
||||
# 环境变量模板已迁移至 config/.env.example
|
||||
# 使用: cp config/.env.example config/.env
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.env
|
||||
config/.env
|
||||
*.db
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.settings.admin_settings
|
||||
from modules.settings.admin_settings import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.notify.ai_client
|
||||
from modules.notify.ai_client import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.notify.ai_messages
|
||||
from modules.notify.ai_messages import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.notify.ai_worker
|
||||
from modules.notify.ai_worker import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.core.contract_profile
|
||||
from modules.core.contract_profile import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.core.contract_specs
|
||||
from modules.core.contract_specs import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.ctp.ctp_entry_price
|
||||
from modules.ctp.ctp_entry_price import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.ctp.ctp_fee_sync
|
||||
from modules.ctp.ctp_fee_sync import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.ctp.ctp_fee_worker
|
||||
from modules.ctp.ctp_fee_worker import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.ctp.ctp_ipc_client
|
||||
from modules.ctp.ctp_ipc_client import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.ctp.ctp_kline
|
||||
from modules.ctp.ctp_kline import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.ctp.ctp_premarket_connect
|
||||
from modules.ctp.ctp_premarket_connect import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.ctp.ctp_reconnect
|
||||
from modules.ctp.ctp_reconnect import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.ctp.ctp_settings
|
||||
from modules.ctp.ctp_settings import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.ctp.ctp_symbol
|
||||
from modules.ctp.ctp_symbol import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.ctp.ctp_trade_sync
|
||||
from modules.ctp.ctp_trade_sync import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.ctp.ctp_trading_state
|
||||
from modules.ctp.ctp_trading_state import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.ctp.ctp_worker
|
||||
from modules.ctp.ctp_worker import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.stats.dashboard_lib
|
||||
from modules.stats.dashboard_lib import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.backup.db_backup
|
||||
from modules.backup.db_backup import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.core.db_conn
|
||||
from modules.core.db_conn import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.core.doc_render
|
||||
from modules.core.doc_render import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.core.env_file
|
||||
from modules.core.env_file import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.fees.fee_specs
|
||||
from modules.fees.fee_specs import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.fees.fee_sync
|
||||
from modules.fees.fee_sync import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.keys.key_monitor_lib
|
||||
from modules.keys.key_monitor_lib import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.market.kline_chart
|
||||
from modules.market.kline_chart import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.market.kline_store
|
||||
from modules.market.kline_store import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.market.kline_stream
|
||||
from modules.market.kline_stream import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.core.locale_fix
|
||||
from modules.core.locale_fix import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.market.market
|
||||
from modules.market.market import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.market.market_sessions
|
||||
from modules.market.market_sessions import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.settings.nav_settings
|
||||
from modules.settings.nav_settings import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.trading.order_pending
|
||||
from modules.trading.order_pending import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.trading.pending_order_worker
|
||||
from modules.trading.pending_order_worker import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.trading.position_sizing
|
||||
from modules.trading.position_sizing import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.trading.position_stream
|
||||
from modules.trading.position_stream import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.trading.product_recommend
|
||||
from modules.trading.product_recommend import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.trading.recommend_store
|
||||
from modules.trading.recommend_store import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.trading.recommend_stream
|
||||
from modules.trading.recommend_stream import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.trading.recommend_trend
|
||||
from modules.trading.recommend_trend import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.risk.account_risk_lib
|
||||
from modules.risk.account_risk_lib import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.trading.sl_tp_guard
|
||||
from modules.trading.sl_tp_guard import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.stats.stats_engine
|
||||
from modules.stats.stats_engine import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.strategy.fib_lib
|
||||
from modules.strategy.fib_lib import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.strategy.strategy_db
|
||||
from modules.strategy.strategy_db import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.strategy.strategy_roll_lib
|
||||
from modules.strategy.strategy_roll_lib import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.strategy.strategy_roll_monitor_lib
|
||||
from modules.strategy.strategy_roll_monitor_lib import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.strategy.strategy_snapshot_lib
|
||||
from modules.strategy.strategy_snapshot_lib import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.strategy.strategy_trend_lib
|
||||
from modules.strategy.strategy_trend_lib import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.core.symbols
|
||||
from modules.core.symbols import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.trading.trade_log_lib
|
||||
from modules.trading.trade_log_lib import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.trading.trade_notify
|
||||
from modules.trading.trade_notify import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.core.trading_context
|
||||
from modules.core.trading_context import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.ctp.vnpy_bridge
|
||||
from modules.ctp.vnpy_bridge import * # noqa: F401,F403
|
||||
@@ -0,0 +1,2 @@
|
||||
# Compatibility shim — use modules.notify.wechat_notify
|
||||
from modules.notify.wechat_notify import * # noqa: F401,F403
|
||||
@@ -0,0 +1,61 @@
|
||||
# 服务配置
|
||||
HOST=0.0.0.0
|
||||
PORT=6600
|
||||
DEBUG=false
|
||||
|
||||
SECRET_KEY=change-this-to-a-random-secret-key
|
||||
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=change-me-on-first-login
|
||||
ADMIN_SYNC_FROM_ENV=false
|
||||
|
||||
WECHAT_WEBHOOK=
|
||||
|
||||
QUOTE_SOURCE=sina
|
||||
THS_REFRESH_TOKEN=
|
||||
|
||||
# 交易模式:simulation=SimNow,live=期货公司(系统设置页可改)
|
||||
TRADING_MODE=simulation
|
||||
POSITION_SIZING_MODE=risk
|
||||
RISK_PERCENT=1
|
||||
|
||||
# CTP 断线后后台自动重连(true/false)
|
||||
CTP_AUTO_RECONNECT=true
|
||||
|
||||
# —— SimNow 模拟盘(也可在「系统设置 → CTP 连接」配置,优先于本文件)——
|
||||
SIMNOW_USER=
|
||||
SIMNOW_PASSWORD=
|
||||
SIMNOW_BROKER_ID=9999
|
||||
# 7×24 / 日盘前置(deploy.sh 会自动 nc 探测并写入可用线路)
|
||||
SIMNOW_TD_ADDRESS=tcp://180.168.146.187:10201
|
||||
SIMNOW_MD_ADDRESS=tcp://180.168.146.187:10211
|
||||
SIMNOW_APP_ID=simnow_client_test
|
||||
SIMNOW_AUTH_CODE=0000000000000000
|
||||
# SimNow 看穿式前置固定用「实盘」;仅穿透式测评才用「测试」
|
||||
SIMNOW_ENV=实盘
|
||||
|
||||
# —— 期货公司实盘(后期接入)——
|
||||
CTP_LIVE_USER=
|
||||
CTP_LIVE_PASSWORD=
|
||||
CTP_LIVE_BROKER_ID=
|
||||
CTP_LIVE_TD_ADDRESS=
|
||||
CTP_LIVE_MD_ADDRESS=
|
||||
CTP_LIVE_APP_ID=
|
||||
CTP_LIVE_AUTH_CODE=
|
||||
CTP_LIVE_PRODUCT_INFO=
|
||||
|
||||
# 账户冷静期
|
||||
RISK_CONTROL_ENABLED=true
|
||||
RISK_COOLING_HOURS_MANUAL=4
|
||||
RISK_COOLING_HOURS_MANUAL_JOURNAL=1
|
||||
RISK_MANUAL_CLOSE_DAILY_LIMIT=2
|
||||
MAX_ACTIVE_POSITIONS=1
|
||||
RISK_DAILY_POSITION_LIMIT=5
|
||||
RISK_DAILY_TRADING_RISK_PCT=2
|
||||
TRADING_DAY_RESET_HOUR=8
|
||||
|
||||
# —— 数据库(生产推荐 PostgreSQL,见 docs/POSTGRES.md)——
|
||||
# 未配置 DATABASE_URL 时使用本地 SQLite futures.db
|
||||
# DATABASE_URL=postgresql://qihuo:your_password@127.0.0.1:5432/qihuo
|
||||
# PG_POOL_MIN=2
|
||||
# PG_POOL_MAX=20
|
||||
@@ -161,25 +161,27 @@ pip install --upgrade pip -q
|
||||
pip install -r "$APP_DIR/requirements.txt"
|
||||
python -c "from vnpy_ctp import CtpGateway; print('vnpy_ctp OK')"
|
||||
|
||||
echo "==> 配置 .env..."
|
||||
if [ ! -f "$APP_DIR/.env" ]; then
|
||||
cp "$APP_DIR/.env.example" "$APP_DIR/.env"
|
||||
echo "==> 配置 config/.env..."
|
||||
ENV_FILE="$APP_DIR/config/.env"
|
||||
mkdir -p "$APP_DIR/config"
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
cp "$APP_DIR/config/.env.example" "$ENV_FILE"
|
||||
RAND_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
|
||||
sed -i "s/change-this-to-a-random-secret-key/${RAND_KEY}/" "$APP_DIR/.env"
|
||||
echo " 已生成 .env,请编辑 SIMNOW_USER / ADMIN_PASSWORD"
|
||||
sed -i "s/change-this-to-a-random-secret-key/${RAND_KEY}/" "$ENV_FILE"
|
||||
echo " 已生成 config/.env,请编辑 SIMNOW_USER / ADMIN_PASSWORD"
|
||||
fi
|
||||
ensure_env_key "$APP_DIR/.env" "SIMNOW_ENV" "实盘"
|
||||
ensure_env_key "$APP_DIR/.env" "CTP_AUTO_RECONNECT" "true"
|
||||
ensure_env_key "$APP_DIR/.env" "SIMNOW_BROKER_ID" "9999"
|
||||
ensure_env_key "$APP_DIR/.env" "SIMNOW_APP_ID" "simnow_client_test"
|
||||
ensure_env_key "$APP_DIR/.env" "SIMNOW_AUTH_CODE" "0000000000000000"
|
||||
update_simnow_front_in_env "$APP_DIR/.env" || true
|
||||
ensure_env_key "$ENV_FILE" "SIMNOW_ENV" "实盘"
|
||||
ensure_env_key "$ENV_FILE" "CTP_AUTO_RECONNECT" "true"
|
||||
ensure_env_key "$ENV_FILE" "SIMNOW_BROKER_ID" "9999"
|
||||
ensure_env_key "$ENV_FILE" "SIMNOW_APP_ID" "simnow_client_test"
|
||||
ensure_env_key "$ENV_FILE" "SIMNOW_AUTH_CODE" "0000000000000000"
|
||||
update_simnow_front_in_env "$ENV_FILE" || true
|
||||
|
||||
mkdir -p "$APP_DIR/logs"
|
||||
|
||||
echo "==> 验证 CTP 环境..."
|
||||
if grep -q "^SIMNOW_USER=.\+" "$APP_DIR/.env" 2>/dev/null && \
|
||||
grep -q "^SIMNOW_PASSWORD=.\+" "$APP_DIR/.env" 2>/dev/null; then
|
||||
if grep -q "^SIMNOW_USER=.\+" "$ENV_FILE" 2>/dev/null && \
|
||||
grep -q "^SIMNOW_PASSWORD=.\+" "$ENV_FILE" 2>/dev/null; then
|
||||
set +e
|
||||
python "$APP_DIR/scripts/test_simnow.py"
|
||||
CTP_TEST=$?
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# 主目录结构
|
||||
|
||||
```
|
||||
qihuo/ # 主文件夹(仓库根)
|
||||
├── app.py # 主程序入口(Flask 启动)
|
||||
├── requirements.txt
|
||||
├── deploy.sh # 一键部署脚本
|
||||
├── ecosystem.config.cjs # PM2 启动配置
|
||||
├── config/
|
||||
│ ├── .env.example # 环境变量模板
|
||||
│ └── .env # 运行时配置(git 忽略)
|
||||
├── modules/ # 业务模块(每个模块 register(deps))
|
||||
│ ├── core/ # DB、路径、公共工具
|
||||
│ ├── web/ # 页面路由 + static/ + templates/
|
||||
│ ├── trading/ # 下单监控、持仓、推荐
|
||||
│ ├── ctp/ # vn.py / CTP 连接与报单
|
||||
│ ├── risk/ # 账户风控
|
||||
│ ├── strategy/ # 趋势、滚仓策略
|
||||
│ ├── keys/ # 关键位
|
||||
│ ├── plans/ # 开单计划
|
||||
│ ├── market/ # 行情、K 线
|
||||
│ ├── records/ # 交易记录、复盘
|
||||
│ ├── stats/ # 统计、看板
|
||||
│ ├── settings/ # 系统设置
|
||||
│ ├── notify/ # 微信、AI 消息
|
||||
│ ├── fees/ # 手续费
|
||||
│ └── backup/ # 备份
|
||||
├── _legacy/ # 旧 import 兼容 shim(PM2 PYTHONPATH)
|
||||
├── data/ # 静态数据(如 fee_rates.json)
|
||||
├── docs/ # 文档
|
||||
├── scripts/ # 运维/诊断脚本(非运行时)
|
||||
├── futures.db # SQLite(未配 PG 时)
|
||||
├── uploads/
|
||||
└── logs/
|
||||
```
|
||||
|
||||
根目录 `_legacy/` 为旧 `import db_conn` 等路径的兼容层;新代码请 `from modules.xxx import ...`。
|
||||
|
||||
## 进程模型
|
||||
|
||||
- **单进程**:PM2 仅 `qihuo`(`app.py` + CTP 同进程)
|
||||
- 详见 [DEPLOY.md](./DEPLOY.md)
|
||||
|
||||
## 模块契约
|
||||
|
||||
每个 `modules/<name>/` 提供 `register(deps: AppDeps)`;主程序 `app.py` 只做串联,不写业务。
|
||||
|
||||
## 发布
|
||||
|
||||
见 [DEPLOY.md](./DEPLOY.md):**本地修改 → git push → 服务器 git pull**,禁止 SCP。
|
||||
+15
-16
@@ -43,7 +43,7 @@ pm2 save
|
||||
|
||||
以下文件 **不** 随 `git pull` 更新,卸载/重装时须 **单独备份与恢复**:
|
||||
|
||||
- `/opt/qihuo/.env`
|
||||
- `/opt/qihuo/config/.env`(兼容旧版 `/opt/qihuo/.env`)
|
||||
- `/opt/qihuo/futures.db`(SQLite)或 PostgreSQL 数据
|
||||
- `/opt/qihuo/uploads/`
|
||||
- `/opt/qihuo/backups/`(若有)
|
||||
@@ -58,18 +58,17 @@ pm2 save
|
||||
| 运行用户 | `root`(与 `deploy.sh` / PM2 配置一致) |
|
||||
| Web 端口 | `6600`(对外) |
|
||||
| CTP Worker 端口 | `6601`(仅 `127.0.0.1`,Web 进程 IPC 调用,勿对外开放) |
|
||||
| 进程管理 | PM2:`qihuo`(Flask Web)+ `qihuo-ctp`(CTP / vn.py 独立进程) |
|
||||
| 进程管理 | PM2:**仅** `qihuo`(Flask + CTP 单进程) |
|
||||
| 数据库 | **生产推荐 PostgreSQL**(见 [POSTGRES.md](./POSTGRES.md));未配置 `DATABASE_URL` 时使用 SQLite `futures.db` |
|
||||
| 仓库 | https://git.bz121.com/dekun/qihuo.git |
|
||||
|
||||
### 进程架构(2026-03 起)
|
||||
### 进程架构(2026-07 起:单进程)
|
||||
|
||||
| PM2 应用 | 角色 | 说明 |
|
||||
|----------|------|------|
|
||||
| `qihuo` | Web(`QIHUO_CTP_ROLE=client`) | Flask、页面、API、数据库;通过 HTTP 调用本机 Worker |
|
||||
| `qihuo-ctp` | Worker(`QIHUO_CTP_ROLE=worker`) | **唯一** 加载 vn.py / vnpy_ctp;CTP 连接、报单、持仓回调、止盈止损 tick、滚仓监控 |
|
||||
| PM2 应用 | 说明 |
|
||||
|----------|------|
|
||||
| `qihuo` | Flask Web + **vn.py / CTP 同进程**(`vnpy_bridge.CtpBridge`) |
|
||||
|
||||
Web 进程崩溃或重启 **不会** 直接带走 CTP 原生连接;Worker 重启后 Web 会自动通过 IPC 恢复读写。两个进程的 Token 须一致(见 `ecosystem.config.cjs` 中 `QIHUO_CTP_WORKER_TOKEN`)。
|
||||
详见 [ARCHITECTURE.md](./ARCHITECTURE.md)。旧版 `qihuo-ctp` 独立 Worker **已废弃**,`ecosystem.config.cjs` 不再启动该进程。
|
||||
|
||||
---
|
||||
|
||||
@@ -110,7 +109,7 @@ bash deploy.sh
|
||||
6. 首次生成 `.env`,并补全 `SIMNOW_ENV=实盘`、`CTP_AUTO_RECONNECT=true` 等缺项
|
||||
7. **自动探测 SimNow 前置**(`nc` 测端口),写入可用的 `SIMNOW_TD/MD_ADDRESS`(优先 `182.254.243.31`,其次 `180.168.146.187`)
|
||||
8. 若已配置 SimNow 账号,运行 `scripts/test_simnow.py` 验证连接
|
||||
9. `pm2 restart ecosystem.config.cjs --update-env` 或首次 `pm2 start ecosystem.config.cjs`,并 `pm2 save`(同时启动 **`qihuo`** 与 **`qihuo-ctp`**)
|
||||
9. `pm2 restart ecosystem.config.cjs --update-env` 或首次 `pm2 start ecosystem.config.cjs`,并 `pm2 save`(仅 **`qihuo`** 一个进程)
|
||||
|
||||
部署完成后访问:`http://<服务器IP>:6600`
|
||||
|
||||
@@ -141,7 +140,7 @@ MIGRATE_SQLITE=1 sudo bash scripts/deploy_postgres.sh
|
||||
|
||||
```bash
|
||||
# 在服务器上
|
||||
cp /opt/qihuo/.env /root/qihuo.env.bak
|
||||
cp /opt/qihuo/config/.env /root/qihuo.env.bak 2>/dev/null || cp /opt/qihuo/.env /root/qihuo.env.bak 2>/dev/null || true
|
||||
# SQLite
|
||||
cp /opt/qihuo/futures.db /root/futures.db.bak 2>/dev/null || true
|
||||
# PostgreSQL 见 POSTGRES.md 备份命令
|
||||
@@ -151,8 +150,8 @@ tar czf /root/qihuo_uploads.bak.tar.gz -C /opt/qihuo uploads 2>/dev/null || true
|
||||
### 2. 卸载 PM2 与代码目录
|
||||
|
||||
```bash
|
||||
pm2 stop qihuo qihuo-ctp 2>/dev/null || true
|
||||
pm2 delete qihuo qihuo-ctp 2>/dev/null || true
|
||||
pm2 stop qihuo 2>/dev/null || true
|
||||
pm2 delete qihuo 2>/dev/null || true
|
||||
pm2 save
|
||||
rm -rf /opt/qihuo
|
||||
```
|
||||
@@ -174,7 +173,7 @@ bash deploy.sh
|
||||
|
||||
```bash
|
||||
cd /opt/qihuo && git log -1 --oneline # 须与远端 main 最新提交一致
|
||||
pm2 status # qihuo、qihuo-ctp 均为 online
|
||||
pm2 status # qihuo 为 online
|
||||
```
|
||||
|
||||
浏览器访问 `http://<服务器IP>:6600` 登录验证。
|
||||
@@ -225,8 +224,8 @@ python -c "from vnpy_ctp import CtpGateway; print('vnpy_ctp OK')"
|
||||
若提示找不到模块,查看本文「CTP / vnpy 故障排查」一节。
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
cp config/.env.example config/.env
|
||||
nano config/.env
|
||||
```
|
||||
|
||||
| 变量 | 说明 |
|
||||
@@ -318,7 +317,7 @@ pm2 restart ecosystem.config.cjs --update-env
|
||||
pm2 save
|
||||
```
|
||||
|
||||
> 须 **同时重启** `qihuo` 与 `qihuo-ctp`。仅 `pm2 restart qihuo` 会导致 Web 与 Worker 代码/协议不一致。
|
||||
> 更新后执行 `pm2 restart ecosystem.config.cjs --update-env` 即可(仅 `qihuo`)。
|
||||
|
||||
若服务器曾用 SCP 覆盖文件导致 `git pull` 冲突,用 `git reset --hard origin/main` 与远端对齐。
|
||||
|
||||
|
||||
+1
-27
@@ -21,12 +21,10 @@ module.exports = {
|
||||
max_memory_restart: "8192M",
|
||||
env: {
|
||||
NODE_ENV: "production",
|
||||
PYTHONPATH: path.join(ROOT, "_legacy"),
|
||||
LANG: "zh_CN.UTF-8",
|
||||
LC_ALL: "zh_CN.UTF-8",
|
||||
LC_CTYPE: "zh_CN.UTF-8",
|
||||
QIHUO_CTP_ROLE: "client",
|
||||
QIHUO_CTP_WORKER_URL: "http://127.0.0.1:6601",
|
||||
QIHUO_CTP_WORKER_TOKEN: "qihuo-local-ctp",
|
||||
QIHUO_STARTUP_WORKERS: "8",
|
||||
QIHUO_MEMORY_MB: "8192",
|
||||
},
|
||||
@@ -34,29 +32,5 @@ module.exports = {
|
||||
out_file: path.join(ROOT, "logs", "pm2-out.log"),
|
||||
time: true,
|
||||
},
|
||||
{
|
||||
name: "qihuo-ctp",
|
||||
script: "ctp_worker.py",
|
||||
cwd: ROOT,
|
||||
interpreter,
|
||||
instances: 1,
|
||||
autorestart: true,
|
||||
watch: false,
|
||||
max_memory_restart: "8192M",
|
||||
env: {
|
||||
NODE_ENV: "production",
|
||||
LANG: "zh_CN.UTF-8",
|
||||
LC_ALL: "zh_CN.UTF-8",
|
||||
LC_CTYPE: "zh_CN.UTF-8",
|
||||
QIHUO_CTP_ROLE: "worker",
|
||||
QIHUO_CTP_WORKER_HOST: "127.0.0.1",
|
||||
QIHUO_CTP_WORKER_PORT: "6601",
|
||||
QIHUO_CTP_WORKER_TOKEN: "qihuo-local-ctp",
|
||||
QIHUO_MEMORY_MB: "8192",
|
||||
},
|
||||
error_file: path.join(ROOT, "logs", "pm2-ctp-error.log"),
|
||||
out_file: path.join(ROOT, "logs", "pm2-ctp-out.log"),
|
||||
time: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
+3
-4684
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
|
||||
"""Qihuo feature modules package."""
|
||||
@@ -0,0 +1,5 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
|
||||
from modules.backup.routes import register
|
||||
|
||||
__all__ = ["register"]
|
||||
@@ -22,7 +22,7 @@ from pathlib import Path
|
||||
from typing import Callable, Optional
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from db_conn import DB_PATH, db_backend
|
||||
from modules.core.db_conn import DB_PATH, db_backend
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -107,7 +107,8 @@ cp -a uploads/. /opt/qihuo/uploads/
|
||||
|
||||
|
||||
def _app_root() -> Path:
|
||||
return Path(os.path.dirname(os.path.abspath(__file__)))
|
||||
from modules.core.paths import ROOT
|
||||
return ROOT
|
||||
|
||||
|
||||
def default_backup_dir() -> str:
|
||||
@@ -0,0 +1,78 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
"""HTTP routes for backup module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime
|
||||
|
||||
from flask import (
|
||||
Response,
|
||||
flash,
|
||||
jsonify,
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
send_file,
|
||||
session,
|
||||
stream_with_context,
|
||||
url_for,
|
||||
)
|
||||
|
||||
|
||||
def register(deps) -> None:
|
||||
app = deps.app
|
||||
login_required = deps.login_required
|
||||
require_nav = deps.require_nav
|
||||
get_db = deps.get_db
|
||||
get_setting = deps.get_setting
|
||||
set_setting = deps.set_setting
|
||||
fetch_price = deps.fetch_price
|
||||
send_wechat_msg = deps.send_wechat_msg
|
||||
touch_stats_cache = deps.touch_stats_cache
|
||||
get_stats_data = deps.get_stats_data
|
||||
build_market_quote_payload = deps.build_market_quote_payload
|
||||
today_str = deps.today_str
|
||||
expire_old_plans = deps.expire_old_plans
|
||||
TZ = deps.tz
|
||||
DB_PATH = deps.db_path
|
||||
UPLOAD_DIR = deps.upload_dir
|
||||
OPEN_TYPES = deps.open_types
|
||||
EXIT_TRIGGERS = deps.exit_triggers
|
||||
BEHAVIOR_TAGS = deps.behavior_tags
|
||||
KLINE_PERIODS = deps.kline_periods
|
||||
KLINE_CUTOFFS = deps.kline_cutoffs
|
||||
calc_holding_duration = deps.calc_holding_duration
|
||||
holding_to_minutes = deps.holding_to_minutes
|
||||
classify_close_result = deps.classify_close_result
|
||||
calc_rr_ratio = deps.calc_rr_ratio
|
||||
calc_theoretical_pnl = deps.calc_theoretical_pnl
|
||||
parse_review_date_filter = deps.parse_review_date_filter
|
||||
_trading_mode = deps.trading_mode
|
||||
_ua_is_phone = deps.ua_is_phone
|
||||
_static_asset_v = deps.static_asset_v
|
||||
|
||||
from modules.backup.db_backup import list_backups, resolve_backup_file
|
||||
|
||||
@app.route("/api/backup/list")
|
||||
@login_required
|
||||
def api_backup_list():
|
||||
return jsonify(
|
||||
{
|
||||
"dir": str(backup_dir()),
|
||||
"last_at": get_backup_last_at(get_setting),
|
||||
"running": backup_in_progress(),
|
||||
"items": list_backups(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/api/backup/download/<filename>")
|
||||
@login_required
|
||||
def api_backup_download(filename):
|
||||
from flask import send_file
|
||||
|
||||
try:
|
||||
path = resolve_backup_file(filename)
|
||||
except (ValueError, FileNotFoundError) as exc:
|
||||
return jsonify({"error": str(exc)}), 404
|
||||
return send_file(path, as_attachment=True, download_name=path.name)
|
||||
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
|
||||
"""Core bootstrap and shared types."""
|
||||
|
||||
from modules.core.bootstrap import register_all_modules, start_module_workers
|
||||
from modules.core.deps import AppDeps
|
||||
|
||||
__all__ = ["AppDeps", "register_all_modules", "start_module_workers"]
|
||||
@@ -0,0 +1,55 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
|
||||
"""Application module registry and startup wiring."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from modules.core.deps import AppDeps
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Registration order: core services first, trading last among features.
|
||||
_MODULE_NAMES = (
|
||||
"modules.web",
|
||||
"modules.market",
|
||||
"modules.keys",
|
||||
"modules.plans",
|
||||
"modules.notify",
|
||||
"modules.records",
|
||||
"modules.stats",
|
||||
"modules.fees",
|
||||
"modules.backup",
|
||||
"modules.settings",
|
||||
"modules.risk",
|
||||
"modules.strategy",
|
||||
"modules.ctp",
|
||||
"modules.trading",
|
||||
)
|
||||
|
||||
|
||||
def register_all_modules(deps: "AppDeps") -> None:
|
||||
for name in _MODULE_NAMES:
|
||||
mod = importlib.import_module(name)
|
||||
register = getattr(mod, "register", None)
|
||||
if not callable(register):
|
||||
logger.warning("module %s has no register()", name)
|
||||
continue
|
||||
register(deps)
|
||||
logger.debug("registered %s", name)
|
||||
|
||||
|
||||
def start_module_workers(deps: "AppDeps") -> None:
|
||||
"""Background threads owned by feature modules."""
|
||||
from modules.ctp.vnpy_bridge import try_init_vnpy
|
||||
|
||||
try_init_vnpy({})
|
||||
for name in ("modules.market",):
|
||||
mod = importlib.import_module(name)
|
||||
start = getattr(mod, "start_workers", None)
|
||||
if callable(start):
|
||||
start(deps)
|
||||
@@ -10,8 +10,8 @@ from typing import Any, Optional
|
||||
|
||||
import requests
|
||||
|
||||
from contract_specs import get_contract_spec
|
||||
from symbols import ths_to_codes, search_symbols
|
||||
from modules.core.contract_specs import get_contract_spec
|
||||
from modules.core.symbols import ths_to_codes, search_symbols
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -101,7 +101,7 @@ def margin_one_lot(
|
||||
est = round(float(price) * spec["mult"] * spec["margin_rate"], 2)
|
||||
if trading_mode:
|
||||
try:
|
||||
from vnpy_bridge import ctp_estimate_margin_one_lot, ctp_lookup_contract_spec, ctp_status
|
||||
from modules.ctp.vnpy_bridge import ctp_estimate_margin_one_lot, ctp_lookup_contract_spec, ctp_status
|
||||
|
||||
if ctp_status(trading_mode).get("connected"):
|
||||
ctp_margin = ctp_estimate_margin_one_lot(
|
||||
@@ -13,7 +13,9 @@ import threading
|
||||
import time
|
||||
from typing import Any, Iterable, Optional, Sequence
|
||||
|
||||
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "futures.db")
|
||||
from modules.core.paths import DB_PATH as _ROOT_DB_PATH
|
||||
|
||||
DB_PATH = _ROOT_DB_PATH
|
||||
|
||||
_backend_lock = threading.Lock()
|
||||
_backend: Optional[str] = None
|
||||
@@ -0,0 +1,46 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
|
||||
"""Shared dependencies passed into each feature module at register() time."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class AppDeps:
|
||||
app: Any
|
||||
get_db: Callable
|
||||
get_setting: Callable
|
||||
set_setting: Callable
|
||||
login_required: Callable
|
||||
require_nav: Callable
|
||||
fetch_price: Callable
|
||||
send_wechat_msg: Callable
|
||||
touch_stats_cache: Callable
|
||||
get_stats_data: Callable
|
||||
build_market_quote_payload: Callable
|
||||
today_str: Callable
|
||||
expire_old_plans: Callable
|
||||
check_order_plans: Callable
|
||||
check_key_monitors: Callable
|
||||
background_task: Callable
|
||||
start_background_threads: Callable
|
||||
tz: Any
|
||||
db_path: str
|
||||
upload_dir: str
|
||||
open_types: list
|
||||
exit_triggers: list
|
||||
behavior_tags: list
|
||||
kline_periods: list
|
||||
kline_cutoffs: list
|
||||
calc_holding_duration: Callable
|
||||
holding_to_minutes: Callable
|
||||
classify_close_result: Callable
|
||||
calc_rr_ratio: Callable
|
||||
calc_theoretical_pnl: Callable
|
||||
parse_review_date_filter: Callable
|
||||
trading_mode: Callable
|
||||
static_asset_v: Callable
|
||||
ua_is_phone: Callable
|
||||
@@ -9,12 +9,26 @@ from __future__ import annotations
|
||||
import os
|
||||
import re
|
||||
|
||||
ENV_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
|
||||
from modules.core.paths import ENV_FILE, LEGACY_ENV_FILE
|
||||
|
||||
|
||||
def _default_env_path() -> str:
|
||||
if ENV_FILE.is_file():
|
||||
return str(ENV_FILE)
|
||||
if LEGACY_ENV_FILE.is_file():
|
||||
return str(LEGACY_ENV_FILE)
|
||||
return str(ENV_FILE)
|
||||
|
||||
|
||||
ENV_PATH = _default_env_path()
|
||||
_KEY_RE = re.compile(r"^([A-Za-z_][A-Za-z0-9_]*)\s*=")
|
||||
|
||||
|
||||
def env_file_path(path: str | None = None) -> str:
|
||||
return path or ENV_PATH
|
||||
if path:
|
||||
return path
|
||||
from modules.core.paths import resolve_env_file
|
||||
return resolve_env_file()
|
||||
|
||||
|
||||
def _quote_env_value(value: str) -> str:
|
||||
@@ -0,0 +1,37 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
|
||||
"""Repository layout paths — single source for config, data, uploads."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# .../qihuo/modules/core/paths.py -> repo root
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
CONFIG_DIR = ROOT / "config"
|
||||
ENV_FILE = CONFIG_DIR / ".env"
|
||||
LEGACY_ENV_FILE = ROOT / ".env"
|
||||
|
||||
DATA_DIR = ROOT / "data"
|
||||
UPLOADS_DIR = ROOT / "uploads"
|
||||
LOGS_DIR = ROOT / "logs"
|
||||
|
||||
DB_PATH = str(ROOT / "futures.db")
|
||||
|
||||
|
||||
def ensure_runtime_dirs() -> None:
|
||||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
UPLOADS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
LOGS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def resolve_env_file() -> str:
|
||||
"""Prefer config/.env, fall back to legacy root .env."""
|
||||
if ENV_FILE.is_file():
|
||||
return str(ENV_FILE)
|
||||
if LEGACY_ENV_FILE.is_file():
|
||||
return str(LEGACY_ENV_FILE)
|
||||
return str(ENV_FILE)
|
||||
@@ -15,7 +15,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
from market import fetch_raw_for_volume, get_price as market_get_price, THS_EX_SUFFIX
|
||||
from modules.market.market import fetch_raw_for_volume, get_price as market_get_price, THS_EX_SUFFIX
|
||||
|
||||
PRODUCTS = [
|
||||
{"name": "白银", "ths": "ag", "sina": "AG", "exchange": "上期所", "ex": "SHFE"},
|
||||
@@ -106,7 +106,7 @@ def product_has_night_session(ths_or_product) -> bool:
|
||||
|
||||
def filter_for_trading_session(rows: list[dict]) -> list[dict]:
|
||||
"""夜盘时段隐藏无夜盘品种。"""
|
||||
from market_sessions import is_night_trading_session
|
||||
from modules.market.market_sessions import is_night_trading_session
|
||||
|
||||
if not is_night_trading_session():
|
||||
return rows
|
||||
@@ -467,8 +467,8 @@ def search_symbols(query: str, *, capital: float | None = None, ctp_connected: b
|
||||
return []
|
||||
|
||||
q_lower = q.lower()
|
||||
from market_sessions import is_night_trading_session
|
||||
from product_recommend import filter_products_for_capital, should_apply_small_account_scope
|
||||
from modules.market.market_sessions import is_night_trading_session
|
||||
from modules.trading.product_recommend import filter_products_for_capital, should_apply_small_account_scope
|
||||
|
||||
night_only = is_night_trading_session()
|
||||
product_pool = PRODUCTS
|
||||
@@ -503,7 +503,7 @@ def search_symbols(query: str, *, capital: float | None = None, ctp_connected: b
|
||||
if capital is not None and should_apply_small_account_scope(
|
||||
capital, ctp_connected=ctp_connected,
|
||||
):
|
||||
from product_recommend import product_in_small_account_whitelist
|
||||
from modules.trading.product_recommend import product_in_small_account_whitelist
|
||||
if not product or not product_in_small_account_whitelist(product):
|
||||
return results
|
||||
raw = fetch_raw_for_volume(codes["sina_code"])
|
||||
@@ -631,7 +631,7 @@ def list_recommended_symbols_grouped(recommend_rows: list[dict]) -> list[dict]:
|
||||
if not product:
|
||||
continue
|
||||
if not product_has_night_session(product):
|
||||
from market_sessions import is_night_trading_session
|
||||
from modules.market.market_sessions import is_night_trading_session
|
||||
if is_night_trading_session():
|
||||
continue
|
||||
seen.add(ths_key)
|
||||
@@ -18,7 +18,7 @@ def get_trading_mode(get_setting: Callable[[str, str], str]) -> str:
|
||||
|
||||
|
||||
def get_sizing_mode(get_setting: Callable[[str, str], str]) -> str:
|
||||
from position_sizing import normalize_sizing_mode
|
||||
from modules.trading.position_sizing import normalize_sizing_mode
|
||||
return normalize_sizing_mode(get_setting("position_sizing_mode", "fixed"))
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ def _cached_ctp_account(mode: str) -> dict[str, float]:
|
||||
import json
|
||||
|
||||
try:
|
||||
from position_stream import position_hub
|
||||
from modules.trading.position_stream import position_hub
|
||||
|
||||
snap = position_hub.get_snapshot() or {}
|
||||
cap = float(snap.get("capital") or 0)
|
||||
@@ -93,7 +93,7 @@ def _cached_ctp_account(mode: str) -> dict[str, float]:
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
from db_conn import connect_db
|
||||
from modules.core.db_conn import connect_db
|
||||
|
||||
conn = connect_db()
|
||||
try:
|
||||
@@ -121,7 +121,7 @@ def _cached_ctp_account(mode: str) -> dict[str, float]:
|
||||
def _ctp_status_from_snapshot(mode: str) -> Optional[dict]:
|
||||
"""读持仓快照中的 CTP 状态,避免页面渲染同步 IPC。"""
|
||||
try:
|
||||
from position_stream import position_hub
|
||||
from modules.trading.position_stream import position_hub
|
||||
|
||||
snap = position_hub.get_snapshot() or {}
|
||||
st = snap.get("ctp_status")
|
||||
@@ -142,7 +142,7 @@ def get_account_capital(conn, get_setting: Callable[[str, str], str]) -> float:
|
||||
if balance > 0:
|
||||
return balance
|
||||
try:
|
||||
from vnpy_bridge import ctp_status, get_ctp_balance
|
||||
from modules.ctp.vnpy_bridge import ctp_status, get_ctp_balance
|
||||
|
||||
st = ctp_status(mode)
|
||||
if st.get("connected"):
|
||||
@@ -159,7 +159,7 @@ def get_account_capital(conn, get_setting: Callable[[str, str], str]) -> float:
|
||||
|
||||
def get_recommend_capital(conn, get_setting: Callable[[str, str], str]) -> float:
|
||||
"""可开仓品种表用权益:已连接 CTP 用柜台权益,未连接固定 10 万。"""
|
||||
from product_recommend import DISCONNECTED_RECOMMEND_CAPITAL
|
||||
from modules.trading.product_recommend import DISCONNECTED_RECOMMEND_CAPITAL
|
||||
|
||||
if is_ctp_connected(get_setting):
|
||||
return get_account_capital(conn, get_setting)
|
||||
@@ -173,7 +173,7 @@ def is_ctp_connected(get_setting: Callable[[str, str], str]) -> bool:
|
||||
if st is not None:
|
||||
return bool(st.get("connected"))
|
||||
try:
|
||||
from vnpy_bridge import ctp_status
|
||||
from modules.ctp.vnpy_bridge import ctp_status
|
||||
|
||||
return bool(ctp_status(mode).get("connected"))
|
||||
except Exception:
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
|
||||
"""CTP / vn.py integration — single-process mode."""
|
||||
|
||||
|
||||
def register(deps) -> None:
|
||||
del deps
|
||||
|
||||
|
||||
__all__ = ["register"]
|
||||
@@ -6,9 +6,9 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from contract_specs import get_contract_spec
|
||||
from ctp_symbol import ths_to_vnpy_symbol
|
||||
from symbols import ths_to_codes
|
||||
from modules.core.contract_specs import get_contract_spec
|
||||
from modules.ctp.ctp_symbol import ths_to_vnpy_symbol
|
||||
from modules.core.symbols import ths_to_codes
|
||||
|
||||
|
||||
def symbols_match(ctp_sym: str, ths: str) -> bool:
|
||||
@@ -11,9 +11,9 @@ import re
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from contract_specs import get_contract_spec
|
||||
from fee_specs import upsert_fee_rate
|
||||
from vnpy_bridge import get_bridge
|
||||
from modules.core.contract_specs import get_contract_spec
|
||||
from modules.fees.fee_specs import upsert_fee_rate
|
||||
from modules.ctp.vnpy_bridge import get_bridge
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -44,7 +44,7 @@ def _collect_main_ths_codes() -> list[str]:
|
||||
"""从主力列表收集同花顺合约代码(供 CTP 手续费查询)。"""
|
||||
from datetime import date
|
||||
|
||||
from symbols import PRODUCTS, build_ths_code, list_main_contracts_grouped
|
||||
from modules.core.symbols import PRODUCTS, build_ths_code, list_main_contracts_grouped
|
||||
|
||||
symbols: list[str] = []
|
||||
for group in list_main_contracts_grouped():
|
||||
@@ -58,7 +58,7 @@ def try_daily_ctp_fee_sync(
|
||||
return 0, "今日已从 CTP 同步过"
|
||||
|
||||
t0 = time.monotonic()
|
||||
from ctp_fee_sync import sync_fees_from_ctp
|
||||
from modules.ctp.ctp_fee_sync import sync_fees_from_ctp
|
||||
|
||||
count, msg = sync_fees_from_ctp(mode)
|
||||
elapsed = time.monotonic() - t0
|
||||
@@ -113,7 +113,7 @@ def start_ctp_fee_worker(
|
||||
time.sleep(20)
|
||||
while True:
|
||||
try:
|
||||
from vnpy_bridge import ctp_status
|
||||
from modules.ctp.vnpy_bridge import ctp_status
|
||||
|
||||
mode = get_mode_fn()
|
||||
st = ctp_status(mode)
|
||||
@@ -9,7 +9,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from kline_chart import (
|
||||
from modules.market.kline_chart import (
|
||||
PERIOD_MINUTES,
|
||||
_aggregate_bars,
|
||||
_bar_datetime,
|
||||
@@ -76,7 +76,7 @@ def compose_period_bars(bars_1m: list, period: str) -> list:
|
||||
def fetch_ctp_klines(symbol: str, period: str, mode: str) -> Optional[list]:
|
||||
"""CTP 已连接时由 tick 聚合 K 线;失败返回 None。"""
|
||||
try:
|
||||
from vnpy_bridge import ctp_status, get_bridge
|
||||
from modules.ctp.vnpy_bridge import ctp_status, get_bridge
|
||||
|
||||
if not ctp_status(mode).get("connected"):
|
||||
return None
|
||||
@@ -12,13 +12,13 @@ import threading
|
||||
import time
|
||||
from typing import Callable
|
||||
|
||||
from market_sessions import (
|
||||
from modules.market.market_sessions import (
|
||||
in_premarket_connect_window,
|
||||
in_postmarket_grace_window,
|
||||
is_trading_session,
|
||||
should_keep_ctp_connected,
|
||||
)
|
||||
from vnpy_bridge import ctp_start_connect, ctp_status
|
||||
from modules.ctp.vnpy_bridge import ctp_start_connect, ctp_status
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -12,9 +12,9 @@ import threading
|
||||
import time
|
||||
from typing import Callable
|
||||
|
||||
from ctp_premarket_connect import premarket_minutes_before, should_auto_connect_now
|
||||
from market_sessions import in_premarket_connect_window, is_trading_session
|
||||
from vnpy_bridge import ctp_try_auto_reconnect
|
||||
from modules.ctp.ctp_premarket_connect import premarket_minutes_before, should_auto_connect_now
|
||||
from modules.market.market_sessions import in_premarket_connect_window, is_trading_session
|
||||
from modules.ctp.vnpy_bridge import ctp_try_auto_reconnect
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -41,7 +41,7 @@ CTP_DISABLED_HINT = "CTP 自动连接已关闭(非交易时段不重连;开
|
||||
def is_ctp_auto_connect_enabled(get_setting=None) -> bool:
|
||||
"""系统设置:是否允许手动连接及非交易时段自动重连(盘前/交易时段计划连接不受此限制)。"""
|
||||
if get_setting is None:
|
||||
from fee_specs import get_setting as _gs
|
||||
from modules.fees.fee_specs import get_setting as _gs
|
||||
|
||||
get_setting = _gs
|
||||
val = (get_setting(CTP_AUTO_CONNECT_KEY, "1") or "1").strip().lower()
|
||||
@@ -60,7 +60,7 @@ def save_ctp_auto_connect(form: Any, set_setting: Callable[[str, str], None]) ->
|
||||
|
||||
|
||||
def _get_db_setting(key: str, default: str = "") -> str:
|
||||
from fee_specs import get_setting
|
||||
from modules.fees.fee_specs import get_setting
|
||||
|
||||
return (get_setting(key, default) or default).strip()
|
||||
|
||||
@@ -9,7 +9,7 @@ from __future__ import annotations
|
||||
import re
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from symbols import ths_to_codes
|
||||
from modules.core.symbols import ths_to_codes
|
||||
|
||||
try:
|
||||
from vnpy.trader.constant import Exchange
|
||||
@@ -12,17 +12,17 @@ from datetime import datetime
|
||||
from typing import Any, Callable, Optional
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from contract_specs import calc_position_metrics
|
||||
from ctp_symbol import ths_to_vnpy_symbol
|
||||
from fee_specs import calc_round_trip_fee
|
||||
from symbols import ths_to_codes
|
||||
from trade_log_lib import (
|
||||
from modules.core.contract_specs import calc_position_metrics
|
||||
from modules.ctp.ctp_symbol import ths_to_vnpy_symbol
|
||||
from modules.fees.fee_specs import calc_round_trip_fee
|
||||
from modules.core.symbols import ths_to_codes
|
||||
from modules.trading.trade_log_lib import (
|
||||
calc_equity_after,
|
||||
purge_duplicate_local_trade_logs,
|
||||
ensure_trade_log_columns,
|
||||
refresh_trade_log_equity_chain,
|
||||
)
|
||||
from vnpy_bridge import ctp_list_trades, ctp_status
|
||||
from modules.ctp.vnpy_bridge import ctp_list_trades, ctp_status
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
TZ = ZoneInfo("Asia/Shanghai")
|
||||
@@ -291,11 +291,11 @@ def sync_trade_logs_from_ctp(
|
||||
)
|
||||
stats["synced"] += 1
|
||||
try:
|
||||
from trade_notify import notify_trade_log_close
|
||||
from trading_context import trading_mode_label
|
||||
from modules.trading.trade_notify import notify_trade_log_close
|
||||
from modules.core.trading_context import trading_mode_label
|
||||
from app import get_setting, send_wechat_msg
|
||||
from ai_worker import schedule_ai_event_analysis
|
||||
from db_conn import DB_PATH
|
||||
from modules.notify.ai_worker import schedule_ai_event_analysis
|
||||
from modules.core.db_conn import DB_PATH
|
||||
|
||||
notify_trade_log_close(
|
||||
send_wechat=send_wechat_msg,
|
||||
@@ -323,7 +323,7 @@ def sync_trade_logs_from_ctp(
|
||||
|
||||
if stats["synced"] or stats["updated"]:
|
||||
try:
|
||||
from stats_engine import refresh_stats_cache
|
||||
from modules.stats.stats_engine import refresh_stats_cache
|
||||
refresh_stats_cache(conn, capital)
|
||||
except Exception as exc:
|
||||
logger.debug("stats refresh after ctp trade sync: %s", exc)
|
||||
@@ -44,7 +44,7 @@ def reconcile_position_avg(
|
||||
) -> dict[str, Any]:
|
||||
"""手数变化时采用柜台回报均价;手数不变时保持已锁定柜台价。"""
|
||||
del tick, trades
|
||||
from ctp_entry_price import round_to_tick
|
||||
from modules.ctp.ctp_entry_price import round_to_tick
|
||||
|
||||
row = dict(new)
|
||||
lots = int(row.get("lots") or 0)
|
||||
@@ -19,15 +19,15 @@ os.environ.setdefault("QIHUO_CTP_ROLE", "worker")
|
||||
|
||||
from flask import Flask, jsonify, request
|
||||
|
||||
from ctp_ipc_client import worker_token
|
||||
from db_conn import DB_PATH, commit_retry, connect_db
|
||||
from fee_specs import get_setting, set_setting
|
||||
from locale_fix import ensure_process_locale
|
||||
from market_sessions import is_trading_session
|
||||
from sl_tp_guard import check_sl_tp_on_tick, ensure_monitor_order_columns, start_sl_tp_guard_worker
|
||||
from modules.ctp.ctp_ipc_client import worker_token
|
||||
from modules.core.db_conn import DB_PATH, commit_retry, connect_db
|
||||
from modules.fees.fee_specs import get_setting, set_setting
|
||||
from modules.core.locale_fix import ensure_process_locale
|
||||
from modules.market.market_sessions import is_trading_session
|
||||
from modules.trading.sl_tp_guard import check_sl_tp_on_tick, ensure_monitor_order_columns, start_sl_tp_guard_worker
|
||||
from strategy.strategy_db import init_strategy_tables
|
||||
from trading_context import get_account_capital, get_trading_mode, get_trailing_be_tick_buffer
|
||||
from vnpy_bridge import (
|
||||
from modules.core.trading_context import get_account_capital, get_trading_mode, get_trailing_be_tick_buffer
|
||||
from modules.ctp.vnpy_bridge import (
|
||||
_ctp_td_lock,
|
||||
ctp_cancel_order,
|
||||
ctp_disconnect,
|
||||
@@ -103,7 +103,7 @@ def _mode_from_request() -> str:
|
||||
|
||||
def _fast_status(mode: str) -> dict[str, Any]:
|
||||
"""Return worker/native bridge state without slow network probing."""
|
||||
from ctp_settings import CTP_DISABLED_HINT, is_ctp_auto_connect_enabled
|
||||
from modules.ctp.ctp_settings import CTP_DISABLED_HINT, is_ctp_auto_connect_enabled
|
||||
|
||||
try:
|
||||
st = dict(get_bridge().status(mode) or {})
|
||||
@@ -255,11 +255,11 @@ def _start_background_workers() -> None:
|
||||
set_tick_sl_tp_callback(_on_tick_sl_tp)
|
||||
set_ctp_connected_callback(_on_ctp_connected)
|
||||
|
||||
from ctp_fee_worker import start_ctp_fee_worker
|
||||
from ctp_premarket_connect import start_ctp_premarket_connect_worker
|
||||
from ctp_reconnect import start_ctp_reconnect_worker
|
||||
from order_pending import reconcile_pending_orders
|
||||
from pending_order_worker import start_pending_order_worker
|
||||
from modules.ctp.ctp_fee_worker import start_ctp_fee_worker
|
||||
from modules.ctp.ctp_premarket_connect import start_ctp_premarket_connect_worker
|
||||
from modules.ctp.ctp_reconnect import start_ctp_reconnect_worker
|
||||
from modules.trading.order_pending import reconcile_pending_orders
|
||||
from modules.trading.pending_order_worker import start_pending_order_worker
|
||||
|
||||
def _mode() -> str:
|
||||
return get_trading_mode(get_setting)
|
||||
@@ -15,14 +15,14 @@ from collections import deque
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
import ctp_ipc_client
|
||||
from locale_fix import ensure_process_locale
|
||||
from modules.core.locale_fix import ensure_process_locale
|
||||
|
||||
if ctp_ipc_client.is_worker_role():
|
||||
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 contract_specs import get_contract_spec
|
||||
from modules.ctp.ctp_settings import live_setting_dict, simnow_setting_dict
|
||||
from modules.ctp.ctp_symbol import ths_to_vnpy_symbol, to_vnpy_exchange
|
||||
from modules.core.contract_specs import get_contract_spec
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -37,11 +37,15 @@ CTP_LAST_ERROR_KEY = "ctp_last_error"
|
||||
|
||||
|
||||
def _use_ctp_worker_client() -> bool:
|
||||
"""默认单进程直连 CTP;仅当显式设置 QIHUO_CTP_WORKER=1 时使用独立 Worker IPC。"""
|
||||
flag = (os.getenv("QIHUO_CTP_WORKER", "") or "").strip().lower()
|
||||
if flag not in ("1", "true", "yes"):
|
||||
return False
|
||||
return not ctp_ipc_client.is_worker_role()
|
||||
|
||||
|
||||
def _persist_login_cooldown(seconds: float) -> None:
|
||||
from fee_specs import get_setting, set_setting
|
||||
from modules.fees.fee_specs import get_setting, set_setting
|
||||
|
||||
new_until = time.time() + max(0.0, seconds)
|
||||
try:
|
||||
@@ -53,7 +57,7 @@ def _persist_login_cooldown(seconds: float) -> None:
|
||||
|
||||
|
||||
def _persisted_login_cooldown_remaining() -> int:
|
||||
from fee_specs import get_setting
|
||||
from modules.fees.fee_specs import get_setting
|
||||
|
||||
try:
|
||||
until = float(get_setting(CTP_COOLDOWN_UNTIL_KEY, "0") or 0)
|
||||
@@ -63,19 +67,19 @@ def _persisted_login_cooldown_remaining() -> int:
|
||||
|
||||
|
||||
def _clear_persisted_login_cooldown() -> None:
|
||||
from fee_specs import set_setting
|
||||
from modules.fees.fee_specs import set_setting
|
||||
|
||||
set_setting(CTP_COOLDOWN_UNTIL_KEY, "0")
|
||||
|
||||
|
||||
def _persist_last_error(msg: str) -> None:
|
||||
from fee_specs import set_setting
|
||||
from modules.fees.fee_specs import set_setting
|
||||
|
||||
set_setting(CTP_LAST_ERROR_KEY, (msg or "").strip())
|
||||
|
||||
|
||||
def _load_persisted_last_error() -> str:
|
||||
from fee_specs import get_setting
|
||||
from modules.fees.fee_specs import get_setting
|
||||
|
||||
return (get_setting(CTP_LAST_ERROR_KEY, "") or "").strip()
|
||||
|
||||
@@ -382,7 +386,7 @@ class CtpBridge:
|
||||
|
||||
def _on_position(event) -> None:
|
||||
try:
|
||||
from ctp_trading_state import trading_state
|
||||
from modules.ctp.ctp_trading_state import trading_state
|
||||
|
||||
pos = event.data
|
||||
row = self._position_row_from_vnpy(pos)
|
||||
@@ -401,7 +405,7 @@ class CtpBridge:
|
||||
if vol <= 0:
|
||||
exchange = getattr(pos, "exchange", None)
|
||||
ex_name = str(exchange.value if hasattr(exchange, "value") else exchange or "")
|
||||
from ctp_trading_state import position_key
|
||||
from modules.ctp.ctp_trading_state import position_key
|
||||
|
||||
trading_state.remove_position(
|
||||
position_key(ex_name, sym, d), notify=False,
|
||||
@@ -433,7 +437,7 @@ class CtpBridge:
|
||||
|
||||
def _on_order(event) -> None:
|
||||
try:
|
||||
from ctp_trading_state import trading_state
|
||||
from modules.ctp.ctp_trading_state import trading_state
|
||||
|
||||
order = event.data
|
||||
row = self._order_row_from_vnpy(order)
|
||||
@@ -540,7 +544,7 @@ class CtpBridge:
|
||||
"td_volume": td,
|
||||
}
|
||||
try:
|
||||
from ctp_entry_price import round_to_tick
|
||||
from modules.ctp.ctp_entry_price import round_to_tick
|
||||
|
||||
ths = CtpBridge._vnpy_sym_to_ths(sym, ex_name) or sym
|
||||
if price > 0:
|
||||
@@ -555,7 +559,7 @@ class CtpBridge:
|
||||
def calibrate_trading_state(self) -> None:
|
||||
"""全量校准内存簿(读 vnpy 缓存,不 query 柜台)。"""
|
||||
try:
|
||||
from ctp_trading_state import trading_state
|
||||
from modules.ctp.ctp_trading_state import trading_state
|
||||
|
||||
with _ctp_td_lock:
|
||||
orders = self.list_active_orders()
|
||||
@@ -650,7 +654,7 @@ class CtpBridge:
|
||||
self._last_position_query_ts = 0.0
|
||||
self._last_instruments_ready_ts = 0.0
|
||||
try:
|
||||
from ctp_trading_state import trading_state
|
||||
from modules.ctp.ctp_trading_state import trading_state
|
||||
|
||||
trading_state.clear()
|
||||
except Exception:
|
||||
@@ -736,7 +740,7 @@ class CtpBridge:
|
||||
}
|
||||
|
||||
def connect(self, mode: str, *, force: bool = False, scheduled: bool = False) -> None:
|
||||
from ctp_settings import CTP_DISABLED_HINT
|
||||
from modules.ctp.ctp_settings import CTP_DISABLED_HINT
|
||||
|
||||
if not _ctp_connect_permitted(scheduled=scheduled):
|
||||
self._last_error = CTP_DISABLED_HINT
|
||||
@@ -852,7 +856,7 @@ class CtpBridge:
|
||||
self, mode: str, *, force: bool = False, scheduled: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""后台连接,不阻塞 HTTP 请求。"""
|
||||
from ctp_settings import CTP_DISABLED_HINT
|
||||
from modules.ctp.ctp_settings import CTP_DISABLED_HINT
|
||||
|
||||
if not _ctp_connect_permitted(scheduled=scheduled):
|
||||
self._last_error = CTP_DISABLED_HINT
|
||||
@@ -1033,7 +1037,7 @@ class CtpBridge:
|
||||
|
||||
def reconnect_after_settings_saved(self, mode: str) -> dict[str, Any]:
|
||||
"""保存前置/账号后关闭旧连接,并用数据库中的新配置重连。"""
|
||||
from ctp_settings import is_ctp_auto_connect_enabled
|
||||
from modules.ctp.ctp_settings import is_ctp_auto_connect_enabled
|
||||
|
||||
self._close_gateway()
|
||||
self._last_error = ""
|
||||
@@ -1048,14 +1052,14 @@ class CtpBridge:
|
||||
def _run() -> None:
|
||||
time.sleep(45)
|
||||
try:
|
||||
from ctp_fee_worker import try_daily_ctp_fee_sync
|
||||
from modules.ctp.ctp_fee_worker import try_daily_ctp_fee_sync
|
||||
|
||||
def _gs(key: str, default: str = "") -> str:
|
||||
from fee_specs import get_setting
|
||||
from modules.fees.fee_specs import get_setting
|
||||
return get_setting(key, default)
|
||||
|
||||
def _ss(key: str, val: str) -> None:
|
||||
from fee_specs import set_setting
|
||||
from modules.fees.fee_specs import set_setting
|
||||
set_setting(key, val)
|
||||
|
||||
try_daily_ctp_fee_sync(
|
||||
@@ -1472,7 +1476,7 @@ class CtpBridge:
|
||||
price = self._price_from_tick(tick)
|
||||
if price and price > 0:
|
||||
try:
|
||||
from ctp_trading_state import trading_state
|
||||
from modules.ctp.ctp_trading_state import trading_state
|
||||
|
||||
trading_state.set_tick_price(ex_s, sym, price)
|
||||
except Exception:
|
||||
@@ -2359,13 +2363,13 @@ def vnpy_available() -> bool:
|
||||
|
||||
def _ctp_connect_permitted(*, scheduled: bool = False) -> bool:
|
||||
"""scheduled=True:盘前/交易时段计划连接,不受「自动连接」开关限制。"""
|
||||
from ctp_settings import is_ctp_auto_connect_enabled
|
||||
from modules.ctp.ctp_settings import is_ctp_auto_connect_enabled
|
||||
|
||||
if is_ctp_auto_connect_enabled():
|
||||
return True
|
||||
if not scheduled:
|
||||
return False
|
||||
from ctp_premarket_connect import should_auto_connect_now
|
||||
from modules.ctp.ctp_premarket_connect import should_auto_connect_now
|
||||
|
||||
return should_auto_connect_now()
|
||||
|
||||
@@ -2375,7 +2379,7 @@ def ctp_disconnect(*, set_disabled_hint: bool = False) -> None:
|
||||
if _use_ctp_worker_client():
|
||||
ctp_ipc_client.disconnect(set_disabled_hint=set_disabled_hint)
|
||||
return
|
||||
from ctp_settings import CTP_DISABLED_HINT
|
||||
from modules.ctp.ctp_settings import CTP_DISABLED_HINT
|
||||
|
||||
b = get_bridge()
|
||||
b._close_gateway()
|
||||
@@ -2450,7 +2454,7 @@ def ctp_try_auto_reconnect(mode: str) -> bool:
|
||||
|
||||
|
||||
def ctp_status(mode: str) -> dict[str, Any]:
|
||||
from ctp_settings import CTP_DISABLED_HINT, is_ctp_auto_connect_enabled
|
||||
from modules.ctp.ctp_settings import CTP_DISABLED_HINT, is_ctp_auto_connect_enabled
|
||||
|
||||
if _use_ctp_worker_client():
|
||||
st = ctp_ipc_client.status(mode)
|
||||
@@ -0,0 +1,5 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
|
||||
from modules.fees.routes import register
|
||||
|
||||
__all__ = ["register"]
|
||||
@@ -10,9 +10,9 @@ import re
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from contract_specs import get_contract_spec
|
||||
from modules.core.contract_specs import get_contract_spec
|
||||
|
||||
from db_conn import connect_db, is_benign_migration_error
|
||||
from modules.core.db_conn import connect_db, is_benign_migration_error
|
||||
|
||||
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "futures.db")
|
||||
DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")
|
||||
@@ -145,7 +145,7 @@ def get_fee_spec(ths_code: str, *, trading_mode: str = "simulation") -> dict:
|
||||
if row:
|
||||
return _row_to_spec(row, mult)
|
||||
try:
|
||||
from ctp_fee_sync import sync_fee_for_symbol
|
||||
from modules.ctp.ctp_fee_sync import sync_fee_for_symbol
|
||||
fields = sync_fee_for_symbol(trading_mode, ths_code)
|
||||
if fields:
|
||||
return {"product": product, **fields}
|
||||
@@ -7,8 +7,8 @@
|
||||
import re
|
||||
from typing import Any, Optional
|
||||
|
||||
from contract_specs import get_contract_spec
|
||||
from fee_specs import get_fee_multiplier, upsert_fee_rate
|
||||
from modules.core.contract_specs import get_contract_spec
|
||||
from modules.fees.fee_specs import get_fee_multiplier, upsert_fee_rate
|
||||
|
||||
|
||||
def _to_float(val: Any) -> float:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user