Author SHA1 Message Date
crypto_predictor d34b67f522 Add CLAUDE.md guide and preprocessing comparison script 2026-02-10 23:26:44 +08:00
crypto_predictorandClaude Opus 4.6 84288a687a feat: auto_trade 每小时预测结果写入预测表并回填上一小时实际结果
- 导入 PredictionModel,预测完成后保存到 model_predictions 表
- 当前小时预测 actual_result/is_correct/close_price 留空(未收盘)
- 新增 _update_previous_prediction:每次整点回填上一小时实际涨跌(收盘价 vs 开盘价)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 23:19:57 +08:00
crypto_predictorandClaude Opus 4.6 e78836bf42 fix: 修复 auto_trade 推理预处理逻辑,新增 create_labels 版对比测试脚本
auto_trade_polymarket.py: 新增 preprocess_for_inference 方法,推理时不调用
create_labels(避免 df[:-1] 砍掉输入K线),与 test 脚本逻辑一致。
test_dlinear_jan_2026_create_labels.py: 使用 create_labels 方式的 DLinear 测试
脚本,用于对比两种预处理方式的准确率差异。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 23:05:17 +08:00
crypto_predictor 7990312878 feat: 添加 ONNX 导出功能及 DLinear 模型导出 2026-02-08 15:05:09 +08:00
crypto_predictor 5ca1779d7d fix: 精简 requirements.txt,移除未使用的依赖
移除的依赖:
- gluonts: 项目中未使用
- pytorch-lightning: 非必需
- torchmetrics: 非必需
- onnx/onnxruntime: 非核心功能
- sqlalchemy: 使用 pymysql 直连
- jupyter/matplotlib/seaborn/pytest: 开发工具,非运行必需

保留的核心依赖:
- pandas, numpy, scikit-learn: 数据处理
- granite-tsfm: TTM-R2 模型
- pandas-ta: 技术指标
- fastapi, uvicorn: API 服务
- httpx, python-binance: 数据获取
- schedule: 任务调度
- pymysql, cryptography: 数据库

注意:PyTorch 需要单独安装(Windows 用 CPU 版本)
2026-02-07 22:28:46 +08:00
crypto_predictor 9320b79730 refactor: 重新整理 requirements.txt
主要更改:
- 修正 TTM 模型包名: tsfm_public -> granite-tsfm
- 添加缺失依赖: httpx, schedule, pytorch-lightning, torchmetrics
- 移除过时依赖: darts, u8darts
- 按功能分类组织依赖
- 添加 Windows/macOS 安装说明注释
- 更新安装脚本和文档中的包名
2026-02-07 22:26:07 +08:00
crypto_predictor 06800ddb43 fix: 修正 tsfm-public 包名,添加一键安装脚本
- 添加 install_windows.bat 一键安装脚本
- 修正文档中 tsfm_public -> tsfm-public (PyPI 包名用连字符)
- 添加 GitHub 备用安装方式
2026-02-07 22:02:56 +08:00
crypto_predictor 340c11d4ab docs: 添加 Windows 环境安装指南
包含完整的安装流程:
- Miniconda 安装
- Conda 环境创建
- PyTorch CPU 版本安装
- 项目依赖安装
- 数据库配置
- 环境变量配置
- 运行和验证步骤
- 常见问题解答
2026-02-07 21:55:56 +08:00
crypto_predictor d0696a42ba feat: 添加 Windows 启动脚本
- start_system.bat: CMD 批处理脚本,双击即可运行
- start_system.ps1: PowerShell 脚本,支持进程监控和优雅退出

两个脚本都会:
1. 激活 crypto_pred_312 conda 环境
2. 启动 API 服务 (端口 8000)
3. 启动自动交易程序
2026-02-07 21:54:46 +08:00
crypto_predictor 233f0c5d10 feat: 添加 Windows 兼容性支持,优化设备选择逻辑
- start_system.py: 跨平台进程管理
  - Windows 使用 CREATE_NEW_PROCESS_GROUP 和 terminate()
  - Unix 使用 setsid 和 killpg
  - 自动检测并启用 Windows ANSI 颜色支持

- 设备选择逻辑优化:
  - macOS: 优先使用 MPS,回退到 CPU
  - Windows/Linux: 强制使用 CPU(无 CUDA 依赖)

- 影响文件:
  - api/prediction_service.py
  - auto_trade_polymarket.py
  - training/config.py
2026-02-07 21:48:06 +08:00
crypto_predictor 8cdfb2a8e2 fix: 统一时区处理,准确率统计使用CST时间分组
- 增强 convert_to_cst() 函数,新增 source_tz 参数支持 UTC/ET/CST 输入
- 修改 get_monthly_stats() 使用 CONVERT_TZ 按 CST 月份统计
- 修改 get_daily_stats() 使用 CONVERT_TZ 按 CST 日期分组
- 修改 get_hourly_details() 按 CST 日期筛选和小时排序
- 交易记录使用 source_tz='CST' 避免重复转换
- 前端表头添加'(北京)'时区标注
2026-02-07 18:14:37 +08:00
crypto_predictor 6182630203 docs: 更新 AGENTS.md 开发指南,反映最新的架构变更 2026-02-07 16:58:44 +08:00
18 changed files with 1644 additions and 187 deletions

No files matched your search

