配置说明
配置加载优先级
所有可调项定义在 backend/app/core/config.py(Pydantic Settings),生效顺序由高到低:
| 优先级 | 来源 | 说明 |
|---|---|---|
| 1 | 系统环境变量(os.environ) | 部署注入(systemd Environment=/shell export),压过文件里的同名项 |
| 2 | .env 文件 | APP_ENV=production 时读 .env + .env.production;否则读 .env + .env.development(后者覆盖同名的 .env 项) |
| 3 | 运行时持久化配置 backend/data/ | db_config.json(Fernet 加密)、model_configs/{admin_uid}.json、auth_settings.json、smtp_config.json、search_config.json —— 即设置页保存的值 |
| 4 | Settings 类默认值 | config.py 中声明的 class defaults |
config.py 用 _BACKEND_DIR = Path(__file__).resolve().parents[2] 反推 backend 根目录再拼
env_file。pydantic-settings 按进程当前工作目录解析相对路径,从 backend/ 之外启动
(python tools/xxx.py、IDE 把工作目录设成仓库根)会让 .env.development 静默找不到,
DB_PASSWORD 退回默认空串,最终报 (1045, Access denied for user 'root'@'localhost' (using password: NO)) —— 看着像密码错,实际是文件没加载。改这段不要退回相对路径。
排查「配置到底生效了哪个值」:GET /health 的 config_summary() 视图(已脱敏)。
凭据落位契约(三档,别放错)
| 档位 | 位置 | 是否入库 | 放什么 |
|---|---|---|---|
| 样例/默认 | backend/.env、backend/.env.example | ✅ 入库 | 占位符或已失效的旧凭据,仅供新环境起手 |
| 本机私有配置 | backend/data/(.gitignore 已覆盖) | ❌ 不入库 | 真实凭据:db_config.json(加密)、model_configs/{uid}.json、auth_settings.json 等 |
| 部署注入 | 环境变量 / systemd 配置 | ❌ 不入库 | 生产凭据(见 deploy/README.md) |
backend/data/.jwt_secret(JWT 签名密钥)、backend/data/.db_key(库加密密钥)、
backend/data/db_config.json、backend/data/model_configs/(AI Key)以及该目录下其余
运行时配置。.env/.env.development 是有意入库的样例配置,不要往里写真实凭据;
一旦发现真实凭据进了仓库,先轮换再迁移,只删文件没用(git 历史仍可读)。
环境变量
数据库
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_NAME=xuangu
DB_POOL_SIZE=20 # 连接池常驻连接
DB_MAX_OVERFLOW=10 # 峰值溢出连接
DB_POOL_TIMEOUT=30 # 取连接等待上限(秒)
MySQL 缺失时后端自动降级 mock 模式(接口仍返回结构,数据为空/样例)。
设置页保存的加密配置 data/db_config.json 优先于以上变量。
数据源与 AI
DATA_SOURCE=real # real / akshare / mock / tushare(详见下表)
TUSHARE_TOKEN= # 预留,接口未完整实现
AI_BASE_URL=https://api.deepseek.com/v1
AI_API_KEY=
AI_MODEL=deepseek-v4-flash
AI_API_KEYS= # 多 Key 负载均衡(逗号分隔,Round-Robin),空则回退 AI_API_KEY
AI_FALLBACK_MODEL= # 主模型连续失败时的降级模型
AI_LLM_MAX_RETRIES=2 # LLM 调用重试次数
AI_LLM_RETRY_DELAY=1.0 # 重试间隔(秒)
进程角色与调度器(🔴 最容易踩的一块)
XUANGU_ROLE=all # all(默认)/ web / worker —— 见「进程模型」小节
ENABLE_SCHEDULER=true # 调度器总开关
SCHEDULER_WARMUP_MARKET=true # 交易时段预热市场聚合快照
SCHEDULER_EOD_SYNC=true # 收盘后同步自选股日终数据
SCHEDULER_BASEINFO_REFRESH=true # 交易时段刷新全市场基础信息
SCHEDULER_BASEINFO_INTERVAL=600 # 上者的刷新间隔(秒,仅交易时段生效)
XUANGU_ROLE 由 app/core/roles.py 调用时读取 os.environ(刻意不做成模块常量:
测试用 monkeypatch.setenv 切角色,且 web/worker 两个子进程的环境变量各自独立)。
未设置或值非法一律按 all(= 历史单进程行为,最安全)。
| 角色 | 谁在用 | 行为 |
|---|---|---|
all(默认) | 单进程部署 / 测试 | API + 调度器同进程 |
web | uvicorn 进程 | 只服务 API,不起调度器、不参与 leader 竞选 |
worker | backend/worker.py | 只跑调度器 + 命令队列 + 心跳,不起 HTTP |
联网搜索与邮件通知
TAVILY_API_KEY=
WEB_SEARCH_PROVIDER=auto # auto(有 Tavily Key 时并行,否则纯 DuckDuckGo)/ tavily / duckduckgo
WEB_SEARCH_MAX_RESULTS=6
MODEL_WEB_SEARCH=true # DeepSeek Responses API 服务端联网搜索;不支持的模型自动回退本地检索
SMTP_HOST=smtp.qq.com
SMTP_PORT=465
SMTP_USER= # 发件邮箱
SMTP_PASS= # SMTP 授权码(不是登录密码)
SMTP_FROM= # 发件人显示地址,留空等同 SMTP_USER
ENABLE_EMAIL_NOTIFICATION=false # 邮件通知总开关
APP_BASE_URL=https://pyminer.com # 邮件内跳转链接前缀
ENABLE_PORTFOLIO_MONITOR=true # 持仓异动监控开关
PRICE_ALERT_THRESHOLD=5.0 # 涨跌幅阈值 (%)
VOLUME_ALERT_RATIO=2.0 # 量比阈值
STOP_LOSS_PROXIMITY=3.0 # 逼近止损距离 (%)
安全
ADMIN_SEED_USERNAME=admin
ADMIN_SEED_PASSWORD= # 留空则跳过种子管理员创建
ADMIN_SEED_EMAIL=
JWT_TTL_HOURS=24 # Token 有效期(小时)
JWT_ISSUER=xuangu
JWT_AUDIENCE=xuangu-web
JWT 签名密钥落在 backend/data/.jwt_secret(首次启动随机生成 32 字节,不入库)。
日志
LOG_FORMAT=json
LOG_LEVEL=INFO
# LOG_FILE=logs/run.log # 默认注释:留空仅输出控制台
设置 LOG_FILE 后日志同时落盘到 backend/logs/(相对 backend 目录解析,自动建目录),
按 10MB 轮转保留 3 份;logs/ 已被 .gitignore 忽略。文件通道同样启用敏感信息脱敏。
数据源运行参数
DATA_SOURCE 选项
| 值 | 说明 | 依赖 |
|---|---|---|
real | 多源降级(推荐):腾讯/TDX 协议/东财/新浪内置实现 | 无额外数据源依赖 |
akshare | 等效 akshare(直连东财/新浪 API,零额外依赖) | 无额外依赖 |
tushare | 预留(接口未完整实现,调用即抛 NotImplementedError) | TUSHARE_TOKEN |
mock | 模拟数据 | 无 |
限流与连接池
| 变量 | 默认值 | 说明 |
|---|---|---|
EM_MIN_INTERVAL | 0.3 | 两次东财请求最小间隔(秒);0.3 是批量同步实测安全值 |
EM_JITTER_MIN / EM_JITTER_MAX | 0.1 / 0.5 | 随机抖动范围(秒) |
TENCENT_MIN_INTERVAL | 0.2 | 两次腾讯请求最小间隔(秒) |
XQ_MIN_INTERVAL | 1.0 | 两次雪球请求最小间隔(秒,雪球风控较严) |
HOST_FAILURE_THRESHOLD | 2 | 东财主机连续失败 N 次才冷却 |
STOCK_CHANGES_CONCURRENCY | 3 | 盘中异动(push2ex)并发上限 |
HTTP_MAX_CONNECTIONS / HTTP_MAX_KEEPALIVE / HTTP_KEEPALIVE_EXPIRY | 20 / 10 / 30 | httpx 连接池 |
HTTP_TIMEOUT / HTTP_CONNECT_TIMEOUT | 10.0 / 5.0 | 默认请求/连接超时(秒) |
RETRY_BUDGET_MAX | 6 | 单次用户请求的最大外部调用次数(重试预算) |
EOD_STOCK_THROTTLE_MIN / EOD_STOCK_THROTTLE_MAX | 0.5 / 1.5 | 逐股同步随机节流区间(秒) |
VALUATION_THS_EPS_ENABLED | true | 估值扩展字段(前瞻 PE/PEG/一致预期)走同花顺 F10 逐股抓取;全市场融合会发 1.1 万次 HTTP,批量回填/低配环境可设 false(fox_valuation 仍保留腾讯口径) |
熔断器参数
| 变量 | 默认值 | 说明 |
|---|---|---|
EM_BREAKER_FAILURE_THRESHOLD | 4 | 主行情熔断:连续失败 N 次触发 |
EM_BREAKER_COOLDOWN_SEC | 120 | 主行情熔断冷却(秒) |
盘中异动(push2ex)用的是独立的 SourceCircuitBreaker,阈值 2 / 冷却 30s 写死在
services/data_sources/stock_changes.py 里,刻意不共用 EM_BREAKER_*(该域名风控更敏感,
要更快切断)。详见熔断器与限流。
缓存 TTL(交易时段)
分级依据是数据在盘中的变化频率;SSE 侧 QuoteService 每 3s 轮询、有新数据即推,
回源频率由下列 TTL 收敛(TTL 越长,同样推送频率下回源越少):
| 变量 | 默认值 | 数据 |
|---|---|---|
CACHE_TTL_SENTIMENT | 10 | 指数/涨跌家数(秒级变化) |
CACHE_TTL_DISTRIBUTION | 10 | 涨跌幅分布直方图 |
CACHE_TTL_FUND_FLOW | 15 | 资金流概况 |
CACHE_TTL_INDUSTRY_RANK | 30 | 行业排名 |
CACHE_TTL_HOT_STOCKS | 10 | 热门股票排行 |
CACHE_TTL_SECTOR_TREEMAP | 15 | 板块热力图 |
CACHE_TTL_IDLE | 3600 | 休市时统一 TTL |
运行时配置(SysConfig)与调度开关
上面是进程启动前的静态配置。运行期可改的参数落在 SysConfig(数据库表), 改完下个调度 tick(30s)即生效,无需重启:
| 键 | 用途 |
|---|---|
sched.{key}.enabled / sched.{key}.trigger_hm | 单个定时任务的开关与触发时间 |
sched.{key}.last_success_date | 补跑判据。🔴 记的是应执行日(数据日),不是运行日 |
data.cleanup.retention_days | 过期数据保留天数(默认 3 年,下限 30 天) |
pattern.{group}.{field} | K线形态识别阈值覆盖(60s TTL,改后须重跑 quant_calc) |
runtime.worker.heartbeat_at / runtime.worker.pid | worker 心跳(90s 判鲜),任务中心据此显示调度器在哪 |
runtime.sched.cmd.{id} | 跨进程手动触发的命令队列 |
入口:GET/PUT /datamgr/config、GET/PUT /datamgr/schedule/{key}、
POST /datamgr/schedule/{key}/run(详见任务中心与进程模型)。
数据库初始化
python backend/run_init_db.py # 执行初始化脚本
mysql -u root -p xuangu < database/init.sql # 或手动执行 SQL
应用启动时 ensure_tables() 还会幂等 create_all(checkfirst=True) 补建缺失表,
因此新增 ORM 模型不必先改 SQL 脚本。
依赖安装
Python 依赖清单统一为 python/requirements.txt(python/ 目录下仅此文件入库)。
# Windows:使用项目内置 Python
cd python && python.exe get-pip.py && python.exe -m pip install -r requirements.txt
# Linux / macOS:虚拟环境约定放在 backend/venv
cd backend && python3 -m venv venv && venv/bin/pip install -r ../python/requirements.txt
# 前端依赖(拉取代码后若报 Cannot find module,先跑这条)
npm install
启动服务
# 一键脚本(仓库根,Windows 用 .bat / Linux·macOS 用 .sh,两平台同名)
./start-backend.sh # 后端 → http://localhost:6789
./start-frontend.sh # 前端 → http://localhost:1420
# 手动等价命令
cd backend && venv/bin/python run.py # Windows:cd backend && python\python.exe run.py
npm run dev
backend/run.py 默认拉起两个子进程:XUANGU_ROLE=worker python worker.py(调度器,
不重载)与 XUANGU_ROLE=web python -m uvicorn app.main:app(API)。
- 改
app/下的代码:保存后 1~2s 自动重启 web 子进程即生效(热重载由run.py自己轮询 mtime 实现,刻意不用 uvicorn 的--reload:win32 下其重启分支用CTRL_C_EVENT投信号,在无控制台/nohup启动时投不出去,reloader 会永久卡死)。 - 改调度器/任务代码:worker 不随热重载,必须重启
run.py。 - 端口 6789 已被占用时
run.py会拒绝起第二个实例并给出查/杀 PID 的命令。 - 脚本只「检查环境 → 启动」:后端固定用
backend/venv,缺失即报错不回退系统 Python。
细节见任务中心与进程模型。