# 📊 View — 加密货币图表平台

自建的实时加密货币数据采集与可视化平台，适用于 **Binance USDS-M 永续合约**。结合高性能 Rust 后端与专业图表前端，提供机构级市场数据工具。

这个项目主要是我自用的，实在没太多精力维护。我也不擅长前端，前端代码可能不太规范。欢迎大家发起 Pull Request 和 Issue！

如您所见，这并不是一个简单的一键启动的傻瓜工程，也不是面向小白的玩具项目。请原谅我没有时间也没有能力将其简单化。最简单的部署方式就是让 **Claude Opus 4.6** 研究完整个项目，然后让它来指导你启动。

如果后端部署实在太过困难，您可以暂时直接使用我的公开后端 API 端口：`api-view.cathiefish.org`，这样您只需将前端部署到 Cloudflare Pages 即可。注意该端口仅保证提供 **BTCUSDT, ETHUSDT, SOLUSDT, BNBUSDT, XRPUSDT, SUIUSDT** 的数据。请参阅下方[部署](#-部署)章节了解如何配置。

![tv-1](https://img.cathiefish.art/ns/tv-1.png)
![tv-2](https://img.cathiefish.art/ns/tv-2.png)
![tv-3](https://img.cathiefish.art/ns/tv-3.png)

## ✨ 功能

### 数据采集与存储
- [x] **实时 WebSocket 推流** — 从 Binance 合并流实时接收 1 分钟 K 线数据，支持自动重连和缺口回补
- [x] **三级历史数据同步** — 月度 ZIP → 每日 ZIP → REST API，从 [Binance 数据归档](https://data.binance.vision/) 尽可能快速回补
- [x] **TimescaleDB 时序存储** — Hypertable 优化，支持 `time_bucket` 聚合，覆盖 8 个时间周期（1m, 5m, 15m, 1h, 4h, 1D, 1W, 1M）
- [x] **代理池支持** — 最多 100 个并发代理客户端，用于高吞吐量并行下载
- [ ] **Binance 现货支持** — 现货市场数据采集，使用 `.P` 后缀区分合约与现货
- [ ] **MCP 支持** — Model Context Protocol 集成

### 图表与可视化
- [x] **专业图表引擎** — 完整的指标和画图工具支持
- [x] **自定义指标** — 净成交量（NV-C）和累积成交量差（CVD-C）
- [x] **多图表布局** — 单图、垂直分割、水平分割、1 左 + 2 右布局，分隔线可拖动
- [x] **图表保存/加载** — 通过内置 `save_load_adapter` 实现保存/加载（元数据存 localStorage，图表内容存后端 API），支持自动保存
- [x] **深色/浅色主题** — 主题切换同步到图表组件和 UI

### 自选列表与交互
- [x] **实时自选列表侧栏** — 实时价格、24 小时涨跌幅、拖拽排序
- [x] **多自定义列表** — 创建和管理多个自选列表
- [ ] **前端品种管理** — 从前端 UI 添加/移除追踪品种（目前 `TRACKED_SYMBOL` 仅通过后端 `.env` 文件控制）

### 安全与部署
- [x] **Google OAuth 保护** — 邮箱白名单访问控制，144 小时会话持久化
- [x] **Docker 就绪** — 多阶段构建，Docker Compose 共享网络
- [ ] **Google Firebase 集成** — 通过 Firebase 连接后端 API，实现托管认证与托管部署

## 📁 项目结构

```
view/
├── backend/                          # Rust 数据引擎 & API 服务
│   ├── src/
│   │   ├── main.rs                   # Axum HTTP 服务启动
│   │   ├── binance_collector.rs      # WebSocket 实时采集 + REST 同步
│   │   ├── historical_downloader.rs  # Binance 数据归档 (ZIP) 下载器
│   │   ├── database.rs              # TimescaleDB 操作与聚合
│   │   ├── scheduler.rs            # 任务调度与采集器生命周期
│   │   ├── klinechart.rs           # KlineChart REST API 处理
│   │   ├── tradingview.rs          # TradingView UDF API + WebSocket + Canvas
│   │   ├── structs.rs              # 数据类型（CandleData, Interval, WsMessage…）
│   │   ├── error.rs                # 自定义错误类型
│   │   └── lib.rs                  # 公开模块导出
│   ├── tests/
│   │   ├── connection_test.rs       # 数据库连接测试
│   │   ├── database_test.rs         # CRUD 与查询测试
│   │   ├── scheduler_test.rs        # 调度器命令与生命周期测试
│   │   ├── sync_test.rs             # 单品种同步测试
│   │   └── sync_full_history_test.rs
│   ├── examples/
│   │   ├── sync_all.rs              # 同步所有品种（标准模式）
│   │   ├── sync_all_fast.rs         # 同步所有品种（代理池并行）
│   │   └── sql.txt                  # TimescaleDB 建表 SQL
│   ├── Dockerfile
│   └── docker-compose.yml
│
├── frontend/                         # 图表库前端
│   ├── index.html                    # 主应用（图表 + 自选列表 + 布局）
│   ├── login.html                    # Google OAuth 登录页
│   ├── auth.js                       # AuthGuard — 会话管理
│   ├── auth-config.js                # OAuth 与 API 配置
│   ├── charting_library/             # 图表库资源
│   ├── datafeeds/                    # UDF 数据源适配器
│   ├── Dockerfile
│   └── docker-compose.yml
│
└── references/                       # Git 子模块
    ├── binance-rust/                 # Binance 连接器 SDK
    └── library/                      # 图表库源码
```

## 🛠️ 技术栈

| 层级 | 技术 |
|------|------|
| **后端** | Rust, Axum, sqlx, tokio, tokio-tungstenite |
| **数据库** | PostgreSQL + TimescaleDB |
| **前端** | Charting Library v29.4, Vanilla JS |
| **认证** | Google Identity Services (OAuth) |
| **部署** | Docker, Nginx, Docker Compose |

## 🚀 部署

对于第一次接触 Nginx Proxy Manager、TimescaleDB、PgAdmin、Docker 或 Cloudflare 的用户，最简单的部署方式就是让 **Claude Opus 4.6** 研究这个项目，然后让它一步步指导你部署。如果持续失败，请在本仓库提交 Issue。

### 方案一：完全自建部署

在自己的服务器上部署全套服务，使用 Docker 和 Nginx Proxy Manager 作为反向代理。

> [!NOTE]
> 如果不清楚以下任何步骤，请咨询 **Claude Opus 4.6**，它可以详细指导你完成每一步。

#### 网络架构

```
┌─────────────────── Docker 网络: cycle ──────────────────────┐
│                                                             │
│   ┌──────────────────┐    ┌──────────────┐                  │
│   │ Nginx Proxy Mgr  │    │  TimescaleDB │                  │
│   │    :80 / :443    │    │    :5432     │                  │
│   └────────┬─────────┘    └──────┬───────┘                  │
│            │                     │                          │
│            │                     │                          │
│   ┌────────▼──┐  ┌───────────┐   │                          │
│   │ Frontend  │  │ Backend   │───┘                          │
│   │ :80       │  │ :3000     │                              │
│   │ (nginx)   │  │ (axum)    │                              │
│   └───────────┘  └───────────┘                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘
         ▲
      公网域名
  view.yourdomain.com
```

所有容器**必须**在同一个 Docker 网络 `cycle` 中运行，这样才能通过容器名互相通信。只有**前端**需要通过 Nginx Proxy Manager 暴露到公网 —— 后端（端口 `3000`）由前端容器通过 Docker 内部 DNS 访问。

---

#### 第一步：创建 Docker 网络

```bash
docker network create cycle
```

---

#### 第二步：数据库部署（TimescaleDB）

为 TimescaleDB 创建 `docker-compose.yml`，加入 `cycle` 网络：

```yaml
# database/docker-compose.yml
services:
  timescaledb:
    image: timescale/timescaledb:latest-pg16
    container_name: timescaledb
    networks:
      - cycle
    environment:
      - POSTGRES_USER=quant
      - POSTGRES_PASSWORD=your_secure_password
      - POSTGRES_DB=crypto_database
    volumes:
      - timescaledb_data:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  timescaledb_data:

networks:
  cycle:
    external: true
```

```bash
docker compose up -d
```

然后使用 [backend/examples/sql.txt](backend/examples/sql.txt) 中的 SQL 初始化数据库：

```bash
docker exec -i timescaledb psql -U quant -d crypto_database < backend/examples/sql.txt
```

> [!TIP]
> 建议同时部署 **PgAdmin** 用于数据库管理。详见[这篇教程](https://n8n.cathiefish.art/n8n-instances-twitter-ai-%E5%88%86%E6%9E%90%E7%9B%91%E6%8E%A7-5b706bb444d7)。新手建议直接让 **Claude Opus 4.6** 来指导。

#### 第三步：部署后端

**1. 配置 `.env`**

```bash
cd backend
cp .env.example .env
```

编辑 `backend/.env`：

```env
RUST_LOG="INFO,binance_sdk::common::utils=off,binance_sdk::common::websocket=off"

DATABASE_URL="postgres://quant:your_secure_password@timescaledb:5432/crypto_database"

TRACKED_SYMBOL=[BTCUSDT,ETHUSDT,BNBUSDT,SOLUSDT,XRPUSDT]

# 代理设置（可选 — 留空则直连）
PROXY_HOST=dc.your-proxy-provider.com
PROXY_USERNAME=your_username
PROXY_PASSWORD=your_password
PROXY_PROTOCOL=https
PROXY_PORT_START=10000
PROXY_PORT_END=10099
```

> [!NOTE]
> `DATABASE_URL` 使用容器名 `timescaledb` 作为主机名 —— 因为两个容器都在 `cycle` 网络中。也可以使用数据库容器的绝对 IP（通过 `docker inspect timescaledb --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'` 查询）。确保数据库容器也在 `cycle` 网络中。`TRACKED_SYMBOL` 目前仅支持 **USDS-M 永续合约（Swap）**，不支持现货。

> [!WARNING]
> **强烈建议配置代理。** 后端需要从 [Binance 数据归档](https://data.binance.vision/) 下载所有追踪品种的历史数据。使用多端口代理池（如 100 个并发连接）。**不使用代理的话，同步可能需要几天时间。** 如果 `PROXY_HOST` 留空，后端将使用单一直连。

**2. 构建并运行**

```bash
docker build -t backend .
docker compose up -d
```

---

#### 第四步：部署前端

构建前端容器之前，需要先配置认证和 API 连接。

**1. 配置 Google OAuth — `auth-config.js`**

前端在 Docker 网络内部运行，通过**容器名**访问后端：

```js
// 使用后端容器名作为主机名（Docker 内部 DNS）
window.API_CONFIG = { baseUrl: 'http://backend:3000' };

const AUTH_CONFIG = {
    // 替换为你从 GCP Console 获取的 Google OAuth Client ID
    clientId: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
    onSuccess: (user) => { console.log('Auth successful:', user.email); },
    onError: (error) => { console.error('Auth error:', error); }
};
window.AUTH_CONFIG = AUTH_CONFIG;
```

查询后端容器 IP（调试用）：

```bash
$ docker inspect backend --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
172.18.0.3
```

**2. 配置邮箱白名单 — `login.html`**

将你的 Google 邮箱添加到白名单：

```js
const allowedEmails = ['your-email@gmail.com'];
```

> [!IMPORTANT]  
> Google OAuth 需要从 GCP Console 获取 Client ID。教程待补充。

**3. 构建并运行**

```bash
cd frontend
docker build -t frontend .
docker compose up -d
```

> [!WARNING]
> 前端和后端容器**必须**在同一个 Docker 网络（`cycle`）中。两个 `docker-compose.yml` 文件已通过 `networks: cycle: external: true` 配置好。

---

#### 第五步：Nginx Proxy Manager

Nginx Proxy Manager 也必须在 `cycle` 网络中运行。创建 `docker-compose.yml`：

```yaml
# npm/docker-compose.yml
services:
  nginx-proxy-manager:
    image: jc21/nginx-proxy-manager:latest
    container_name: nginx-proxy-manager
    networks:
      - cycle
    ports:
      - "80:80"
      - "443:443"
      - "81:81"
    volumes:
      - npm_data:/data
      - npm_letsencrypt:/etc/letsencrypt
    restart: unless-stopped

volumes:
  npm_data:
  npm_letsencrypt:

networks:
  cycle:
    external: true
```

```bash
docker compose up -d
```

> [!TIP]
> Nginx Proxy Manager 的详细部署和 Docker 最佳实践，请参阅[这篇教程](https://n8n.cathiefish.art/rsshub-install-f5ad036a4dd9)。新手建议直接让 **Claude Opus 4.6** 来指导。

访问管理面板 `http://你的服务器IP:81`（默认账号：`admin@example.com` / `changeme`）。

**为前端创建 Proxy Host：**

| 域名 | 转发主机名 | 转发端口 | SSL |
|------|-----------|---------|-----|
| `view.yourdomain.com` | `frontend_tv` | `80` | ✅ Let's Encrypt |

> [!TIP]
> "转发主机名"使用的是**容器名**（不是 IP），因为所有容器共享 `cycle` 网络。后端**不需要**公网代理 —— 前端通过 Docker 内部 DNS（`http://backend:3000`）访问。

部署完成后，即可通过 `https://view.yourdomain.com` 访问。

---

### 方案二：后端自建 + 前端部署到 Cloudflare Pages

在**方案一**的基础上，额外将后端 API 暴露到公网，并将前端部署到 Cloudflare Pages，而不是自建托管。

> [!NOTE]
> 如果不清楚以下步骤，请咨询 **Claude Opus 4.6** —— 这是很简单的操作。

> [!CAUTION]
> Cloudflare Pages 项目名和自定义域名中**不要**包含 "tradingview"。TradingView 会主动维权，你的部署**会被下架**。使用中性名称如 `view`、`chart`、`crypto-dash`。

#### 第一步：后端 + 数据库

完成**方案一的第一至三步**（创建 `cycle` 网络、部署 TimescaleDB、部署后端）。

#### 第二步：通过 Nginx Proxy Manager 暴露后端 API

由于前端将从 Cloudflare 提供服务（在 Docker 网络外部），后端 API 必须可以从公网访问。在 Nginx Proxy Manager 中添加一个 **Proxy Host**：

| 域名 | 转发主机名 | 转发端口 | SSL |
|------|-----------|---------|-----|
| `api.yourdomain.com` | `backend` | `3000` | ✅ Let's Encrypt |

> [!IMPORTANT]
> 为此 Proxy Host 启用 **Websockets Support** —— 实时 K 线推送需要 WebSocket。

#### 第三步：配置前端

部署到 Cloudflare 之前，更新配置文件：

**`auth-config.js`** —— 将 `baseUrl` 指向第二步的**公网后端域名**：

```js
window.API_CONFIG = { baseUrl: 'https://api.yourdomain.com' };

const AUTH_CONFIG = {
    clientId: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
    // ...
};
```

**`login.html`** —— 添加你的邮箱到白名单：

```js
const allowedEmails = ['your-email@gmail.com'];
```

#### 第四步：部署到 Cloudflare Pages

1. Fork 或 Clone 本仓库到你自己的 GitHub 账号
2. 进入 [Cloudflare 控制台](https://dash.cloudflare.com/) → **Workers & Pages** → **创建**
3. 选择 **Pages** → **连接到 Git**
4. 授权 Cloudflare 访问你的 GitHub 账号，选择仓库
5. 配置构建设置：

| 设置 | 值 |
|------|---|
| 生产分支 | `main` |
| 构建命令 | *（留空）* |
| 构建输出目录 | `frontend` |

6. 点击 **保存并部署**

Cloudflare 会分配一个 `*.pages.dev` 域名。你可以在 **Pages** → **自定义域** 中添加自定义域名。

---

## 📜 许可证

MIT


Generated By Claude Opus 4.6