+6 -1
View File
@@ -12,7 +12,12 @@
"Bash(git push:*)",
"Bash(conda activate:*)",
"WebSearch",
"Bash(pip install:*)"
"Bash(pip install:*)",
"Bash(git status:*)",
"Bash(git -C /Users/xxz/PycharmProjects/crypto_predictor check-ignore -v test_dlinear_jan_2026_create_labels.py)",
"Bash(git -C /Users/xxz/PycharmProjects/crypto_predictor status)",
"Bash(git -C /Users/xxz/PycharmProjects/crypto_predictor diff)",
"Bash(git -C /Users/xxz/PycharmProjects/crypto_predictor log --oneline -5)"
]
}
}
+43 -81
View File
@@ -8,104 +8,66 @@
* **语言要求**:所有回答必须使用**中文**。
* **称呼要求**:每次回答必须加上**“主人”**。
* **最小改动 (Minimal Change)**:只修改完成任务所必须的代码。**禁止**为了“最佳实践”或“代码美观”而进行未被请求的重构。
* **环境约束**:本项目严格使用 **Conda** 环境。禁止使用 venv/poetry/pipenv。
* **复用优先**:在编写新功能前,必须检查 `data/`, `models/`, `utils/` 下是否已有类似实现。
* **环境约束**:本项目严格使用 **Conda** 环境 (`crypto_pred_312`)。禁止使用 venv/poetry/pipenv。
* **复用优先**:在编写新功能前,必须检查 `data/`, `models/`, `api/` 下是否已有类似实现。
## 2. 环境与构建 (Environment & Build)
本项目依赖管理基于 `requirements.txt`, conda环境名称**crypto_pred_312**
* **Python 环境**: `conda activate crypto_pred_312`
* **依赖安装**: `pip install -r requirements.txt`
* **数据库**:
* **仅支持 MySQL**。禁止引入 SQLite 代码。
* 配置:通过 `.env` 文件配置 `MYSQL_HOST`, `MYSQL_USER` 等参数。
* 连接管理:使用 `data/database.py` 中的 `DatabaseManager` 或 `api/models.py` 中的 `get_db_connection`。
* **安装依赖**:
```bash
pip install -r requirements.txt
```
## 3. 项目结构说明 (Project Structure)
## 3. 测试与质量保证 (Testing & QA)
* **`api/`**: 核心服务层
* `main.py`: FastAPI 主应用入口。
* `models.py`: 数据库模型与操作(MySQL),包含时区转换逻辑。
* `prediction_service.py`: 预测业务逻辑。
* `static/`: Vue 3 前端静态资源(Dashboard)。
* **`data/`**: 数据层
* `database.py`: 市场数据 (OHLCV) 数据库管理。
* `trading_db.py`: 交易历史数据库管理。
* **`models/`**: 机器学习模型定义 (DLinear, TTM-R2 等) 及训练产物。
* **`scripts/`**: 辅助工具脚本。
* **根目录**:
* `start_system.py`: **系统统一启动脚本**(同时启动 API 和自动交易)。
* `auto_trade_polymarket.py`: 自动交易主程序。
使用 `pytest` 作为测试框架。在提交修改前,必须确保相关测试通过。
## 4. 开发规范 (Development Standards)
* **运行所有测试**:
```bash
pytest
```
### 4.1 时区处理 (Timezone)
* **存储**: 数据库中时间戳通常为 **UTC** 或 **ET (美东时间)**。
* **展示**: 前端及日志输出必须转换为 **CST (北京时间)**。
* **工具**: 使用 `api.models.convert_to_cst` 进行转换。
* **运行单个测试文件 (常用)**:
```bash
pytest tests/test_example.py
```
### 4.2 代码风格
* **注释**: 必须使用**中文**解释“为什么这样做”。
* **命名**: 变量/函数 `snake_case`,类名 `PascalCase`。
* **类型提示**: 推荐在关键接口使用 Type Hints。
* **运行单个测试用例 (推荐)**:
用于专注调试某一个功能:
```bash
pytest tests/test_example.py::test_specific_function
```
### 4.3 数据库操作
* **禁止**使用 SQLite。
* 使用 `pymysql` 或 `sqlalchemy` 进行操作。
* 确保所有数据库连接都使用上下文管理器 (`with` 语句) 以自动关闭。
* **显示打印输出 (Debug模式)**:
```bash
pytest -s tests/test_example.py
```
## 5. 测试与验证 (Testing & QA)
* **代码格式化与检查**:
```bash
black . # 格式化代码
flake8 . # 代码风格检查
```
* **运行测试**: `pytest`
* **代码检查**: `flake8 .`
* **本地验证**:
* 修改 API 或前端后,运行 `./start_system.py` 并访问 `http://localhost:8000` 验证。
* 修改模型后,运行 `python ensemble_predict.py` 验证预测逻辑。
## 4. 代码风格规范 (Code Style)
* **注释 (Comments)**:
* **必须使用中文**编写所有注释和文档字符串 (Docstrings)。
* Docstring 风格推荐 Google Style 或简单的多行注释。
* 注释应解释“为什么这样做” (Why),而不是“做了什么” (What)。
* **命名约定 (Naming)**:
* 变量/函数: `snake_case` (如 `add_technical_features`)
* 类名: `PascalCase` (如 `BinanceDataFetcher`)
* 常量: `UPPER_CASE` (如 `MAX_RETRIES`)
* 私有成员: `_snake_case` (前缀下划线)
* **导入 (Imports)**:
* 顺序:标准库 -> 第三方库 -> 本地模块。
* 尽量使用绝对导入。
* 注意:项目中部分脚本使用了 `sys.path.insert` hack,修改时需保持兼容,不要随意删除。
* **类型提示 (Type Hints)**:
* 推荐在函数签名中添加类型提示,但不强制要求全覆盖。
* 示例: `def process_data(df: pd.DataFrame) -> np.ndarray:`
* **错误处理 (Error Handling)**:
* 使用具体的异常类型 (如 `ValueError`, `FileNotFoundError`),避免裸 `except:`。
* 在 API 调用或网络请求处必须添加重试或错误捕获逻辑。
## 5. 项目结构说明 (Project Structure)
在修改代码前,请参考以下结构定位文件:
* **`api/`**: FastAPI 接口服务代码。
* **`data/`**: 数据获取 (BinanceFetcher等) 和预处理逻辑。
* **`models/`**: 模型定义 (N-BEATS, DLinear, CatBoost 等) 和 训练好的模型文件。
* **`tests/`**: 单元测试和集成测试。
* **`scripts/`**: 辅助脚本。
* **`training/`** & **`logs/`**: 训练日志和过程文件。
* **根目录脚本**: 包含多个入口脚本 (如 `ensemble_predict.py`, `train_catboost.py`),修改时需格外小心,避免破坏生产流程。
## 6. 开发流程 (Workflow)
1. **理解需求**: 先阅读代码,确认需求范围。如果有歧义,**先询问主人**。
2. **定位代码**: 使用 `grep` 或 `glob` 查找相关文件。
3. **制定计划**: 思考修改方案,优先考虑不破坏现有逻辑的“加法”修改。
4. **实施修改**: 编辑代码,保持风格一致。
5. **验证**:
* 运行 `flake8` 检查语法错误。
* 运行 `pytest` 验证逻辑。
* 如果是脚本,尝试带 `--help` 或测试参数运行以确保无运行时错误。
## 7. 禁止事项 (Constraints)
## 6. 禁止事项 (Constraints)
* ❌ **禁止**猜测业务逻辑。
* ❌ **禁止**删除看似无用但可能被外部脚本调用的代码。
* ❌ **禁止**在未确认的情况下升级核心库版本 (如 torch, pandas)。
* ❌ **禁止**输出英语回复 (代码变量名除外)。
* ❌ **禁止**恢复已删除的 SQLite 相关代码。
---
*遵循此指南,确保高效、安全地协助主人完成任务。*
+83
View File
@@ -0,0 +1,83 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## 项目概述
基于深度学习的 BTC 小时级涨跌预测系统,包含模型训练、FastAPI 后端、Vue 3 看板、Polymarket 自动交易。当前最佳模型 DLinear 准确率 53.10%。
## 环境与构建
```bash
# Python 环境(必须使用 Conda,禁止 venv/poetry)
conda activate crypto_pred_312
# 安装依赖(PyTorch 需根据平台单独安装,requirements.txt 不含 torch)
pip install -r requirements.txt
# 数据库:仅支持 MySQL,禁止 SQLite
# 配置:复制 .env.example 为 .env 并填写 MySQL 连接信息
```
## 常用命令
```bash
# 启动完整系统(API + 自动交易)
python start_system.py
# 单独启动 API 服务
python -m uvicorn api.main:app --host 0.0.0.0 --port 8000
# 单独启动自动交易
python auto_trade_polymarket.py
# 训练 DLinear 分类模型
python train_classification_full.py
# 测试模型(2026年数据)
python test_models_feb_2026.py
# 集成预测
python ensemble_predict.py
# 测试与检查
pytest
flake8 .
```
## 架构
```
数据流: Binance API → data/fetcher.py → data/preprocessor.py (技术指标) → data/dataset.py → 模型推理
交易流: 模型预测 → auto_trade_polymarket.py → Polymarket 下单 → MySQL 记录
服务流: FastAPI (api/main.py) → 前端看板 (api/static/) → http://localhost:8000
```
### 核心模块
- **`data/`** — 数据层:`fetcher.py`(Binance数据获取)、`preprocessor.py`(特征工程: RSI+MACD)、`dataset.py`(PyTorch数据集)、`database.py`(市场数据MySQL)、`trading_db.py`(交易历史MySQL)
- **`models/`** — 模型层:`dlinear/model.py`(主力模型,时序分解+线性层)、`nbeats/`、`base.py`(抽象基类)
- **`models/checkpoints/dlinear_classification_2026/`** — 训练产物:`best_model.pt`、`preprocessor.pkl`、`test_2026_results.json`
- **`training/`** — 训练层:`trainer.py`(训练循环,支持 MPS/CUDA/CPU)、`config.py`(超参数)、`evaluator.py`(评估)
- **`api/`** — 服务层:`main.py`(FastAPI入口)、`models.py`(DB操作+时区转换)、`prediction_service.py`(预测服务)、`polymarket_client.py`(交易客户端)
- **`start_system.py`** — 统一启动入口(同时启动 API 和自动交易)
- **`auto_trade_polymarket.py`** — 自动交易主程序(每小时执行预测+下单)
### DLinear 模型结构
输入 (168h × 9特征) → MovingAvg 时序分解 → 季节性/趋势分量各经线性层 → MLP 聚合 → logits 输出 (BCEWithLogitsLoss)
## 开发约束
- **数据库**:仅 MySQL(`pymysql`),禁止 SQLite。连接使用 `data/database.py` 的 `DatabaseManager` 或 `api/models.py` 的 `get_db_connection`
- **时区**:数据库存 UTC/ET,前端展示 CST(北京时间),转换用 `api.models.convert_to_cst()`
- **代码注释**:中文,解释"为什么"
- **命名**:变量/函数 `snake_case`,类 `PascalCase`
- **代理访问 Binance**:需在 `.env` 配置 `SOCKS5_PROXY` 和 `USE_PROXY=true`
- **日志**:交易日志在 `logs/polymarket_trading.log`(10MB 轮转)
## 禁止事项
- 禁止引入 SQLite 或恢复已删除的 SQLite 代码
- 禁止在未确认的情况下升级核心库版本(torch, pandas 等)
- 禁止删除看似无用但可能被外部脚本调用的代码
+42 -24
View File
@@ -17,12 +17,13 @@ TZ_ET = ZoneInfo('America/New_York') # 美东时间 (Polymarket)
TZ_CST = ZoneInfo('Asia/Shanghai') # 中国标准时间 (前端显示)
def convert_to_cst(timestamp_val) -> str:
def convert_to_cst(timestamp_val, source_tz: str = 'UTC') -> str:
"""
将时间戳转换为 CST 时间字符串
Args:
timestamp_val: datetime 对象或 ISO 格式时间戳字符串 (假设是 UTC)
timestamp_val: datetime 对象或 ISO 格式时间戳字符串
source_tz: 输入时区 - 'UTC'(默认,币安数据), 'ET'(Polymarket), 'CST'(已是北京时间)
Returns:
CST 时间字符串 (YYYY-MM-DD HH:MM:SS)
@@ -37,9 +38,19 @@ def convert_to_cst(timestamp_val) -> str:
# 字符串格式
dt = datetime.fromisoformat(str(timestamp_val).replace('Z', '+00:00'))
# 如果没有时区信息,假设是 UTC
# 根据来源时区设置时区信息
if dt.tzinfo is None:
dt = dt.replace(tzinfo=TZ_UTC)
if source_tz == 'ET':
dt = dt.replace(tzinfo=TZ_ET)
elif source_tz == 'CST':
# 已经是 CST,直接格式化返回
return dt.strftime('%Y-%m-%d %H:%M:%S')
else: # 默认 UTC
dt = dt.replace(tzinfo=TZ_UTC)
# 如果来源已经是 CST,直接格式化
if source_tz == 'CST':
return dt.strftime('%Y-%m-%d %H:%M:%S')
# 转换为 CST
dt_cst = dt.astimezone(TZ_CST)
@@ -122,7 +133,8 @@ class TradeModel:
row = cursor.fetchone()
result = _dict_from_row(row)
if result and result.get('timestamp'):
result['timestamp_cst'] = convert_to_cst(result['timestamp'])
# 交易记录时间已经是 CST,直接格式化
result['timestamp_cst'] = convert_to_cst(result['timestamp'], source_tz='CST')
return result
@staticmethod
@@ -173,7 +185,8 @@ class TradeModel:
for row in rows:
item = _dict_from_row(row)
if item and item.get('timestamp'):
item['timestamp_cst'] = convert_to_cst(item['timestamp'])
# 交易记录时间已经是 CST,直接格式化
item['timestamp_cst'] = convert_to_cst(item['timestamp'], source_tz='CST')
data_list.append(item)
return {
@@ -292,7 +305,7 @@ class PredictionModel:
SUM(CASE WHEN is_correct = 1 THEN 1 ELSE 0 END) as correct,
SUM(CASE WHEN is_correct = 0 THEN 1 ELSE 0 END) as incorrect
FROM model_predictions
WHERE DATE_FORMAT(date, '%%Y-%%m') = %s
WHERE DATE_FORMAT(CONVERT_TZ(timestamp, '+00:00', '+08:00'), '%%Y-%%m') = %s
AND actual_result IS NOT NULL
GROUP BY model_name
"""
@@ -333,16 +346,16 @@ class PredictionModel:
sql = """
SELECT
date,
DATE(CONVERT_TZ(timestamp, '+00:00', '+08:00')) as date_cst,
model_name,
COUNT(*) as total,
SUM(CASE WHEN is_correct = 1 THEN 1 ELSE 0 END) as correct,
SUM(CASE WHEN is_correct = 0 THEN 1 ELSE 0 END) as incorrect
FROM model_predictions
WHERE DATE_FORMAT(date, '%%Y-%%m') = %s
WHERE DATE_FORMAT(CONVERT_TZ(timestamp, '+00:00', '+08:00'), '%%Y-%%m') = %s
AND actual_result IS NOT NULL
GROUP BY date, model_name
ORDER BY date DESC
GROUP BY date_cst, model_name
ORDER BY date_cst DESC
"""
cursor.execute(sql, (month,))
@@ -352,7 +365,7 @@ class PredictionModel:
daily_data = {}
for row in rows:
row_dict = _dict_from_row(row)
date = row_dict['date']
date = str(row_dict['date_cst']) # CST 日期
if date not in daily_data:
daily_data[date] = {"date": date}
@@ -391,28 +404,33 @@ class PredictionModel:
获取指定日期的每小时详情
Args:
date: 日期 (YYYY-MM-DD)
date: 日期 (YYYY-MM-DD, CST 北京时间)
"""
with get_db_connection() as conn:
cursor = conn.cursor()
# 按 CST 日期筛选,并获取 CST 小时
cursor.execute("""
SELECT * FROM model_predictions
WHERE date = %s
ORDER BY hour, model_name
SELECT
*,
HOUR(CONVERT_TZ(timestamp, '+00:00', '+08:00')) as hour_cst,
CONVERT_TZ(timestamp, '+00:00', '+08:00') as timestamp_cst_dt
FROM model_predictions
WHERE DATE(CONVERT_TZ(timestamp, '+00:00', '+08:00')) = %s
ORDER BY hour_cst, model_name
""", (date,))
rows = cursor.fetchall()
# 按小时重组数据
# 按 CST 小时重组数据
hourly_data = {}
for row in rows:
row_dict = _dict_from_row(row)
hour = row_dict['hour']
if hour not in hourly_data:
# 将 timestamp 转换为 CST 时间
hour_cst = row_dict['hour_cst'] # 使用 CST 小时
if hour_cst not in hourly_data:
# 将 timestamp 转换为 CST 时间字符串
timestamp_cst = convert_to_cst(row_dict.get('timestamp', ''))
hourly_data[hour] = {
"hour": hour,
hourly_data[hour_cst] = {
"hour": hour_cst, # 返回 CST 小时
"timestamp_et": row_dict['timestamp_et'],
"timestamp_cst": timestamp_cst,
"open_price": row_dict['open_price'],
@@ -422,13 +440,13 @@ class PredictionModel:
}
model = row_dict['model_name']
hourly_data[hour]["models"][model] = {
hourly_data[hour_cst]["models"][model] = {
"prediction": row_dict['prediction'],
"probability": row_dict['probability'],
"is_correct": row_dict['is_correct']
}
# 按小时排序返回
# 按 CST 小时排序返回
return [hourly_data[h] for h in sorted(hourly_data.keys())]
@staticmethod
+8 -2
View File
@@ -87,12 +87,18 @@ class PredictionService:
def __init__(self):
import threading
import platform
# 初始化锁
if PredictionService._prediction_lock is None:
PredictionService._prediction_lock = threading.Lock()
self.device = torch.device('mps' if torch.backends.mps.is_available() else
'cuda' if torch.cuda.is_available() else 'cpu')
# 设备选择:macOS 用 MPS,Windows/Linux 用 CPU
if platform.system() == 'Darwin': # macOS
self.device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
else: # Windows / Linux
self.device = torch.device('cpu')
print(f"使用设备: {self.device}")
self.et_tz = ZoneInfo('America/New_York')
self.fetcher = BinanceDataFetcher()
+2 -2
View File
@@ -259,7 +259,7 @@
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>日期</th>
<th>日期 (北京)</th>
<th>DLinear 预测</th>
<th>DLinear 准确率</th>
<th>TTM-R2 预测</th>
@@ -341,7 +341,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="bi bi-clock"></i> {{ hourlyModal.date }} 每小时预测详情
<i class="bi bi-clock"></i> {{ hourlyModal.date }} 每小时预测详情 (北京时间)
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
+116 -24
View File
@@ -35,6 +35,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from data.fetcher import BinanceDataFetcher
from data.trading_db import TradingDatabase
from api.models import PredictionModel
load_dotenv()
@@ -123,24 +124,44 @@ class ClassificationPreprocessor:
df = self.add_features(df)
df = self.create_labels(df)
df = df.dropna().reset_index(drop=True)
feature_cols = ['open', 'high', 'low', 'close', 'volume',
feature_cols = ['open', 'high', 'low', 'close', 'volume',
'rsi', 'macd', 'macd_hist', 'macd_signal']
if fit:
scaled_features = self.scaler.fit_transform(df[feature_cols])
self.is_fitted = True
self.feature_columns = feature_cols
else:
scaled_features = self.scaler.transform(df[feature_cols])
X, y = [], []
for i in range(len(scaled_features) - self.lookback):
X.append(scaled_features[i:i + self.lookback])
y.append(df['label'].iloc[i + self.lookback])
return np.array(X), np.array(y), df
def preprocess_for_inference(self, df: pd.DataFrame):
"""推理专用预处理:不调用create_labels,整个df一次性处理
与test_dlinear_jan_2026.py逻辑一致:
加特征 -> dropna -> scaler.transform -> 构建窗口
"""
df = self.add_features(df)
df = df.dropna().reset_index(drop=True)
feature_cols = ['open', 'high', 'low', 'close', 'volume',
'rsi', 'macd', 'macd_hist', 'macd_signal']
scaled_features = self.scaler.transform(df[feature_cols])
X = []
for i in range(len(scaled_features) - self.lookback):
X.append(scaled_features[i:i + self.lookback])
return np.array(X), df
class PolymarketBTCTrader:
"""Polymarket BTC Hourly交易器"""
@@ -175,9 +196,13 @@ class PolymarketBTCTrader:
headers={'Accept-Encoding': 'identity'}
)
# 设备
self.device = torch.device('mps' if torch.backends.mps.is_available() else
'cuda' if torch.cuda.is_available() else 'cpu')
# 设备选择:macOS 用 MPS,Windows/Linux 用 CPU
import platform
if platform.system() == 'Darwin': # macOS
self.device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
else: # Windows / Linux
self.device = torch.device('cpu')
print(f"使用设备: {self.device}")
# 加载模型
self.model = None
@@ -610,13 +635,25 @@ class PolymarketBTCTrader:
logger.info(f" 上一K线时间: {prev_time} (用于预测)")
logger.info(f" 上一收盘价: ${prev_close:,.2f}")
logger.info(f" 当前K线时间: {current_time} (预测目标)")
logger.info(f" 当前收盘价: ${current_close:,.2f} (实际结果)")
logger.info(f" 当前收盘价: ${current_close:,.2f} (未收盘)")
# 回填上一小时的实际结果
# prev_time的K线已收盘,用它的收盘价(prev_close) vs 再前一根的收盘价来判断涨跌
self._update_previous_prediction(df)
# 用除最后一根外的数据进行预测
# 用除最后一根外的数据进行预测(无create_labels,与test逻辑一致)
df_for_prediction = df.iloc[:-1]
X, _, _ = self.preprocessor.preprocess(df_for_prediction, fit=False)
X, processed_df = self.preprocessor.preprocess_for_inference(df_for_prediction)
# 打印模型输入的最后一根K线数据
last_input_row = df_for_prediction.iloc[-1]
logger.info(f" 📌 模型输入末根K线: {last_input_row['timestamp']} "
f"O:{last_input_row['open']:.2f} H:{last_input_row['high']:.2f} "
f"L:{last_input_row['low']:.2f} C:{last_input_row['close']:.2f} "
f"V:{last_input_row['volume']:.0f}")
X_last = torch.FloatTensor(X[-1:]).to(self.device)
with torch.no_grad():
logits = self.model(X_last).cpu().numpy()
prob = 1 / (1 + np.exp(-logits))
@@ -627,17 +664,72 @@ class PolymarketBTCTrader:
confidence = probability if prediction == 1 else 1 - probability
logger.info(f"🤖 DLinear预测: {direction}")
logger.info(f" 置信度: {confidence:.2%}")
# 显示预测是否正确 (用于验证)
actual_up = current_close > prev_close
if prediction == 1:
correct = actual_up
else:
correct = not actual_up
logger.info(f" 实际涨跌: {'📈 涨' if actual_up else '📉 跌'} ({'✅ 正确' if correct else '❌ 错误'})")
# 保存预测结果到 model_predictions 表
# 当前K线尚未收盘,actual_result 和 is_correct 留空,等收盘后更新
try:
et_tz = ZoneInfo('America/New_York')
# current_time 是目标K线的UTC时间戳(当前正在进行的K线)
current_time_utc = pd.Timestamp(current_time)
current_time_et = current_time_utc.tz_localize('UTC').tz_convert(et_tz)
prediction_data = {
"timestamp": current_time_utc.isoformat(),
"timestamp_et": current_time_et.isoformat(),
"date": current_time_et.strftime('%Y-%m-%d'),
"hour": current_time_et.hour,
"model_name": "DLinear",
"prediction": prediction,
"probability": probability,
"actual_result": None,
"is_correct": None,
"open_price": float(df['open'].iloc[-1]),
"close_price": None,
}
PredictionModel.save_prediction(prediction_data)
logger.info(f"💾 预测记录已保存 (ET {current_time_et.strftime('%Y-%m-%d %H:%M')})")
except Exception as e:
logger.error(f"保存预测记录失败: {e}")
return prediction, probability
def _update_previous_prediction(self, df: pd.DataFrame):
"""回填上一小时预测的实际结果
df.iloc[-2] 是上一小时已收盘K线,收盘价 vs 开盘价即为实际涨跌。
"""
if len(df) < 2:
return
try:
et_tz = ZoneInfo('America/New_York')
prev_row = df.iloc[-2]
prev_utc = pd.Timestamp(prev_row['timestamp'])
prev_et = prev_utc.tz_localize('UTC').tz_convert(et_tz)
prev_date = prev_et.strftime('%Y-%m-%d')
prev_hour = prev_et.hour
prev_open = float(prev_row['open'])
prev_close = float(prev_row['close'])
actual_result = 1 if prev_close > prev_open else 0
updated = PredictionModel.update_actual_result(
date=prev_date,
hour=prev_hour,
model_name="DLinear",
actual_result=actual_result,
close_price=prev_close
)
if updated:
record = PredictionModel.get_prediction(prev_date, prev_hour, "DLinear")
is_correct = record.get('is_correct') if record else None
correct_str = '✅ 正确' if is_correct == 1 else '❌ 错误' if is_correct == 0 else '未知'
logger.info(f"📝 回填上一小时结果: ET {prev_date} {prev_hour}:00, "
f"开:{prev_open:.2f} 收:{prev_close:.2f} "
f"实际{'涨' if actual_result == 1 else '跌'}, {correct_str}")
except Exception as e:
logger.error(f"回填上一小时结果失败: {e}")
def run_once(self) -> Dict:
"""运行一次交易流程(不等待整点)"""
logger.info("运行单次交易...")
@@ -780,9 +872,9 @@ class PolymarketBTCTrader:
logger.info(f" 上一K线: {prev_time} 收盘价: ${prev_close:,.2f}")
logger.info(f" 当前K线: {current_time} 收盘价: ${current_close:,.2f}")
# 进行预测
# 进行预测(无create_labels,与test逻辑一致)
df_for_prediction = df.iloc[:-1]
X, _, _ = self.preprocessor.preprocess(df_for_prediction, fit=False)
X, _ = self.preprocessor.preprocess_for_inference(df_for_prediction)
X_last = torch.FloatTensor(X[-1:]).to(self.device)
with torch.no_grad():
+380
View File
@@ -0,0 +1,380 @@
"""对比 test 旧逻辑 vs auto_trade 逻辑的数据处理差异
两种预处理方式:
- test旧逻辑:整个df一次性预处理,无create_labels,按索引遍历窗口
- auto_trade逻辑:截取到当前K线的df,含create_labels,取X[-1]预测
对比内容:
1. 预处理后的df行数差异
2. 同一时间点的特征值差异(RSI、MACD等)
3. scaler transform后的值差异
4. 最终输入窗口的数值差异
5. 模型输出logits/概率/预测结果差异
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import torch
import numpy as np
import pandas as pd
import pickle
import pandas_ta as ta
from datetime import timedelta
from sklearn.preprocessing import StandardScaler
from data.fetcher import BinanceDataFetcher
# ==================== pickle反序列化需要的占位类 ====================
class ClassificationPreprocessor:
"""pkl文件中保存的类,pickle.load时需要能找到这个类名"""
def __init__(self, lookback=168, forecast_horizon=1):
self.lookback = lookback
self.forecast_horizon = forecast_horizon
self.scaler = StandardScaler()
self.is_fitted = False
# ==================== 两种预处理器 ====================
class TestPreprocessor:
"""test旧逻辑:无create_labels"""
def __init__(self, lookback=168):
self.lookback = lookback
self.scaler = StandardScaler()
def preprocess(self, df, fit=True):
df = df.copy()
df['rsi'] = ta.rsi(df['close'], length=14)
macd = ta.macd(df['close'])
if macd is not None and not macd.empty:
df['macd'] = macd.iloc[:, 0]
df['macd_hist'] = macd.iloc[:, 1]
df['macd_signal'] = macd.iloc[:, 2]
df = df.dropna().reset_index(drop=True)
feature_cols = ['open', 'high', 'low', 'close', 'volume',
'rsi', 'macd', 'macd_hist', 'macd_signal']
if fit:
scaled_features = self.scaler.fit_transform(df[feature_cols])
else:
scaled_features = self.scaler.transform(df[feature_cols])
X = []
valid_indices = []
for i in range(len(scaled_features) - self.lookback):
X.append(scaled_features[i:i + self.lookback])
valid_indices.append(i + self.lookback - 1)
return np.array(X), df, valid_indices, scaled_features
class AutoTradePreprocessor:
"""auto_trade逻辑:含create_labels"""
def __init__(self, lookback=168, forecast_horizon=1):
self.lookback = lookback
self.forecast_horizon = forecast_horizon
self.scaler = StandardScaler()
def preprocess(self, df, fit=True):
df = df.copy()
df['rsi'] = ta.rsi(df['close'], length=14)
macd = ta.macd(df['close'])
if macd is not None and not macd.empty:
df['macd'] = macd.iloc[:, 0]
df['macd_hist'] = macd.iloc[:, 1]
df['macd_signal'] = macd.iloc[:, 2]
# create_labels: shift + 砍最后一行
df['future_price'] = df['close'].shift(-self.forecast_horizon)
df['future_return'] = (df['future_price'] - df['close']) / df['close']
df['label'] = (df['future_return'] > 0).astype(int)
df = df[:-self.forecast_horizon]
df = df.dropna().reset_index(drop=True)
feature_cols = ['open', 'high', 'low', 'close', 'volume',
'rsi', 'macd', 'macd_hist', 'macd_signal']
if fit:
scaled_features = self.scaler.fit_transform(df[feature_cols])
else:
scaled_features = self.scaler.transform(df[feature_cols])
X = []
for i in range(len(scaled_features) - self.lookback):
X.append(scaled_features[i:i + self.lookback])
return np.array(X), df, scaled_features
def utc_to_beijing(ts):
return ts + timedelta(hours=8)
def main():
print("=" * 100)
print("数据处理对比:test旧逻辑 vs auto_trade逻辑")
print("=" * 100)
device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
# 加载模型和训练时的scaler
from models.dlinear.model import DLinear
checkpoint = torch.load('./models/checkpoints/dlinear_classification_2026/best_model.pt',
map_location=device, weights_only=False)
model = DLinear(checkpoint['config']).to(device).eval()
with open('./models/checkpoints/dlinear_classification_2026/preprocessor.pkl', 'rb') as f:
trained_preprocessor = pickle.load(f)
# 提取训练时的scaler参数
trained_scaler = trained_preprocessor.scaler
print(f"训练时scaler均值: {trained_scaler.mean_}")
print(f"训练时scaler方差: {trained_scaler.scale_}")
# 获取数据
print("\n获取数据...")
fetcher = BinanceDataFetcher()
df_full = fetcher.fetch_ohlcv('BTC', '1h', start_date='2025-12-01', end_date='2026-03-01', use_cache=False)
print(f"总行数: {len(df_full)}")
# 选几个2月10日(北京时间)的时间点做对比
# 北京时间02-10 00:00 = UTC 02-09 16:00
test_targets_utc = [
'2026-02-09 16:00:00', # 北京 02-10 00:00
'2026-02-09 20:00:00', # 北京 02-10 04:00
'2026-02-10 00:00:00', # 北京 02-10 08:00
'2026-02-10 04:00:00', # 北京 02-10 12:00
'2026-02-10 08:00:00', # 北京 02-10 16:00
'2026-02-10 12:00:00', # 北京 02-10 20:00
]
feature_cols = ['open', 'high', 'low', 'close', 'volume',
'rsi', 'macd', 'macd_hist', 'macd_signal']
for target_utc_str in test_targets_utc:
target_utc = pd.Timestamp(target_utc_str)
target_bj = utc_to_beijing(target_utc)
# 找到目标K线在df中的索引
target_mask = df_full['timestamp'] == target_utc
if not target_mask.any():
print(f"\n⚠ 找不到 {target_utc_str},跳过")
continue
target_idx = df_full[target_mask].index[0]
input_idx = target_idx - 1 # 输入的最后一根K线
input_row = df_full.iloc[input_idx]
target_row = df_full.iloc[target_idx]
print(f"\n{'=' * 100}")
print(f"目标K线: 北京{target_bj.strftime('%m-%d %H:%M')} (UTC {target_utc_str})")
print(f"输入末根: UTC {input_row['timestamp']} O:{input_row['open']:.2f} H:{input_row['high']:.2f} L:{input_row['low']:.2f} C:{input_row['close']:.2f}")
print(f"目标K线: UTC {target_row['timestamp']} 收盘:{target_row['close']:.2f}")
print("-" * 100)
# ===== 方式A: test旧逻辑 =====
# 对整个df一次性预处理,按索引找到对应窗口
test_prep = TestPreprocessor(lookback=168)
test_prep.scaler = trained_scaler # 使用训练时的scaler
X_test, df_test, idx_test, scaled_test = test_prep.preprocess(df_full, fit=False)
# 找到input_idx对应的窗口
# idx_test[i] = i + 168 - 1,所以 input_idx 在 df_test 中的位置需要查找
# df_test 是 dropna 后 reset_index 的,timestamp 可能对应不同的行号
test_input_mask = df_test['timestamp'] == input_row['timestamp']
if test_input_mask.any():
test_df_idx = df_test[test_input_mask].index[0]
# 对应的窗口序号
test_window_i = test_df_idx - 168 + 1
if 0 <= test_window_i < len(X_test):
X_test_window = X_test[test_window_i]
else:
X_test_window = None
print(" [test] 窗口索引越界")
else:
test_df_idx = None
X_test_window = None
print(" [test] 在df_test中找不到对应时间")
# ===== 方式B: auto_trade逻辑 =====
# 截取到input_idx为止,含create_labels
df_up_to_t = df_full.iloc[:input_idx + 1]
auto_prep = AutoTradePreprocessor(lookback=168)
auto_prep.scaler = trained_scaler # 使用训练时的scaler
X_auto, df_auto, scaled_auto = auto_prep.preprocess(df_up_to_t, fit=False)
if len(X_auto) > 0:
X_auto_window = X_auto[-1] # 取最后一个窗口
else:
X_auto_window = None
print(" [auto] X为空")
# ===== 对比 =====
# 1. df行数对比
print(f"\n[1] 预处理后df行数")
print(f" test: {len(df_test)} 行 (整个df, 无create_labels)")
print(f" auto: {len(df_auto)} 行 (截取到input, 含create_labels砍尾)")
# 2. 最后一行时间对比
print(f"\n[2] 预处理后df最后一行")
print(f" test最后一行: {df_test.iloc[-1]['timestamp']} close={df_test.iloc[-1]['close']:.2f}")
print(f" auto最后一行: {df_auto.iloc[-1]['timestamp']} close={df_auto.iloc[-1]['close']:.2f}")
# 3. 输入末根K线的原始特征对比
if test_df_idx is not None:
test_features = df_test.loc[test_df_idx, feature_cols]
auto_last_row = df_auto.iloc[-1]
auto_features = auto_last_row[feature_cols]
print(f"\n[3] 输入末根K线原始特征值")
print(f" {'特征':<15} {'test值':>15} {'auto值':>15} {'差异':>15}")
print(f" {'-'*60}")
for col in feature_cols:
tv = test_features[col]
av = auto_features[col]
diff = tv - av
marker = " ⚠" if abs(diff) > 1e-6 else ""
print(f" {col:<15} {tv:>15.6f} {av:>15.6f} {diff:>15.8f}{marker}")
# 4. scaler transform 后的值对比(输入末根K线)
if test_df_idx is not None and X_test_window is not None and X_auto_window is not None:
test_scaled_last = scaled_test[test_df_idx]
auto_scaled_last = scaled_auto[-1]
print(f"\n[4] scaler transform 后的值(输入末根K线)")
print(f" {'特征':<15} {'test值':>15} {'auto值':>15} {'差异':>15}")
print(f" {'-'*60}")
for j, col in enumerate(feature_cols):
tv = test_scaled_last[j]
av = auto_scaled_last[j]
diff = tv - av
marker = " ⚠" if abs(diff) > 1e-6 else ""
print(f" {col:<15} {tv:>15.6f} {av:>15.6f} {diff:>15.8f}{marker}")
# 5. 输入窗口对比(整个168步窗口)
if X_test_window is not None and X_auto_window is not None:
window_diff = X_test_window - X_auto_window
max_diff = np.max(np.abs(window_diff))
mean_diff = np.mean(np.abs(window_diff))
nonzero_count = np.count_nonzero(np.abs(window_diff) > 1e-6)
print(f"\n[5] 输入窗口对比 (168步 × 9特征 = 1512个值)")
print(f" 最大绝对差异: {max_diff:.10f}")
print(f" 平均绝对差异: {mean_diff:.10f}")
print(f" 差异>1e-6的值: {nonzero_count} / 1512")
if nonzero_count > 0:
# 找到差异最大的位置
max_pos = np.unravel_index(np.argmax(np.abs(window_diff)), window_diff.shape)
print(f" 最大差异位置: 步={max_pos[0]} 特征={feature_cols[max_pos[1]]}")
print(f" test值: {X_test_window[max_pos]:.10f}")
print(f" auto值: {X_auto_window[max_pos]:.10f}")
# 6. 模型推理对比
if X_test_window is not None and X_auto_window is not None:
with torch.no_grad():
test_tensor = torch.FloatTensor(X_test_window[np.newaxis]).to(device)
auto_tensor = torch.FloatTensor(X_auto_window[np.newaxis]).to(device)
test_logits = model(test_tensor).cpu().numpy().item()
auto_logits = model(auto_tensor).cpu().numpy().item()
test_prob = 1 / (1 + np.exp(-test_logits))
auto_prob = 1 / (1 + np.exp(-auto_logits))
test_pred = "涨" if test_prob > 0.5 else "跌"
auto_pred = "涨" if auto_prob > 0.5 else "跌"
match = "✓ 一致" if test_pred == auto_pred else "✗ 不一致"
print(f"\n[6] 模型推理结果")
print(f" {'':15} {'test':>15} {'auto':>15} {'差异':>15}")
print(f" {'-'*60}")
print(f" {'logits':<15} {test_logits:>15.8f} {auto_logits:>15.8f} {test_logits-auto_logits:>15.8f}")
print(f" {'概率':<15} {test_prob:>15.8f} {auto_prob:>15.8f} {test_prob-auto_prob:>15.8f}")
print(f" {'预测':<15} {test_pred:>15} {auto_pred:>15} {match:>15}")
# ===== 全量对比:2月所有小时 =====
print(f"\n\n{'=' * 100}")
print("全量对比:2月份所有小时预测结果差异统计")
print("=" * 100)
# test方式:一次性预处理
test_prep_full = TestPreprocessor(lookback=168)
test_prep_full.scaler = trained_scaler
X_test_full, df_test_full, idx_test_full, _ = test_prep_full.preprocess(df_full, fit=False)
# 构建 timestamp -> test窗口索引 的映射
test_ts_map = {}
for i, idx in enumerate(idx_test_full):
ts = df_test_full.iloc[idx]['timestamp']
test_ts_map[ts] = i
total = 0
same = 0
diff_list = []
for target_idx in range(1, len(df_full)):
target_time_utc = df_full.iloc[target_idx]['timestamp']
target_time_bj = utc_to_beijing(target_time_utc)
if not (target_time_bj.year == 2026 and target_time_bj.month == 2):
continue
input_idx = target_idx - 1
input_ts = df_full.iloc[input_idx]['timestamp']
# test方式
test_i = test_ts_map.get(input_ts)
if test_i is None or test_i >= len(X_test_full):
continue
# auto方式
df_up_to_t = df_full.iloc[:input_idx + 1]
auto_prep_tmp = AutoTradePreprocessor(lookback=168)
auto_prep_tmp.scaler = trained_scaler
X_auto_tmp, _, _ = auto_prep_tmp.preprocess(df_up_to_t, fit=False)
if len(X_auto_tmp) == 0:
continue
with torch.no_grad():
test_logits = model(torch.FloatTensor(X_test_full[test_i:test_i+1]).to(device)).cpu().numpy().item()
auto_logits = model(torch.FloatTensor(X_auto_tmp[-1:]).to(device)).cpu().numpy().item()
test_prob = 1 / (1 + np.exp(-test_logits))
auto_prob = 1 / (1 + np.exp(-auto_logits))
test_pred = 1 if test_prob > 0.5 else 0
auto_pred = 1 if auto_prob > 0.5 else 0
total += 1
if test_pred == auto_pred:
same += 1
else:
diff_list.append({
'time_bj': target_time_bj.strftime('%m-%d %H:%M'),
'test_prob': test_prob,
'auto_prob': auto_prob,
'test_pred': '涨' if test_pred == 1 else '跌',
'auto_pred': '涨' if auto_pred == 1 else '跌',
})
print(f"总预测数: {total}")
print(f"结果一致: {same} ({same/total:.1%})")
print(f"结果不同: {len(diff_list)} ({len(diff_list)/total:.1%})")
if diff_list:
print(f"\n不一致的时间点:")
print(f" {'北京时间':<15} {'test概率':>10} {'test预测':>8} {'auto概率':>10} {'auto预测':>8}")
print(f" {'-'*55}")
for d in diff_list:
print(f" {d['time_bj']:<15} {d['test_prob']:>10.6f} {d['test_pred']:>8} {d['auto_prob']:>10.6f} {d['auto_pred']:>8}")
print("=" * 100)
if __name__ == "__main__":
main()
+295
View File
@@ -0,0 +1,295 @@
# Windows 环境安装指南
本指南帮助您在 Windows 系统上部署加密货币预测系统。
---
## 一、前置要求
- Windows 10/11 (64位)
- 至少 8GB 内存
- 至少 10GB 可用磁盘空间
- 网络连接(需访问 GitHub、PyPI、Binance API)
---
## 二、安装 Miniconda
### 1. 下载 Miniconda
访问 [Miniconda 官网](https://docs.conda.io/en/latest/miniconda.html) 下载 Windows 64-bit 安装包,或直接下载:
```
https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe
```
### 2. 安装
1. 双击运行安装程序
2. 选择 "Just Me" (仅为当前用户安装)
3. 安装路径建议使用默认值或 `C:\Miniconda3`
4. **重要**:勾选 "Add Miniconda3 to my PATH environment variable"
5. 完成安装
### 3. 验证安装
打开 **命令提示符 (CMD)** 或 **PowerShell**:
```powershell
conda --version
# 应显示类似: conda 24.x.x
```
---
## 三、创建 Conda 环境
### 1. 创建 Python 3.12 环境
```powershell
conda create -n crypto_pred_312 python=3.12 -y
```
### 2. 激活环境
```powershell
conda activate crypto_pred_312
```
激活后,命令行前缀应显示 `(crypto_pred_312)`
---
## 四、克隆项目
### 1. 安装 Git (如未安装)
下载并安装 [Git for Windows](https://git-scm.com/download/win)
### 2. 克隆仓库
```powershell
cd C:\Projects # 或您喜欢的目录
git clone http://192.168.50.24:3000/subuhui/crpto_predictor.git
cd crpto_predictor
```
---
## 五、安装依赖
### 1. 安装 PyTorch (CPU 版本)
```powershell
# CPU 版本 PyTorch(Windows 不使用 GPU)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
```
### 2. 安装项目依赖
```powershell
pip install -r requirements.txt
```
### 3. 安装 IBM 时序模型 (TTM-R2)
```powershell
# PyPI 包名是 granite-tsfm,导入时用 tsfm_public
pip install granite-tsfm
```
验证安装:
```powershell
python -c "from tsfm_public.models.tinytimemixer import TinyTimeMixerForPrediction; print('TTM-R2 安装成功')"
```
---
## 六、配置环境变量
### 1. 复制环境变量模板
```powershell
copy .env.example .env
```
### 2. 编辑 .env 文件
使用记事本或 VSCode 编辑 `.env` 文件:
```ini
# MySQL 数据库配置
MYSQL_HOST=your_mysql_host
MYSQL_PORT=3306
MYSQL_USER=your_username
MYSQL_PASSWORD=your_password
MYSQL_DB=crypto_predictor
# Binance API (可选,用于实时数据)
BINANCE_API_KEY=your_api_key
BINANCE_API_SECRET=your_api_secret
# Polymarket 配置 (可选,用于自动交易)
POLYMARKET_PRIVATE_KEY=your_private_key
DRY_RUN=true # 设为 true 进行模拟交易
# 代理配置 (可选)
USE_PROXY=false
SOCKS5_PROXY=socks5://127.0.0.1:1080
```
---
## 七、数据库配置
### 1. 安装 MySQL
下载并安装 [MySQL Community Server](https://dev.mysql.com/downloads/mysql/)
或使用 Docker:
```powershell
docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=yourpassword mysql:8.0
```
### 2. 创建数据库
```sql
CREATE DATABASE crypto_predictor CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
### 3. 初始化表结构
首次运行时会自动创建表,或手动执行:
```powershell
python -c "from api.models import init_model_predictions_table; from data.trading_db import TradingDatabase; TradingDatabase()"
```
---
## 八、验证安装
### 1. 测试 PyTorch
```powershell
python -c "import torch; print(f'PyTorch: {torch.__version__}'); print(f'Device: cpu')"
```
### 2. 测试模型加载
```powershell
python -c "from training.config import TrainingConfig; c = TrainingConfig(); print(f'Device: {c.get_device()}')"
# 应输出: Device: cpu
```
### 3. 测试数据库连接
```powershell
python -c "from api.models import get_db_connection; conn = get_db_connection(); print('数据库连接成功!')"
```
---
## 九、运行系统
### 方式一:双击批处理脚本 (推荐)
双击项目根目录的 `start_system.bat`
### 方式二:PowerShell 脚本
```powershell
.\start_system.ps1
```
### 方式三:Python 脚本
```powershell
python start_system.py
```
### 方式四:分别启动
```powershell
# 终端 1: API 服务
python -m uvicorn api.main:app --host 0.0.0.0 --port 8000
# 终端 2: 自动交易
python auto_trade_polymarket.py
```
---
## 十、访问系统
- **前端看板**: http://localhost:8000
- **API 文档**: http://localhost:8000/docs
---
## 常见问题
### Q1: `conda activate` 不生效
在 PowerShell 中需要先初始化 conda:
```powershell
conda init powershell
# 重启 PowerShell
```
### Q2: pip 安装超时
使用国内镜像:
```powershell
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
```
### Q3: PyTorch 安装失败
确保使用 CPU 版本的安装命令,不要安装 CUDA 版本。
### Q4: 数据库连接失败
1. 检查 MySQL 服务是否运行
2. 检查 `.env` 中的数据库配置
3. 检查防火墙是否允许 3306 端口
### Q5: 模型文件缺失
确保 `models/checkpoints/` 目录包含预训练模型文件。如缺失,联系管理员获取。
---
## 目录结构
```
crpto_predictor/
├── api/ # API 服务
│ ├── main.py # FastAPI 入口
│ ├── models.py # 数据库模型
│ ├── prediction_service.py # 预测服务
│ └── static/ # 前端静态文件
├── models/ # 模型定义和权重
│ ├── checkpoints/ # 模型权重文件
│ └── dlinear/ # DLinear 模型
├── data/ # 数据层
├── training/ # 训练配置
├── start_system.py # 跨平台启动脚本
├── start_system.bat # Windows CMD 脚本
├── start_system.ps1 # Windows PowerShell 脚本
├── auto_trade_polymarket.py # 自动交易程序
├── requirements.txt # 依赖列表
└── .env # 环境变量 (需自行创建)
```
---
## 更新项目
```powershell
git pull origin main
pip install -r requirements.txt
```
---
*如有问题,请联系管理员。*
+58
View File
@@ -0,0 +1,58 @@
@echo off
chcp 65001 >nul
REM ============================================
REM Windows 环境安装脚本
REM 安装所有依赖和模型
REM ============================================
echo ==========================================
echo 加密货币预测系统 - Windows 安装脚本
echo ==========================================
echo.
REM 检查 conda
where conda >nul 2>&1
if %errorlevel% neq 0 (
echo [错误] 未找到 conda
echo 请先安装 Miniconda: https://docs.conda.io/en/latest/miniconda.html
pause
exit /b 1
)
echo [1/6] 创建 conda 环境...
call conda create -n crypto_pred_312 python=3.12 -y
if %errorlevel% neq 0 (
echo [警告] 环境可能已存在,继续...
)
echo.
echo [2/6] 激活环境...
call conda activate crypto_pred_312
echo.
echo [3/6] 安装 PyTorch (CPU 版本)...
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
echo.
echo [4/5] 安装项目依赖...
pip install -r requirements.txt
echo.
echo [5/5] 验证安装...
python -c "import torch; print(f'PyTorch: {torch.__version__}')"
python -c "from tsfm_public.models.tinytimemixer import TinyTimeMixerForPrediction; print('TTM-R2: 已安装')"
python -c "import pandas_ta; print('pandas-ta: 已安装')"
python -c "from training.config import TrainingConfig; c=TrainingConfig(); print(f'设备: {c.get_device()}')"
echo.
echo ==========================================
echo 安装完成!
echo ==========================================
echo.
echo 下一步:
echo 1. 复制 .env.example 为 .env 并配置数据库
echo 2. 双击 start_system.bat 启动系统
echo 3. 访问 http://localhost:8000
echo.
pause
+52 -28
View File
@@ -1,45 +1,69 @@
# Core dependencies
torch>=2.0.0
torchvision
torchaudio
python-binance>=1.0.19
# ==============================================
# 加密货币预测系统依赖
# Python 3.12 | 支持 macOS (MPS) 和 Windows (CPU)
# ==============================================
# ----------------------------------------------
# PyTorch (根据平台单独安装)
# ----------------------------------------------
# macOS: pip install torch torchvision torchaudio
# Windows CPU: pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
# 注意:requirements.txt 中不包含 torch,需要手动安装
# ----------------------------------------------
# 数据处理
# ----------------------------------------------
pandas>=2.0.0
numpy>=1.24.0
scikit-learn>=1.3.0
pytz
python-dotenv>=1.0.0
# ONNX
onnx>=1.14.0
onnxruntime>=1.15.0
# ----------------------------------------------
# 时序模型
# ----------------------------------------------
# IBM Granite TTM-R2 (导入名: tsfm_public)
granite-tsfm>=0.3.0
# Model-specific dependencies
darts>=0.27.0
u8darts[torch]>=0.27.0
tsfm_public>=0.2.0
gluonts[torch]>=0.14.0
# ----------------------------------------------
# 技术指标
# ----------------------------------------------
pandas-ta>=0.3.14b
# API dependencies
# ----------------------------------------------
# API 服务
# ----------------------------------------------
fastapi>=0.104.0
uvicorn[standard]>=0.24.0
pydantic>=2.0.0
python-multipart>=0.0.6
# Technical indicators
pandas-ta>=0.3.14b
# ----------------------------------------------
# HTTP 客户端
# ----------------------------------------------
httpx>=0.25.0
# Development tools
jupyter>=1.0.0
matplotlib>=3.7.0
seaborn>=0.12.0
pytest>=7.4.0
black>=23.0.0
flake8>=6.0.0
# ----------------------------------------------
# 数据源
# ----------------------------------------------
python-binance>=1.0.19
# Utilities
tqdm>=4.66.0
# ----------------------------------------------
# 任务调度
# ----------------------------------------------
schedule>=1.2.0
# Database
# ----------------------------------------------
# 数据库
# ----------------------------------------------
pymysql>=1.1.0
sqlalchemy>=2.0.0
cryptography>=41.0.0
# ----------------------------------------------
# 环境变量
# ----------------------------------------------
python-dotenv>=1.0.0
# ----------------------------------------------
# 进度条
# ----------------------------------------------
tqdm>=4.66.0
+37
View File
@@ -0,0 +1,37 @@
"""导出 DLinear 模型为 ONNX 格式。
用法:
python scripts/export_onnx.py
输出:
models/checkpoints/dlinear_classification_2026/model.onnx
"""
import sys
from pathlib import Path
# 添加项目根目录到路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from models.dlinear.model import DLinear
def main():
# 模型路径
checkpoint_path = project_root / "models/checkpoints/dlinear_classification_2026/best_model.pt"
output_path = checkpoint_path.parent / "model.onnx"
print(f"加载模型: {checkpoint_path}")
model = DLinear.load(str(checkpoint_path), device='cpu')
print(f"模型配置: lookback={model.config.lookback}, input_dim={model.config.input_dim}")
print(f"参数量: {model.count_parameters():,}")
# 导出为 ONNX
model.to_onnx(str(output_path))
print(f"✓ ONNX 模型已保存到: {output_path}")
if __name__ == "__main__":
main()
+47
View File
@@ -0,0 +1,47 @@
@echo off
chcp 65001 >nul
REM ============================================
REM 系统启动脚本 (Windows)
REM 启动 API 服务和自动交易程序
REM ============================================
echo [%date% %time%] 正在启动系统...
REM 检查 conda 环境
where conda >nul 2>&1
if %errorlevel% neq 0 (
echo [错误] 未找到 conda,请先安装 Anaconda 或 Miniconda
pause
exit /b 1
)
REM 激活 conda 环境
echo [%date% %time%] 激活 conda 环境: crypto_pred_312
call conda activate crypto_pred_312
if %errorlevel% neq 0 (
echo [错误] 无法激活 conda 环境 crypto_pred_312
echo 请先创建环境: conda create -n crypto_pred_312 python=3.12
pause
exit /b 1
)
REM 启动 API 服务 (后台运行)
echo [%date% %time%] 启动 API 服务...
start "API服务" cmd /c "python -m uvicorn api.main:app --host 0.0.0.0 --port 8000"
REM 等待 API 启动
timeout /t 3 /nobreak >nul
REM 启动自动交易程序 (后台运行)
echo [%date% %time%] 启动自动交易程序...
start "自动交易" cmd /c "python -u auto_trade_polymarket.py"
echo.
echo ============================================
echo 系统启动完成!
echo 前端访问: http://localhost:8000
echo 关闭窗口可停止对应服务
echo ============================================
echo.
pause
+90
View File
@@ -0,0 +1,90 @@
# ============================================
# 系统启动脚本 (Windows PowerShell)
# 启动 API 服务和自动交易程序
# ============================================
$ErrorActionPreference = "Stop"
function Write-Log {
param([string]$Message, [string]$Color = "White")
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Write-Host "[$timestamp] $Message" -ForegroundColor $Color
}
Write-Log "正在启动系统..." "Yellow"
# 检查 conda
$condaPath = Get-Command conda -ErrorAction SilentlyContinue
if (-not $condaPath) {
Write-Log "错误: 未找到 conda,请先安装 Anaconda 或 Miniconda" "Red"
Read-Host "按回车键退出"
exit 1
}
# 激活 conda 环境
Write-Log "激活 conda 环境: crypto_pred_312" "Yellow"
try {
conda activate crypto_pred_312
} catch {
Write-Log "错误: 无法激活 conda 环境 crypto_pred_312" "Red"
Write-Log "请先创建环境: conda create -n crypto_pred_312 python=3.12" "Yellow"
Read-Host "按回车键退出"
exit 1
}
# 启动 API 服务
Write-Log "启动 API 服务..." "Yellow"
$apiProcess = Start-Process -FilePath "python" `
-ArgumentList "-m uvicorn api.main:app --host 0.0.0.0 --port 8000" `
-PassThru -NoNewWindow
Write-Log "API 服务已启动 (PID: $($apiProcess.Id))" "Green"
# 等待 API 启动
Start-Sleep -Seconds 3
# 启动自动交易程序
Write-Log "启动自动交易程序..." "Yellow"
$tradeProcess = Start-Process -FilePath "python" `
-ArgumentList "-u auto_trade_polymarket.py" `
-PassThru -NoNewWindow
Write-Log "自动交易程序已启动 (PID: $($tradeProcess.Id))" "Green"
Write-Host ""
Write-Host "============================================" -ForegroundColor Cyan
Write-Host " 系统启动完成!" -ForegroundColor Green
Write-Host " 前端访问: http://localhost:8000" -ForegroundColor Green
Write-Host " 按 Ctrl+C 停止所有服务" -ForegroundColor Yellow
Write-Host "============================================" -ForegroundColor Cyan
Write-Host ""
# 等待用户中断
try {
while ($true) {
# 检查进程状态
if ($apiProcess.HasExited) {
Write-Log "API 服务意外退出" "Red"
break
}
if ($tradeProcess.HasExited) {
Write-Log "自动交易程序意外退出" "Red"
break
}
Start-Sleep -Seconds 1
}
} finally {
Write-Log "正在停止服务..." "Yellow"
if (-not $apiProcess.HasExited) {
Stop-Process -Id $apiProcess.Id -Force -ErrorAction SilentlyContinue
Write-Log "API 服务已停止" "Green"
}
if (-not $tradeProcess.HasExited) {
Stop-Process -Id $tradeProcess.Id -Force -ErrorAction SilentlyContinue
Write-Log "自动交易程序已停止" "Green"
}
Write-Log "所有服务已停止" "Green"
}
+69 -19
View File
@@ -4,41 +4,93 @@
同时启动:
1. FastAPI 后端服务 (提供前端看板 API)
2. Polymarket 自动交易程序
支持平台:macOS, Linux, Windows
"""
import subprocess
import sys
import time
import signal
import os
import platform
from datetime import datetime
# 颜色代码
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
RESET = '\033[0m'
# 检测操作系统
IS_WINDOWS = platform.system() == 'Windows'
# 颜色代码 (Windows CMD 默认不支持 ANSI,但现代 Windows Terminal 支持)
if IS_WINDOWS:
# 尝试启用 Windows ANSI 支持
try:
import ctypes
kernel32 = ctypes.windll.kernel32
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
ANSI_SUPPORTED = True
except Exception:
ANSI_SUPPORTED = False
else:
ANSI_SUPPORTED = True
if ANSI_SUPPORTED:
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
RESET = '\033[0m'
else:
GREEN = YELLOW = RED = RESET = ''
def log(message, color=RESET):
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print(f"{color}[{timestamp}] {message}{RESET}")
def start_process(command, name):
"""启动子进程"""
"""启动子进程(跨平台兼容)"""
log(f"正在启动 {name}...", YELLOW)
try:
process = subprocess.Popen(
command,
shell=True,
stdout=sys.stdout,
stderr=sys.stderr,
preexec_fn=os.setsid # 创建新的进程组,方便统一杀掉
)
if IS_WINDOWS:
# Windows: 不使用 preexec_fn,使用 CREATE_NEW_PROCESS_GROUP
process = subprocess.Popen(
command,
shell=True,
stdout=sys.stdout,
stderr=sys.stderr,
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
)
else:
# macOS / Linux: 使用 setsid 创建新进程组
process = subprocess.Popen(
command,
shell=True,
stdout=sys.stdout,
stderr=sys.stderr,
preexec_fn=os.setsid
)
log(f"{name} 已启动 (PID: {process.pid})", GREEN)
return process
except Exception as e:
log(f"启动 {name} 失败: {e}", RED)
return None
def terminate_process(process, name):
"""终止进程(跨平台兼容)"""
if process is None:
return
try:
if IS_WINDOWS:
# Windows: 使用 terminate() 或发送 CTRL_BREAK_EVENT
process.terminate()
else:
# macOS / Linux: 终止整个进程组
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
log(f"{name} 已停止", GREEN)
except Exception as e:
log(f"停止 {name} 时出错: {e}", RED)
# 强制杀死
try:
process.kill()
except Exception:
pass
def main():
# 激活 Conda 环境
# 注意:在子进程中直接使用当前已激活的 python 解释器路径,避免 conda activate 问题
@@ -64,10 +116,10 @@ def main():
try:
# 监控子进程状态
while True:
if api_process.poll() is not None:
if api_process and api_process.poll() is not None:
log("API 服务意外退出", RED)
break
if trade_process.poll() is not None:
if trade_process and trade_process.poll() is not None:
log("自动交易程序意外退出", RED)
break
time.sleep(1)
@@ -76,10 +128,8 @@ def main():
log("\n正在停止服务...", YELLOW)
finally:
# 优雅退出
if api_process:
os.killpg(os.getpgid(api_process.pid), signal.SIGTERM)
if trade_process:
os.killpg(os.getpgid(trade_process.pid), signal.SIGTERM)
terminate_process(api_process, "API 服务")
terminate_process(trade_process, "自动交易程序")
log("所有服务已停止", GREEN)
+305
View File
@@ -0,0 +1,305 @@
"""2026年1月BTC数据 - DLinear 准确率测试 (create_labels版)
与test_dlinear_jan_2026.py对比:
- 本脚本使用create_labels预处理方式(auto_trade旧逻辑)
- 每个目标K线截取到input_idx的df,调用create_labels后取X[-1]预测
- 用于验证create_labels对预测结果的影响
输入数据:预测K线的前一根(即用截止到t的数据预测t+1)
价格对比:预测结果与预测K线(t+1)的收盘价做对比
时间:币安数据UTC,输出转为北京时间(UTC+8),按北京时间分天
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import torch
import numpy as np
import pandas as pd
import pickle
import pandas_ta as ta
from datetime import timedelta
from sklearn.preprocessing import StandardScaler
from data.fetcher import BinanceDataFetcher
# ==================== 时区与美股判断 ====================
def utc_to_beijing(ts):
"""UTC时间转北京时间(UTC+8)"""
return ts + timedelta(hours=8)
def is_us_stock_open_by_utc(utc_ts):
"""根据UTC时间判断美股当天是否开盘
美股交易日基于美东时间(ET),UTC-5(冬令时)
2026年1月在冬令时范围内(夏令时3月第二个周日开始)
所以美东 = UTC - 5h
"""
et_ts = utc_ts - timedelta(hours=5)
et_date = pd.Timestamp(et_ts.date())
us_holidays_jan_2026 = {
pd.Timestamp('2026-01-01'), # 元旦 New Year's Day
pd.Timestamp('2026-01-19'), # 马丁·路德·金日 MLK Day
}
if et_date.weekday() >= 5:
return False
if et_date in us_holidays_jan_2026:
return False
return True
# ==================== pickle反序列化占位类 ====================
class ClassificationPreprocessor:
"""pickle.load时需要能找到这个类名"""
def __init__(self, lookback=168, forecast_horizon=1):
self.lookback = lookback
self.forecast_horizon = forecast_horizon
self.scaler = StandardScaler()
self.is_fitted = False
# ==================== create_labels预处理 ====================
def preprocess_with_create_labels(df, scaler, lookback=168, forecast_horizon=1):
"""使用create_labels方式预处理(auto_trade旧逻辑)
步骤:加特征 -> create_labels(shift + 砍尾) -> dropna -> scaler.transform -> 构建窗口
"""
df = df.copy()
# 加特征
df['rsi'] = ta.rsi(df['close'], length=14)
macd = ta.macd(df['close'])
if macd is not None and not macd.empty:
df['macd'] = macd.iloc[:, 0]
df['macd_hist'] = macd.iloc[:, 1]
df['macd_signal'] = macd.iloc[:, 2]
# create_labels: shift + 砍最后一行
df['future_price'] = df['close'].shift(-forecast_horizon)
df['future_return'] = (df['future_price'] - df['close']) / df['close']
df['label'] = (df['future_return'] > 0).astype(int)
df = df[:-forecast_horizon]
df = df.dropna().reset_index(drop=True)
feature_cols = ['open', 'high', 'low', 'close', 'volume',
'rsi', 'macd', 'macd_hist', 'macd_signal']
scaled_features = scaler.transform(df[feature_cols])
X = []
for i in range(len(scaled_features) - lookback):
X.append(scaled_features[i:i + lookback])
return np.array(X), df
# ==================== 工具函数 ====================
def fmt_pred(val):
"""格式化预测结果"""
if val is None or (isinstance(val, float) and np.isnan(val)):
return "N/A"
return "涨" if int(val) == 1 else "跌"
def fmt_result(pred, actual):
"""格式化对错"""
if pred is None or (isinstance(pred, float) and np.isnan(pred)):
return " "
return "✓" if int(pred) == int(actual) else "✗"
def main():
print("=" * 90)
print("2026年1月 BTC 1h 涨跌预测准确率测试 (DLinear - create_labels版)")
print("输入:预测K线的前一根 | 对比:预测结果 vs 预测K线收盘价 | 时间:北京时间(UTC+8)")
print("预处理:截取到input的df,调用create_labels后取X[-1]")
print("=" * 90)
device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
print(f"使用设备: {device}")
# ========== 步骤1: 加载模型 ==========
print("\n[1/4] 加载模型...")
from models.dlinear.model import DLinear
checkpoint = torch.load('./models/checkpoints/dlinear_classification_2026/best_model.pt',
map_location=device, weights_only=False)
model = DLinear(checkpoint['config'])
model.load_state_dict(checkpoint['model_state_dict'])
model = model.to(device).eval()
with open('./models/checkpoints/dlinear_classification_2026/preprocessor.pkl', 'rb') as f:
preprocessor = pickle.load(f)
print(" ✓ DLinear已加载")
# ========== 步骤2: 获取测试数据 ==========
print("\n[2/4] 获取数据...")
fetcher = BinanceDataFetcher()
df = fetcher.fetch_ohlcv('BTC', '1h', start_date='2025-10-01', end_date='2026-02-01', use_cache=False)
print(f"数据范围(UTC): {df['timestamp'].min()} -> {df['timestamp'].max()}")
print(f"总行数: {len(df)}")
# ========== 步骤3: 预处理与预测 ==========
print("\n[3/4] 运行预测...")
lookback = preprocessor.lookback
scaler = preprocessor.scaler
# 找出1月(北京时间)的所有预测目标K线索引
jan_targets = []
for target_idx in range(1, len(df)):
target_time_utc = df.iloc[target_idx]['timestamp']
target_time_bj = utc_to_beijing(target_time_utc)
if target_time_bj.year == 2026 and target_time_bj.month == 1:
jan_targets.append(target_idx)
print(f"1月(北京时间)目标K线数: {len(jan_targets)}")
results = []
for target_idx in jan_targets:
target_time_utc = df.iloc[target_idx]['timestamp']
target_time_bj = utc_to_beijing(target_time_utc)
# 输入K线索引(前一根)
input_idx = target_idx - 1
current_close = df.iloc[input_idx]['close']
target_close = df.iloc[target_idx]['close']
actual_label = 1 if target_close > current_close else 0
# --- DLinear 预测(create_labels方式:截取到input_idx,取X[-1]) ---
dl_pred = None
dl_prob = None
df_up_to_input = df.iloc[:input_idx + 1]
X, _ = preprocess_with_create_labels(df_up_to_input, scaler, lookback)
if len(X) > 0:
x_tensor = torch.FloatTensor(X[-1:]).to(device)
with torch.no_grad():
logits = model(x_tensor).cpu().numpy()
prob = 1 / (1 + np.exp(-logits))
dl_pred = int(prob.item() > 0.5)
dl_prob = float(prob.item())
results.append({
'time_utc': target_time_utc,
'time_bj': target_time_bj,
'current_close': current_close,
'target_close': target_close,
'actual': actual_label,
'dlinear': dl_pred,
'dl_prob': dl_prob,
})
# ========== 步骤4: 输出结果 ==========
if not results:
print("没有找到符合条件的测试样本")
return
df_res = pd.DataFrame(results)
df_res['date_bj'] = df_res['time_bj'].dt.date
df_res['us_open'] = df_res['time_utc'].apply(is_us_stock_open_by_utc)
total_samples = len(df_res)
print(f"\n[4/4] 准确率统计 (2026年1月,北京时间)")
print(f"总样本数: {total_samples}")
# ========== 每小时详细数据 ==========
print("\n" + "=" * 90)
print("每小时详细数据")
print("=" * 90)
hourly_header = (
f"{'北京时间':<18} {'前收盘':>10} {'现收盘':>10} {'实际':>4} "
f"{'DL':>3} {'':>1} {'概率':>6}"
)
for date_bj, day_group in df_res.groupby('date_bj'):
us_open = day_group['us_open'].iloc[0]
us_str = "美股开盘" if us_open else "美股休市"
dl_day_correct = (day_group['dlinear'] == day_group['actual']).sum()
dl_day_total = day_group['dlinear'].notna().sum()
dl_day_acc = dl_day_correct / dl_day_total if dl_day_total > 0 else 0
print(f"\n--- {date_bj} ({us_str}) --- DL准确率:{dl_day_acc:.1%} ({dl_day_correct}/{dl_day_total})")
print(hourly_header)
print("-" * 90)
for _, row in day_group.iterrows():
time_str = row['time_bj'].strftime('%m-%d %H:%M')
actual_str = fmt_pred(row['actual'])
dl_str = fmt_pred(row['dlinear'])
dl_r = fmt_result(row['dlinear'], row['actual'])
prob_str = f"{row['dl_prob']:.3f}" if row['dl_prob'] is not None else " N/A"
print(
f"{time_str:<18} {row['current_close']:>10.2f} {row['target_close']:>10.2f} {actual_str:>4} "
f"{dl_str:>3} {dl_r:>1} {prob_str:>6}"
)
# ========== 每日准确率汇总表 ==========
print("\n\n" + "=" * 90)
print("每日准确率汇总 (北京时间)")
print("=" * 90)
header = f"{'日期':<14} {'样本':>4} {'美股':>6} {'DLinear':>8}"
print(header)
print("-" * 90)
for date_bj, group in df_res.groupby('date_bj'):
total = len(group)
us_open = group['us_open'].iloc[0]
us_str = "开盘" if us_open else "休市"
dl_valid = group.dropna(subset=['dlinear'])
dl_acc = (dl_valid['dlinear'] == dl_valid['actual']).mean() if len(dl_valid) > 0 else 0
dl_str = f"{dl_acc:.1%}"
print(f"{str(date_bj):<14} {total:>4} {us_str:>6} {dl_str:>8}")
print("-" * 90)
# --- 总准确率 ---
dl_valid_all = df_res.dropna(subset=['dlinear'])
dl_total = (dl_valid_all['dlinear'] == dl_valid_all['actual']).mean() if len(dl_valid_all) > 0 else 0
print(f"{'总计':<14} {total_samples:>4} {'':>6} {dl_total:.2%}")
print("=" * 90)
# --- 美股开盘日 vs 休市日对比 ---
print("\n[美股开盘日 vs 休市日 准确率对比]")
print("-" * 60)
for label, flag in [("开盘日", True), ("休市日", False)]:
subset = df_res[df_res['us_open'] == flag]
if len(subset) == 0:
continue
n = len(subset)
dl_s = subset.dropna(subset=['dlinear'])
dl_a = (dl_s['dlinear'] == dl_s['actual']).mean() if len(dl_s) > 0 else 0
print(f" {label} ({n}样本): DLinear={dl_a:.2%}")
print("-" * 60)
# --- 预测分布统计 ---
print("\n[预测分布统计]")
print("-" * 60)
actual_up = (df_res['actual'] == 1).sum()
actual_down = (df_res['actual'] == 0).sum()
print(f" 实际: 上涨={actual_up} ({actual_up/total_samples:.1%}) 下跌={actual_down} ({actual_down/total_samples:.1%})")
dl_valid = df_res['dlinear'].dropna()
if len(dl_valid) > 0:
up = (dl_valid == 1).sum()
down = (dl_valid == 0).sum()
print(f" DLinear: 预测上涨={up} ({up/len(dl_valid):.1%}) 预测下跌={down} ({down/len(dl_valid):.1%})")
print("=" * 90)
if __name__ == "__main__":
main()
+11 -6
View File
@@ -38,18 +38,23 @@ class TrainingConfig:
Returns:
Device string ('mps', 'cuda', or 'cpu')
Note:
macOS 使用 MPS,Windows/Linux 使用 CPU
"""
import torch
import platform
if self.device != 'auto':
return self.device
# Auto-detect best available device
if torch.backends.mps.is_available():
return 'mps'
elif torch.cuda.is_available():
return 'cuda'
else:
# 根据操作系统选择设备:macOS 用 MPS,Windows/Linux 用 CPU
if platform.system() == 'Darwin': # macOS
if torch.backends.mps.is_available():
return 'mps'
else:
return 'cpu'
else: # Windows / Linux
return 'cpu'