深色模式
数据服务说明文档(服务器端采集 + 客户端同步)
适用版本:Futures.Server.Api(api.bywl.top)+ Futures.Desktop(客户端) 关联文档:
server/数据库文档.md(25 张表结构 + JSONB 通用表)、docs/行情数据源接入协议.md
一、系统架构
【服务器端】api.bywl.top(独立 VPS / 容器)
┌──────────────────────────────────────────────────────────┐
│ Futures.Server.Api(C# ASP.NET Core WebAPI, net9.0) │
│ ├─ TushareCollector ← 复用 Futures.Core.TushareApiClient(token/代理/重试)│
│ ├─ DataCollectorHostedService ← 每日盘后定时采集 │
│ ├─ ServerDataStore ← 服务器 PG(srv_* 表 + JSONB) │
│ └─ DataController ← GET /data/* REST │
│ PostgreSQL(futures_srv 库) │
└──────────────────────┬───────────────────────────────────┘
│ HTTPS(客户端 DataServerUrl)
▼
【客户端】客户机器
┌──────────────────────────────────────────────────────────┐
│ DataSyncService(启动时 + 每 6 小时定时同步) │
│ ├─ 拉取 JSON → 反序列化为 Futures.Core 模型 │
│ └─ 复用持久化服务写本地 PG(futures 库,原表不变) │
│ 研判页 / 持仓排名 / 仓单 / 合约参数 / 股票(后续)读本地库 │
└──────────────────────────────────────────────────────────┘设计要点:
- 服务器是唯一数据持有方(Tushare token / 天勤账号只存在于服务器),客户端零第三方账号依赖;
- 客户端本地库是数据仓库——分析页全部读本地 PG,断网可用、查询快;
- REST 是管道,字段名与 Tushare 一致,两端共用 Futures.Core 模型,零映射成本。
二、服务器端部署
2.1 前置要求
- 一台 Linux/Windows 服务器(建议 Linux + Docker 或 systemd)
- PostgreSQL(建议 14+),建库:
CREATE DATABASE futures_srv; - Tushare 账号(token,积分 ≥2000 覆盖大部分接口;5000 覆盖全部)
2.2 构建发布
bash
# 在项目根目录(有 Futures.Server.slnx)
dotnet publish server/Futures.Server.Api -c Release -r linux-x64 --self-contained false -o publish/server
# 将 publish/server 上传到服务器,配置守护进程(systemd/supervisor/Docker)2.3 配置(appsettings.json 或环境变量)
json
{
"PgHost": "localhost", "PgPort": 5432, "PgDatabase": "futures_srv",
"PgUser": "postgres", "PgPassword": "******",
"TushareToken": "服务器持有",
"TushareProxyEnabled": false, "TushareProxyUrl": "",
"TushareBypassSsl": false, "TushareUseHostHeader": false
}环境变量可覆盖:PG_HOST / PG_PORT / PG_DATABASE / PG_USER / PG_PASSWORD / TUSHARE_TOKEN(容器部署友好)。
2.4 启动
- 首次启动自动建表(
EnsureTablesAsync,25 张专用表 + 1 张 JSONB 通用表); - 启动后 30 秒自动采集一次(部署当天即有数据);
- 之后每 5 分钟检查:工作日 15:35 / 17:05 后进入采集窗口执行全部接口。
2.5 鉴权建议
公网部署时在反向代理(nginx)加 X-Api-Key 校验或 IP 白名单;客户端配置 DataServerApiKey 后请求带 ?key=。 注意:/admin 管理界面无内置鉴权,公网部署务必在反向代理加访问控制或仅内网开放。
2.6 管理界面
浏览器访问 http://api.bywl.top/admin 打开数据采集管理页:
- 显示 26 张表的数据行数(
/admin/status); - 列出全部专用接口 + 通用接口清单(
/admin/interfaces); - 支持填日期后逐接口触发采集 / 一键采集全部(POST /collect/*)。
2.7 数据库初始化 SQL
服务器预建库可执行 server/sql/init_database.sql(psql):
bash
psql -U postgres -h localhost -c "CREATE DATABASE futures_srv;"
psql -U postgres -h localhost -d futures_srv -f server/sql/init_database.sql包含 26 张表 DDL + 日期索引,幂等可重复执行。程序启动时也会自动建表(二选一即可)。
2.8 宝塔(bt.cn)部署注意事项 ⭐
服务器是宝塔面板时,按以下核对,否则会遇到"表数据量不变/看不到数据":
| 检查项 | 说明 |
|---|---|
| PostgreSQL 是否装/启动 | 宝塔 → 软件商店 → 安装 PostgreSQL(14+)。确认 5432 端口监听:ss -ltn | grep 5432 |
| PG 连接信息 | 宝塔里创建的数据库名/用户名/密码 → 填进 appsettings.json 的 PgHost/PgPort/PgDatabase/PgUser/PgPassword(注意宝塔 PG 默认端口可能就是 5432,用户名不一定 postgres) |
| TushareToken 必须填 | 服务器采集数据靠 Tushare API;token 为空 = 所有采集失败 = 表数据永远不变。填你自己的 token(客户端 appsettings 里的 aa344fe8... 或服务器专用) |
| API 进程是否常驻 | /admin 能打开 ≠ 定时任务在跑。确认进程活着:systemctl status futures-server 或 ps aux | grep Futures.Server;日志看采集是否报错 |
| 时间窗口 | 采集只在工作日 15:35 后触发。工作日白天看 /admin 表数据不变是正常的 |
| 手动触发验证 | /admin 页面(或 POST /collect/xxx)手动触发一次采集 → 看 /admin/status 行数是否增长 → 若仍 0,查服务器日志的采集错误(多为 token/网络/积分) |
2.8.1 宝塔 PG 的连接与查询(三种方式)
方式 A:宝塔 Web 管理(推荐,无需 SSH)
- 宝塔面板 → 数据库 → 找到
futures_srv库 - 若宝塔装了 phpMyAdmin/pgAdmin 插件 → 点「管理」入口进入 Web 控制台
- 执行查询,例如:sql
SELECT count(*) FROM srv_fut_daily; SELECT max(trade_date) FROM srv_fut_daily; -- 看最新数据日期
方式 B:服务器 SSH 用 psql
bash
# 方式 B1:以 postgres 系统用户执行
su - postgres -c "psql -d futures_srv -c 'SELECT count(*) FROM srv_fut_daily;'"
# 方式 B2:直接连接(需知道宝塔创建的 PG 密码)
psql -h 127.0.0.1 -p 5432 -U 宝塔用户 -d futures_srv \
-c "SELECT count(*) FROM srv_fut_daily;"
# 输入密码即可
# 方式 B3:免密(.pgpass)——宝塔 PG 目录通常在 /www/server/pgsql/
echo "127.0.0.1:5432:futures_srv:用户名:密码" > ~/.pgpass && chmod 600 ~/.pgpass
psql -h 127.0.0.1 -d futures_srv -c "SELECT count(*) FROM srv_fut_daily;"方式 C:通过 API 间接验证(最稳,不碰数据库)
bash
# 直接调数据接口看有无数据返回
curl "https://api.bywl.top/data/fut_daily?start=20260801&end=20260820"
curl "https://api.bywl.top/data/fut_holding?start=20260801&end=20260820"
# 返回 JSON 数组有内容 = 数据库有数据;空数组 = 确实没采到宝塔 PG 路径参考:二进制
/www/server/pgsql/bin/psql,数据目录/www/server/pgsql/data/。 常见排查:宝塔 PG 默认只监听 127.0.0.1,若appsettings.json的PgHost写了别的地址会连不上。
2.8.2 手动触发采集验证(确认数据链路通)
方式 A:/admin 管理页(图形界面)
- 浏览器打开
https://api.bywl.top/admin - 页面列出全部接口 + 每张表行数(
/admin/status) - 点某接口的「采集」按钮(或「一键采集全部」)→ 填日期(默认当天)
- 等 10-60 秒(Tushare 拉取 + 落库)→ 刷新页面看行数是否增长
方式 B:POST /collect/ 直接触发*
bash
# 各接口采集(返回 {success, rows, message})
curl -X POST "https://api.bywl.top/collect/fut_daily?trade_date=20260820"
curl -X POST "https://api.bywl.top/collect/fut_holding?trade_date=20260820"
curl -X POST "https://api.bywl.top/collect/fut_wsr?trade_date=20260820"
# 一键全部
curl -X POST "https://api.bywl.top/collect/all"验证判断:
rows > 0→ 采集成功,数据已落库 → 问题在客户端同步环节rows = 0且 message 含"无数据/失败" → 看 message 具体错误(token/积分/网络/日期非交易日)- 采集后
/admin行数仍 0 → 查服务器日志(journalctl -u futures-server或宝塔网站运行日志)里的Collect*错误
⚠️ 注意:手动触发不受时间窗口限制(随时可跑);但非交易日拉当日数据返回空是正常的。
三、数据接口总览(46 类)
3.1 期货(13 个,专用表)
| REST 端点 | Tushare 接口 | 积分 | 表 |
|---|---|---|---|
| /data/fut_basic | fut_basic | 2000 | srv_fut_basic |
| /data/fut_trade_cal | fut_trade_cal | 2000 | srv_fut_trade_cal |
| /data/fut_daily | fut_daily | 2000 | srv_fut_daily |
| /data/fut_holding | fut_holding | 2000 | srv_fut_holding |
| /data/fut_wsr | fut_wsr | 2000 | srv_fut_wsr |
| /data/fut_settle | fut_settle | 2000 | srv_fut_settle |
| /data/fut_mapping | fut_mapping | 2000 | srv_fut_mapping |
| /data/opt_basic | opt_basic | 2000 | srv_opt_basic |
| /data/opt_daily | opt_daily | 2000 | srv_opt_daily |
| /data/fut_weekly_monthly | fut_weekly_monthly | 2000 | srv_fut_weekly_monthly |
| /data/fut_index_daily | fut_index_daily | 2000 | srv_fut_index_daily |
| /data/fut_weekly_detail | fut_weekly_detail | 600 | srv_fut_weekly_detail |
| /data/ft_limit | ft_limit | 5000 | srv_ft_limit |
3.2 股票核心(12 个,专用表)
| REST 端点 | Tushare 接口 | 积分 | 表 |
|---|---|---|---|
| /data/stock_daily | daily | 2000 | srv_stock_daily |
| /data/stock_adj_factor | adj_factor | 2000 | srv_stock_adj_factor |
| /data/stock_daily_basic | daily_basic | 2000 | srv_stock_daily_basic |
| /data/stock_trade_cal | trade_cal | 2000 | srv_stock_trade_cal |
| /data/stock_namechange | namechange | 120 | srv_stock_namechange |
| /data/stock_company | stock_company | 120 | srv_stock_company |
| /data/stock_new_share | new_share | 120 | srv_stock_new_share |
| /data/stock_limit | stk_limit | 2000 | srv_stock_limit |
| /data/stock_suspend | suspend_d | 120 | srv_stock_suspend |
| /data/stock_moneyflow | moneyflow | 2000 | srv_stock_moneyflow |
| /data/stock_top_list | top_list | 2000 | srv_stock_top_list |
| /data/stock_top_inst | top_inst | 5000 | srv_stock_top_inst |
3.3 通用 JSONB(21 个,srv_api_data 表)
| REST 端点 | 覆盖接口 |
|---|---|
| /data/api/ | 财务(10):income/balancesheet/cashflow/forecast/express/dividend/fina_indicator/fina_audit/fina_mainbz/disclosure_date;基础(3):stock_st/hs_const/stk_managers;行情(2):stk_weekly_monthly/hsgt_top10;资金(4):moneyflow_ths/moneyflow_dc/moneyflow_ind_ths/moneyflow_ind_dc;打板(1):limit_list_d;特色(4):ths_index/ths_member/concept/concept_detail |
通用接口按行内
trade_date/end_date/ann_date分组存 JSONB,查询GET /data/api/{name}?start=&end=。 个别接口名/权限未逐一验证(如 ths_member/concept),采集失败记日志,可在TushareCollector.GenericApis清单修正。
四、手动触发采集(运维)
bash
# 单个接口
POST /collect/holding?date=20260815
POST /collect/stock_daily?date=20260815
POST /collect/api/income?date=20260815 # 通用接口
# 全部接口
POST /collect/all?date=20260815支持类型见 CollectController;采集失败不影响其他接口。
五、客户端配置与使用
5.1 设置页填写指南(系统设置 → 数据服务)
| 字段 | 填写 | 说明 |
|---|---|---|
| 数据服务地址 | https://api.bywl.top | 服务器 API 基址(也可内网直连 http://服务器IP:端口) |
| 访问密钥 | 留空 或 服务器约定 key | 当前服务器端未校验,留空即可;公网建议 nginx 加访问控制 |
留空 = 不启用数据同步(客户端直连 Tushare/天勤);填写后客户端从服务器拉盘后数据。 首启向导步骤 5 也有同名字段,新客户首次安装时可直接填写。
填写后的行为:
- 客户端启动时 →
SyncAllAsync(7天)立即同步一次 - 之后每 6 小时自动同步(幂等 UPSERT,重复无副作用)
- 同步数据:持仓排名 / 仓单 / 结算参数 / 主力映射 / 期货日线 / 期权日线 / IV 快照
5.2 客户端同步机制(7 类数据 + 验证位置)
SyncAllAsync 每次同步 7 类数据(启动时一次 + 每 6 小时定时 + 立即同步):
| # | 数据 | 服务器接口 | 写入本地表 | 验证位置(对应页面) |
|---|---|---|---|---|
| 1 | 持仓排名 | /data/fut_holding | fut_holding_records | 持仓龙虎榜、席位趋势、期货研判"席位"因子 |
| 2 | 仓单日报 | /data/fut_wsr | fut_wsr | 仓单分析、期货研判"库存仓单"因子 |
| 3 | 结算参数 | /data/fut_settle | contract_params(保护 manual 行) | 合约参数页(保证金/手续费) |
| 4 | 主力映射 | /data/fut_mapping | fut_mappings | 主力合约映射页、各分析"主力合约"解析 |
| 5 | 期货日线 | /data/fut_daily | quotes_daily | 回测/信号/基差/价差/板块/K线 |
| 6 | 期权日线 | /data/opt_daily | quotes_option_daily | 期权回测/期权列表/期权研判 |
| 7 | IV 快照 | 本地聚合(非服务器拉取) | option_iv_daily | 波动率百分位(IV 位置) |
逐项核对:持仓龙虎榜=08-20 · 仓单分析=08-19 · 合约参数=最近交易日费率 · 主力映射=08-20 · 期货K线最新=08-20 · 期权列表最新=08-20。 股票数据(个股日线/财务/资金流)不走本同步——由客户端股票模块自己的调度任务采集。
5.3 同步状态查看
系统监控页 → 盘后数据同步:
- 未配置 → "未配置数据服务地址(设置页 → 数据服务)"
- 已同步 → "已同步 X/7 类,共 N 条 | 最近 HH:mm"
- 可点击「立即同步」手动触发
六、数据库说明
- 服务器库(futures_srv):25 张专用表(srv_*)+ 1 张通用表(srv_api_data),结构见
server/数据库文档.md; - 客户端库(futures,原有):表结构与改造前一致,
DataSyncService复用原持久化服务写入; - 索引:所有按日期查询的表已建
trade_date相关索引(DDL 内含),大数据量表(股票日线等)务必保留。
实时行情数据保留策略(2026-08-20)
- 结论:
quotes_realtime(期货/外盘 tick)与quotes_option_realtime(期权 tick)隔天即无分析价值 (分析走quotes_daily/quotes_option_daily日线),已配置 7 天自动保留。 - 配置:TimescaleDB
add_retention_policy('quotes_realtime', INTERVAL '7 days')(期权表同理), 后台任务每天自动删除 7 天前的 chunk。 - 首次清理:
SELECT drop_chunks('quotes_realtime', (now() - interval '7 days')::timestamp);两表共释放约 1.4 亿行(8453万→2321万 / 9453万→1635万)。 - 监控页卡顿修复:
GetDatabaseStatusAsync对两张实时表改用 7 天窗口 COUNT(原全表 COUNT 扫 8000万+ 行需 ~6s), 9 表加载总耗时从 ~6.1s 降至 ~2s;UI 表格"统计口径"列标注"近7天"。 - 监控页卡顿根因(第二次,2026-08-20):剩余卡顿来自
GetScriptStatusAsync的 WMI 逐进程查询 (每进程一次ManagementObjectSearcher,单次 ~1s,5 脚本 × 十几个 python 进程 = 5s+)。 修复:① PID 缓存(桌面端启动进程)与启动时间戳匹配提前到最优先(零 WMI);② 剩余脚本合并为 一次 WMI 批量查询(SELECT ProcessId, CommandLine FROM Win32_Process WHERE ...)+ 3s 快照缓存。
七、常见问题
Q1:为什么模块管理页显示 0 个模块? A:客户端 UpdateServerUrl 未配置(设置页填 https://u.bywl.top)。模块 dll 需在服务器 modules/ 目录。
Q2:数据同步失败? A:检查 ① DataServerUrl 是否配置正确;② 服务器 API 是否可达(浏览器访问 /data/health);③ ApiKey 是否匹配;④ 服务器 PG/Tushare 配置。
Q2.5:访问 /admin 发现表数据量一直不变? A:优先排查(最常见 3 个原因):
- TushareToken 为空 → 采集器拿不到数据,
/admin表永远 0 增长。检查服务器appsettings.json的TushareToken; - 非工作时间 → 采集只在工作日 15:35 后触发;周末/工作日白天看不变是正常的;
- 采集报错 → 看服务器日志(journalctl / nohup 输出)里的
Collect*错误信息(token 无效/积分不足/网络不通)。 另:若 PG 是宝塔装的,连接信息(库名/用户/密码)要与宝塔里实际创建的一致(见 2.8 节)。
Q3:服务器重启后数据丢失? A:不会。数据持久化在服务器 PG(futures_srv);重复采集 UPSERT 幂等,安全。
Q4:新增一个 Tushare 接口怎么加? A:专用表类接口:加 Collector 方法 + Store 方法 + 表 + 端点;长尾接口:只需在 TushareCollector.GenericApis 清单加一行(JSONB 通用表),零建表成本。
Q5:软著/授权合规? A:客户端不携带任何 Tushare/天勤 token 与采集脚本(期货侧),数据源授权集中在服务器运营方;行情数据源也已是"用户/服务器提供"模式(见 docs/行情数据源接入协议.md)。
Q6:同步数据条数有、但数值全 0/NULL(仓单/龙虎榜空白)? ⭐ A:这是 JsonElement 解析 bug(2026-08 修复):
GetRowsAsync用System.Text.Json反序列化到Dictionary<string, object?>,数字会变成JsonElement而非long/int;- 旧
ParseLong/ParseDecimal不认识JsonElement→ 返回 null → 数值字段全 0/NULL; - 修复:
ParseLong/ParseDecimal增加JsonElement分支(TryGetInt64/TryGetDecimal)。 - 表现:数据条数正常(如 fut_wsr 1027 条)但 vol/long_hld 等全 0;仓单分析显示"08-18/08-19 为 0"、持仓龙虎榜空白。
Q7:修复后重新同步,仓单好了但持仓龙虎榜仍空白? ⭐ A:这是 ON CONFLICT DO NOTHING bug(2026-08 修复):
- 仓单
fut_wsr用ON CONFLICT DO UPDATE(修复后重新同步覆盖旧 0 值); - 持仓
fut_holding_records的FutHoldingPersistenceService.SaveAsync用ON CONFLICT DO NOTHING——主键已存在的旧 NULL 行被跳过,新值写不进 → 龙虎榜仍空白; - 修复:改为
ON CONFLICT (trade_date, symbol, broker, exchange) DO UPDATE SET ...(UPSERT 全量覆盖)。 - 教训:数据同步的 UPSERT 一律用
DO UPDATE,不要用DO NOTHING(旧脏数据永远无法自愈)。
Q8:IV 快照(option_iv_daily)永远 0 条,即使期权实时采集在跑? ⭐ A:这是 trade_date() 函数 bug(2026-08 修复):
OptionIvSnapshotService.SnapshotFromRealtimeAsync用WHERE trade_date(datetime) = @d;trade_date()是 TimescaleDB schema 函数(timescaledb.trade_date()),客户端 Npgsql 连接search_path=public找不到该函数 → 每次聚合抛异常被 catch 静默 → 快照 0 条;- 修复:改用标准写法
WHERE datetime::date = @d。 - 排查:
SELECT count(*) FROM option_iv_daily为 0 且实时表有数据时,检查同步日志的IV 快照聚合失败warning。
Q9:如何验证 7 类同步是否都正常? A:见 §5.2 表格——系统监控页「立即同步」后逐项核对:持仓龙虎榜=最新交易日、仓单分析=最新日期、期货/期权 K 线=最新日期、合约参数=最近费率、主力映射=最新日期、IV 位置有数据。