数据源配置指南
这是 AI 量化交易系统系列教程的第一篇。数据源是整个系统的"眼睛和耳朵"——所有后续的 AI 分析和交易决策都建立在这些数据之上。
本篇基于开源项目 data-feeder(GitHub / Gitee),讲清楚一件事:如何通过 TradingView WebSocket 直连,在 K 线收盘的瞬间采集 13 个时间级别的指标趋势线数据,零延迟回写到服务端。
获取项目
项目已同步发布到 GitHub 和 Gitee,按网络情况任选其一。以下按截图步骤引导你完成下载。
方式一:Gitee(国内推荐,速度快)
第 1 步:打开 Gitee 仓库页面,点击橙色的「克隆/下载」按钮:

第 2 步:在弹出的对话框中,你可以选择两种方式:

- 下载 ZIP(图中红箭头):直接下载压缩包,解压即可使用,适合不熟悉 Git 的用户
- 复制 git clone 命令:在终端中粘贴执行,适合需要后续
git pull更新的用户
# 终端执行(Gitee)
git clone https://gitee.com/xibusuper/data-feeder.git
cd data-feeder
方式二:GitHub(海外网络)
打开 GitHub 仓库页面,点击绿色的「Code」按钮,在下拉菜单底部点击「Download ZIP」:

也可以复制 HTTPS 地址用终端克隆:
# 终端执行(GitHub)
git clone https://github.com/xibusuper/data-feeder.git
cd data-feeder
后续更新
如果通过 git clone 方式获取的项目,后续更新只需一条命令:
git pull
下载 ZIP 的用户需要重新下载新版本覆盖旧文件。
项目结构
data_client/
├── clients/
│ └── tv-indicator.js # TradingView 指标采集(多周期共振 v2.0)— 命令行入口
├── lib/
│ └── OpenApiClient.js # 开放平台 API 封装类(fetchTvData/writeTvDataList/reportStatus/fetchLastDataList/fetchDataList)
├── package.json
├── .env.example # 配置文件模板(复制为 .env 使用)
├── .gitignore
├── Dockerfile # Docker 镜像构建(多阶段,非 root 运行)
├── docker-compose.yml # Docker Compose 一键编排
├── .dockerignore
└── README.md
整个项目核心源码文件:clients/tv-indicator.js 是主程序,lib/OpenApiClient.js 是 API 封装类。依赖也只有两个:@mathieuc/tradingview(TradingView API 库,来自 GitHub)和 dotenv(配置读取)。
环境准备
安装 Git
如果本机尚未安装 Git,请前往 git-scm.com 下载对应操作系统的安装包。
安装完成后验证:
git --version # 确认 git 可用
安装 Node.js >= 18
项目使用 Node.js 内置的 fetch API(18.0.0 起内置),不再需要 axios 等第三方 HTTP 库。前往 nodejs.org 下载 LTS 版本,安装后验证:
node --version # 应输出 v18.x.x 或更高
Windows Server 2012 用户
Server 2012 不支持新版 Node.js 安装包,请下载 Node.js 18 专用版本:node-v18.20.8-x64.msi
后端服务
采集客户端需要连接后端服务(默认地址 https://traderai.vip)。后端需提前创建好以下数据:
| 数据表 | 说明 | 关键字段 |
|---|---|---|
bot_tv_user | TV 用户配置 | session_id、session_sign、indicator_id(获取方式见 TV 指标字段) |
bot_tv_data | 数据源订阅 | 关联 TV 用户或交易所账号,配置交易所/币种/周期/K线类型 |
bot_api_key | 访问密钥 | 用于开放接口 Bearer Token 鉴权(获取方式见 PC 端获取 / 手机端获取) |
配置
复制配置模板并填写实际值:
cp .env.example .env
编辑 .env:
# 开放接口地址(后端服务地址)
# 默认写死为正式服务器:https://traderai.vip
# Docker 运行时若后端在宿主机,可填 http://host.docker.internal:7000 覆盖
API_URL=https://traderai.vip
# 访问密钥(bot_api_key 表中的 api_key)
# 也可通过命令行 -k 传入,命令行优先级更高
API_KEY=
# 数据源ID(bot_tv_data 表的自增 id)
# 该 ID 对应的数据源可关联 TV 用户(target_type=1)或交易所账号(target_type=2)
# 也可通过命令行 -i 传入,命令行优先级更高
TV_DATA_ID=
# 价格保留小数位数(默认 2)
PRICE_PRECISION=2
API_KEY 和 TV_DATA_ID 是必填项。两个都没有的情况下程序启动即报错退出:
[错误] 缺少 api_key,请通过 -k/--api-key 传入或在 .env 中配置 API_KEY
[错误] 缺少数据源 id,请通过 -i/--tv-data-id 传入或在 .env 中配置 TV_DATA_ID
命令行参数优先级高于 .env,方便临时切换数据源而不修改配置文件。
参数速查
| 来源 | 参数 | 说明 | 必填 | 默认值 |
|---|---|---|---|---|
.env | API_URL | 开放接口地址 | 否 | https://traderai.vip |
.env / -k | API_KEY | 访问密钥 | 是 | — |
.env / -i | TV_DATA_ID | 数据源 ID | 是 | — |
.env / -p | PRICE_PRECISION | 价格小数位数 | 否 | 2 |
| — | -d | 调试模式 | 否 | 关闭 |
运行方式一:本地运行
安装依赖
进入项目目录后安装依赖:
cd data-feeder
npm install
启动采集
# 方式一:全部从 .env 读取
node clients/tv-indicator.js
# 方式二:命令行传参(覆盖 .env)
node clients/tv-indicator.js -k 你的API_Key -i 数据源ID
# 开启调试模式
node clients/tv-indicator.js -k 你的API_Key -i 数据源ID -d
参数说明
| 参数 | 说明 | 获取方式 |
|---|---|---|
-k | API Key(访问密钥) | 参考 PC 端获取 或 手机端获取 教程,点击「复制」按钮获取完整 Key |
-i | 数据源 ID | 聚合 SaaS 系统 → 数据源订阅页面中的 ID 列数字(见下方截图) |
-d | 调试模式 | 无需传值,加上即开启 |
⚠️ 数据源 ID 必须是自己创建的
-i 后面的数字对应你在聚合 SaaS 系统中自己创建的数据源订阅记录的 ID。不能使用别人的数据源 ID,否则会因权限不足导致采集失败。
数据源 ID 在哪里找?
登录聚合 SaaS 系统后台,在左侧导航栏点击「业务管理」→「数据源订阅」,列表中第一列的 ID 就是对应的数字:

启动后你会看到类似输出:

运行成功的标志:
- 「客户端连接成功!」 — WebSocket 已连接 TradingView
- 「指标准备就绪!」 — 指标加载成功,开始接收数据
- 「等待5分钟K线收盘...」 — 正常等待中,K 线收盘时会自动采集
- 趋势线数据表格 — 13 个时间级别的趋势线值、与价格差值、多空方向
- 「[写入成功]」 — 数据已成功写入服务端数据库
数据上报成功后,回到聚合 SaaS 系统后台,点击数据源订阅页面中的**「运行记录」**,即可查看上报的指标数据。
查看帮助
node clients/tv-indicator.js -h
退出
按 Ctrl + C,客户端清理 WebSocket 连接后退出。
运行方式二:Docker 运行
生产环境推荐使用 Docker,自动重启、隔离环境、方便部署。
Docker Compose 一键启动
# 1. 配置好 .env(填写 API_KEY / TV_DATA_ID)
# 2. 构建并后台启动
docker compose up -d --build
# 3. 查看实时日志
docker compose logs -f
# 4. 停止
docker compose down
# 5. 更新代码后重新构建
git pull && docker compose up -d --build
docker 命令直接运行
docker build -t tradingbot-data-client:latest .
docker run -d \
--name tradingbot-data-client \
--restart unless-stopped \
--add-host host.docker.internal:host-gateway \
-e API_URL=https://traderai.vip \
-e API_KEY=your_api_key \
-e TV_DATA_ID=1 \
-e PRICE_PRECISION=2 \
tradingbot-data-client:latest
Docker 网络说明
| 后端位置 | API_URL 取值 |
|---|---|
| 正式服务器(默认) | https://traderai.vip |
| 宿主机本地 | http://host.docker.internal:7000(Linux 需加 --add-host) |
| 同一 Docker 网络 | http://<后端服务名>:7000 |
| 公网/远程 | 直接填公网地址 |
核心原理:毫秒级指标采集
这是整个项目最有创新性的部分。传统的数据采集方式是定时轮询——每隔 1 分钟发一次 HTTP 请求拉数据。这种方式有两个问题:延迟高(最慢差一个轮询周期),且在 K 线还没收盘时拿到的是不完整的中间值。
data-feeder 采用了完全不同的思路:WebSocket 直连 + 时间戳变化检测,在 K 线收盘的瞬间触发采集,延迟控制在毫秒级。
第一步:WebSocket 直连 TradingView
程序启动后,通过 OpenApiClient 从服务端获取订阅配置(交易所、币种、周期、TV 凭证),然后建立 WebSocket 连接:
// 从服务端获取配置
const { tv_data, tv_user } = await openApi.fetchTvData(tvDataId);
// 用 TV 凭证创建 WebSocket 客户端
const client = new TradingView.Client({
token: tv_user.tv_session_id,
signature: tv_user.tv_session_sign,
});
// 创建图表会话,设置交易对和周期
const chart = new client.Session.Chart();
chart.setMarket(`${exchange}:${symbol}`, {
timeframe: String(timeframe),
range: 20,
type: chartType === 'heikin' ? 'HeikinAshi' : undefined,
});
WebSocket 连接建立后,TradingView 会持续推送实时数据,不需要客户端反复请求。
第二步:加载指标并激活隐藏时间级别
获取指标实例后,程序会打开默认关闭的时间级别开关。这一步是关键——TradingView 的某些指标默认只显示部分时间级别,需要手动激活:
const indicator = await TradingView.getIndicator(indicatorScriptId, 'last', sessionId);
// 打开默认关闭的时间级别
indicator.setOption('in_55', true); // h8 (8小时)
indicator.setOption('in_63', true); // h12 (12小时)
indicator.setOption('in_71', true); // d1 (1日)
// 创建 Study 实例挂载到图表
const study = new chart.Study(indicator);
参数名 in_55、in_63、in_71 对应指标内部定义的输入参数 key。每个时间级别的第一组参数中,type="bool" 的即为显示开关。用 -d 调试模式可以看到所有可用参数。
第三步:K 线收盘检测
这是毫秒级采集的核心。程序通过比较相邻两次 onUpdate 回调中的 K 线时间戳来判断收盘:
let previousTime = null;
study.onUpdate(() => {
if (!study.periods || study.periods.length === 0) return;
const currentPeriod = study.periods[0];
const currentTime = currentPeriod.$time;
// 时间戳变化 = 新K线开始 = 上一根已收盘
if (previousTime !== null && previousTime !== currentTime) {
// periods[1] 就是刚收盘那根K线的最终数据
const closedPeriod = study.periods[1];
if (closedPeriod) {
handleKlineClose(closedPeriod, currentPrice);
}
}
previousTime = currentTime;
});
study.periods 数组按时间倒序排列:[0] 是当前正在形成的 K 线,[1] 是上一根已收盘的 K 线。当 periods[0].$time 发生变化时,意味着新 K 线开始了——上一根已经收盘,periods[1] 中的数据就是最终值。
从 K 线收盘到数据采集完成,整个过程在同一个 WebSocket 回调中同步执行,没有 HTTP 轮询的等待间隔,延迟控制在毫秒级。
第四步:提取 13 个时间级别的趋势线数据
一根 K 线收盘后,程序从 period 对象中提取 13 个时间级别的趋势线数值:
const TIMEFRAMES = ['cur', 'm10', 'm15', 'm30', 'h1', 'h2', 'h4', 'h8', 'h12', 'd1', 'd2', 'd3', 'd5'];
function extractTrendData(period, precision, price) {
const NO_DATA = 1e+100; // TradingView 无趋势哨兵值
const trendData = {};
TIMEFRAMES.forEach((tf) => {
const val = period[tf];
if (val !== undefined && val !== null && val !== NO_DATA) {
const formattedVal = Number(val.toFixed(precision));
if (price > 0) {
// value < price → 做多方向 → _duo
// value >= price → 做空方向 → _kong
const suffix = val < price ? '_duo' : '_kong';
trendData[`${tf}${suffix}`] = formattedVal;
} else {
trendData[tf] = formattedVal;
}
}
});
return trendData;
}
这里有两个细节值得注意:
哨兵值过滤:TradingView 在没有趋势数据时返回 1e+100(一个天文数字),而不是 null 或 undefined。程序会过滤掉这个值,避免把 1e+100 当成真实趋势线价格。
多空方向标记:趋势线值低于当前价格意味着价格在趋势线上方(多头格局),标记为 _duo;反之标记为 _kong。这样 AI 拿到数据后一眼就能看出每个时间级别的多空方向,不需要再做比较计算。
采集的时间级别
| 字段 | 含义 | 字段 | 含义 |
|---|---|---|---|
cur | 当前周期 | h8 | 8 小时 |
m10 | 10 分钟 | h12 | 12 小时 |
m15 | 15 分钟 | d1 | 1 天 |
m30 | 30 分钟 | d2 | 2 天 |
h1 | 1 小时 | d3 | 3 天 |
h2 | 2 小时 | d5 | 5 天 |
h4 | 4 小时 |
变量名按趋势方向加后缀:_duo(做多)、_kong(做空)。例如 m15_duo: 100.5 表示 15 分钟级别趋势线值 100.5,低于当前价格,多头格局。
第五步:零延迟回写服务端
K 线收盘后,趋势线数据立即通过 OpenApiClient 写入服务端数据库:
async function handleKlineClose(closedPeriod, price) {
const klineTimeMs = closedPeriod.$time * 1000; // TV 返回秒级,DB 存毫秒
const trendData = extractTrendData(closedPeriod, precision, price);
const payload = {
tv_data_id: tvDataId,
data: JSON.stringify(trendData), // 如 {"m15_duo":100.5,"h1_kong":101.2,...}
kline_time: klineTimeMs,
price: price.toFixed(precision),
};
await openApi.writeTvDataList(payload);
}
写入失败时只打印错误日志,不影响采集进程继续运行。
OpenApiClient 类
开放平台的五个接口已封装到 lib/OpenApiClient.js,可以在自己的脚本中独立复用:
const { OpenApiClient } = require('./lib/OpenApiClient');
const client = new OpenApiClient({
apiUrl: 'https://traderai.vip',
apiKey: 'your_api_key',
});
// 1. 获取数据源订阅配置 + TV 用户凭证
const { tv_data, tv_user } = await client.fetchTvData(1);
// 2. 写入 K 线收盘指标数据
await client.writeTvDataList({
tv_data_id: 1,
data: JSON.stringify({ m15_duo: 100.5, h1_kong: 101.2 }),
kline_time: Date.now(),
price: '100.80',
});
// 3. 上报运行状态(0=停止, 1=运行中, 2=异常)
await client.reportStatus(1, 1, '');
// 4. 获取最后一条数据记录(无数据时返回空对象)
const last = await client.fetchLastDataList(1);
// 5. 获取最近若干条记录(默认 10 条,最大 100 条,按 id 降序)
const list = await client.fetchDataList(1, 10);
五个接口均需在请求头携带 Authorization: Bearer <api_key>,类内部已自动处理。
| 接口 | 方法 | 说明 | 封装方法 |
|---|---|---|---|
/api/open/tvData/:id | GET | 获取订阅配置 + TV 凭证 | fetchTvData() |
/api/open/tvDataList/write | POST | 写入 K 线收盘数据 | writeTvDataList() |
/api/open/tvData/:id/status | POST | 上报运行状态 | reportStatus() |
/api/open/tvDataList/lastDataList | GET | 获取最后一条记录 | fetchLastDataList() |
/api/open/tvDataList/list | GET | 获取最近若干条记录(limit 1~100) | fetchDataList() |
调试模式
加 -d 或 --debug 参数启动时,程序会打印指标加载的原始入参和出参,包括:
getIndicator的 scriptId、sessionId 入参- 指标的
shortDescription、description、pineId - 所有输入参数定义(
inputs)和当前值(options) - 所有输出定义(
plots) - 完整指标对象的 JSON 序列化
node clients/tv-indicator.js -k abc123def456 -i 1 -d
调试输出示例:
--- [调试] getIndicator 入参 ---
scriptId : USER;YourIndicator
version : last
sessionId: eyJ...
--- [调试] getIndicator 出参(指标元信息)---
shortDescription: MyTrendIndicator
description : Multi-timeframe trend lines
=== 指标输入参数定义 (inputs) ===
[in_55]: {"name":"显示","type":"bool",...} # h8 开关
[in_63]: {"name":"显示","type":"bool",...} # h12 开关
[in_71]: {"name":"显示","type":"bool",...} # d1 开关
=== 指标输出定义 (plots) ===
[0] id=0 type=Line target=cur
[1] id=1 type=Line target=m10
...
--- [调试] 结束 ---
如果需要修改指标参数(如周期长度、数据源、乘数),在调试输出中找到对应的 in_xx key,然后在代码中添加:
indicator.setOption('in_55', true); // 已有:激活 h8
// indicator.setOption('Length', 14); // 示例:修改周期长度
// indicator.setOption('Multiplier', 2.0); // 示例:修改乘数
容错机制
采集客户端需要 7×24 小时稳定运行,内置了多层容错:
| 异常场景 | 处理方式 |
|---|---|
| 获取配置失败 | 指数退避重试(5s → 10s → 20s → … 最长 60s) |
| WebSocket 断连 | 5 秒后自动重新连接 |
| 数据写入失败 | 仅打印错误日志,不影响采集进程 |
| 30 秒未获取到数据 | 自动重新初始化连接 |
| Docker 异常退出 | restart: unless-stopped 自动拉起 |
每次异常发生时,程序都会通过 reportStatus 接口上报状态(2=异常)和错误信息到服务端,方便运维监控。
数据源订阅配置说明
后端管理后台中创建数据源订阅时,需要理解 target_type 字段的含义:
| target_type | 关联目标 | 凭证来源 |
|---|---|---|
1 | TV 用户(bot_tv_user) | TV 用户的 session_id、session_sign、indicator_id |
2 | 交易所账号 | 交易所账号关联的 TV 凭证 |
两种类型最终都提供 TV 凭证给采集客户端使用。区别在于凭证的管理入口不同——target_type=1 直接在 TV 用户管理中维护,target_type=2 绑定在交易所账号上。
K 线类型
| chart_type 值 | 说明 |
|---|---|
regular | 普通K线(默认) |
heikin | 平均K线(Heikin Ashi) |
平均K线会平滑价格波动,适合趋势分析。程序通过 chart.setMarket 的 type 参数切换。
测试与验证
验证清单
逐项检查,全部通过后再进入下一篇教程:
| 检查项 | 验证方法 | 预期结果 |
|---|---|---|
| Node.js 版本 | node --version | v18 或更高 |
| git 可用 | git --version | 输出版本号 |
| 依赖安装 | npm install | 无报错 |
| .env 配置 | cat .env | API_KEY 和 TV_DATA_ID 已填写 |
| API_KEY 校验 | node clients/tv-indicator.js(不传 -k) | 无"缺少 api_key"报错 |
| TV_DATA_ID 校验 | node clients/tv-indicator.js(不传 -i) | 无"缺少数据源 id"报错 |
| 服务端连通 | node clients/tv-indicator.js | 显示"客户端连接成功" |
| 指标加载 | node clients/tv-indicator.js | 显示"指标准备就绪" |
| K 线收盘采集 | 等待一根 K 线收盘 | 控制台输出趋势线数据 |
| 数据写入 | 查看服务端数据库 | bot_tv_data_list 表有新记录 |
| 调试模式 | node clients/tv-indicator.js -d | 打印指标原始入参/出参 |
| Docker 运行 | docker compose up -d | 容器正常运行 |
快速验证流程
# 1. 克隆项目
git clone https://gitee.com/xibusuper/data-feeder.git
cd data-feeder
# 2. 安装依赖
npm install
# 3. 配置
cp .env.example .env
# 编辑 .env,填入 API_KEY 和 TV_DATA_ID
# 4. 调试模式启动(查看指标加载详情)
node clients/tv-indicator.js -d
# 5. 确认指标加载成功后,Ctrl+C 退出,正常模式启动
node clients/tv-indicator.js
# 6. 等待一根 K 线收盘,观察控制台输出
常见问题
提示"缺少 api_key"
在 .env 中填写 API_KEY=你的密钥,或者运行时加 -k 你的密钥。两个都没有时程序直接退出。
提示"缺少数据源 id"
在 .env 中填写 TV_DATA_ID=数字ID,或者运行时加 -i 数字ID。这个 ID 是后端 bot_tv_data 表的自增主键,需要在管理后台先创建数据源订阅。
WebSocket 连接失败
检查后端返回的 TV 凭证是否有效。session_id 和 session_sign 是 TradingView 的登录 Cookie,有有效期,过期后需要在管理后台更新。获取方式:浏览器打开 tradingview.com 登录 → F12 → Application → Cookies → 复制 sessionid 和 signature。
指标加载失败
确认 indicator_id(指标脚本 ID)正确。私有 Pine Script 指标的 ID 格式通常是 USER;xxxxx,需要指标作者分享给你并且你的 TV 账号有访问权限。用 -d 调试模式查看 getIndicator 的原始入参和报错信息。
某些时间级别没有数据
检查是否已激活隐藏的时间级别开关。代码中已默认激活 in_55(h8)、in_63(h12)、in_71(d1)。如果需要其他级别,用 -d 调试模式查看指标的所有 inputs 定义,找到对应级别的 in_xx key 并在代码中添加 indicator.setOption('in_xx', true)。
TradingView 返回 1e+100
这是 TradingView 表示"无趋势数据"的哨兵值,不是真实价格。程序已自动过滤,不会写入数据库。如果所有级别都返回 1e+100,说明指标还没有计算出趋势线,通常是因为 K 线历史数据不足。
Docker 容器无法连接后端
如果后端运行在宿主机上(非容器),API_URL 需要填 http://host.docker.internal:7000,并且在 Linux 上需要加 --add-host host.docker.internal:host-gateway。Docker Compose 方式已在 docker-compose.yml 中配置好。
扩展:接入其他数据源
data-feeder 专注于 TradingView 指标采集。如果你还需要接入其他数据源(行情 API、新闻数据、自定义数据),可以使用 OpenApiClient 类的 writeTvDataList 方法,遵循相同的数据写入格式:
const { OpenApiClient } = require('./lib/OpenApiClient');
const client = new OpenApiClient({
apiUrl: process.env.API_URL,
apiKey: process.env.API_KEY,
});
// 写入自定义数据(如新闻情绪分析结果)
await client.writeTvDataList({
tv_data_id: customDataSourceId,
data: JSON.stringify({
sentiment_score: 0.72,
news_count: 15,
top_keywords: ['BTC', 'ETF', '降息'],
}),
kline_time: Date.now(),
price: '0.72',
});
只要数据写入 bot_tv_data_list 表,聚合平台就能统一读取并交给大模型分析。
写在最后
大家可以参考本项目的代码进行修改,适配自己的指标和交易策略。如果实在不知道怎么改,推荐下载字节跳动的专业 AI 编程工具 Trae,用 AI 辅助编程,分分钟就能搞定。
后续我还会分享更多实战例子,全部开源供大家参考。
