diff --git a/management/cicd/README.md b/management/cicd/README.md new file mode 100644 index 000000000..bffa61397 --- /dev/null +++ b/management/cicd/README.md @@ -0,0 +1,155 @@ +# sanguo_quant_live CI/CD 自动化回测 + +## 📋 概述 + +这是 sanguo_quant_live 全自动化回测 CI/CD 流程,**全程零干预,一条命令搞定**。 + +--- + +## 👥 明确职责分工 + +| 角色 | 你是谁 | 你需要做什么 | 你永远不需要做什么 | +|------|------|-------------|-----------------| +| **各位将军** | 策略开发者 | 写策略 / 改策略 → 一条命令触发回测 → 看结果 | **永远不要碰 vnpy 框架**,永远不要执行 `sanguo_nas_ci_cd.sh` | +| **姜维** | CI/CD 负责人 / vnpy 维护者 | 维护 vnpy 框架 / 修复 bug / 新增功能 → 触发完整重建 | 不需要写策略 | + +> 💡 **记住:** +> - 如果你是将军写策略,你永远只用 `./sync_and_redeploy.sh` +> - 如果你是姜维维护框架,你才用 `./sanguo_nas_ci_cd.sh` + +--- + +## 🚀 使用方法(给各位将军) + +### 📝 你写完策略/改完策略,要回测了: + +```bash +# 记住!你只用这个命令! +cd management/cicd +./sync_and_redeploy.sh +``` + +**全自动完成:** +1. ✅ 同步你本地最新策略代码到 NAS +2. ✅ 重启 Docker 容器 +3. ✅ 自动运行回测 +4. ✅ 自动保存结果 +5. ✅ 输出回测统计结果 + +**全程不用你管,等着看结果就行!** 🎉 + +--- + +## 🚀 使用方法(给姜维) + +### 🔧 你修改了 vnpy 框架/修复了 bug/新增了功能,需要重建环境: + +```bash +# 只有姜维能用这个命令! +cd management/cicd +./sanguo_nas_ci_cd.sh +``` + +**全自动完成:** +1. ✅ 同步本地最新完整代码(包括 vnpy 框架)到 NAS +2. ✅ 停止旧容器 +3. ✅ 完全重新构建 Docker 镜像(包含所有修改) +4. ✅ 启动新容器 +5. ✅ 运行回测验证 +6. ✅ 保存结果 + +--- + +## 🛟 特殊情况:rsync 同步有权限问题 + +```bash +cd management/cicd +./sync_with_tar.sh +``` + +--- + +## 📊 回测结果在哪里 + +结果自动保存到**两个地方**: + +1. **全局汇总**: + - NAS: `/volume1/stock/sanguo_vnpy/backtest_results/` + - 本地: `./backtest_results/` + +2. **和策略放在一起(便于查看)**: + - `./strategies/{your_strategy}/backtest_results/` + - 结果和策略代码放在一起,找起来方便! + +每个回测生成两个文件: +- `{strategy}_{YYYYMMDD_HHMMSS}.txt` - 回测统计结果(年化收益、夏普比率、最大回撤、胜率等) +- `{strategy}_{YYYYMMDD_HHMMSS}.png` - 资金曲线图 + +--- + +## 🔄 完整工作流 + +``` +1. 各位将军开发 + ↓ + ✍️ 在本地 sanguo_quant_live 写策略 / 改策略 + ↓ + 🚀 触发回测(记住只用这个): + cd management/cicd + ./sync_and_redeploy.sh + ↓ + 🔄 自动化执行 + ✓ 同步最新策略到 NAS + ✓ 重启容器 + ✓ 运行回测 + ✓ 保存结果(全局一份 + 策略目录一份) + ↓ + 👀 各位将军看结果 + 去你的策略目录下 backtest_results/ 看结果 +``` + +--- + +## 🛠️ 维护流程(给姜维) + +``` +1. 发现 vnpy 框架有 bug / 需要新增功能 + ↓ + 🔧 在本地 sanguo_vnpy 修改代码 + ↓ + 🚀 触发完整重建: + cd management/cicd + ./sanguo_nas_ci_cd.sh + ↓ + 🔄 自动化执行 + ✓ 同步完整代码到 NAS + ✓ 重建 Docker 镜像 + ✓ 启动新容器 + ↓ + ✅ 完成,各位将军继续用 +``` + +--- + +## 📝 配置信息 + +- **NAS 地址:** `192.168.2.154` +- **NAS 目标目录:** `/volume1/stock/sanguo_vnpy/` +- **本地项目根目录:** `/Users/chufeng/.openclaw/sanguo_projects/sanguo_quant_live/` + +--- + +## ✨ 方案特点 + +- 🤖 **全程零干预** - 你触发后全自动,不需要任何手动操作 +- ⚡ **增量更新** - 策略修改不用重建 Docker,更快出结果 +- 🔒 **职责隔离** - 将军只管策略,姜维只管框架,互不干扰 +- 📂 **结果好查找** - 结果同时保存到全局和策略目录,方便查看 +- 📊 **结果自动保存** - 统计结果 + 资金曲线图都自动保存 +- 🎯 **分工清晰** - 每个人只干自己擅长的,命令记一个就行 + +--- + +**版本:** 1.0 +**最后更新:** 2026-03-28 +**维护负责人:** 姜维 diff --git a/management/cicd/auto_backtest_crontab.sh b/management/cicd/auto_backtest_crontab.sh new file mode 100755 index 000000000..b8cd6a937 --- /dev/null +++ b/management/cicd/auto_backtest_crontab.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +# ============================================ +# 配置定时自动回测 +# 安装:./auto_backtest_crontab.sh install +# 查看:crontab -l +# 卸载:./auto_backtest_crontab.sh uninstall +# ============================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CI_CD_SCRIPT="$SCRIPT_DIR/sanguo_nas_ci_cd.sh" +CRON_ENTRY="0 8 * * * $CI_CD_SCRIPT >> $SCRIPT_DIR/auto_backtest.log 2>&1" + +install() { + echo "安装定时自动回测..." + echo "" + + # 添加到 crontab + (crontab -l 2>/dev/null; echo "$CRON_ENTRY") | crontab - + + echo "" + echo "✅ 安装完成!" + echo "每天早上 8:00 自动运行一次全流程回测" + echo "日志位置: $SCRIPT_DIR/auto_backtest.log" + echo "" +} + +uninstall() { + echo "卸载定时自动回测..." + echo "" + + # 移除 crontab + crontab -l 2>/dev/null | grep -v "sanguo_nas_ci_cd.sh" | crontab - + + echo "" + echo "✅ 卸载完成!" + echo "" +} + +status() { + echo "当前定时任务:" + echo "" + crontab -l | grep "sanguo" + echo "" +} + +case "${1:-help}" in + install) + install + ;; + uninstall) + uninstall + ;; + status) + status + ;; + help) + echo "使用方法:" + echo " $0 install - 安装每天 8:00 自动回测" + echo " $0 uninstall - 卸载定时回测" + echo " $0 status - 查看当前状态" + echo "" + ;; +esac diff --git a/management/cicd/run_backtest_auto.py b/management/cicd/run_backtest_auto.py new file mode 100755 index 000000000..39c70a45e --- /dev/null +++ b/management/cicd/run_backtest_auto.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +通用自动化回测脚本 +自动保存结果到: +1. 全局目录: /volume1/stock/sanguo_vnpy/backtest_results/ +2. 策略目录: ./strategies/{your_strategy}/backtest_results/ 便于和策略放在一起查看 +""" + +import sys +import os +import argparse +from datetime import datetime +from pathlib import Path + +# 添加路径 +sys.path.insert(0, '/volume1/stock/sanguo_vnpy') +sys.path.insert(0, '/volume1/stock/sanguo_vnpy/strategies') + +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt + +from vnpy.trader.constant import Interval +from vnpy_ctabacktester import BacktesterEngine + + +def run_backtest( + strategy_class, + strategy_name: str, + vt_symbol: str = "IF888.CFFEX", + interval: Interval = Interval.DAILY, + start_date: str = "20200101", + end_date: str = "20251231", + capital: int = 1000000, + rate: float = 0.3 / 10000, + slippage: float = 0.2, + size: int = 300, + pricetick: float = 0.2, + strategy_config: dict = None, +): + """运行单个策略回测""" + + # 创建结果保存目录 + # 1. 全局目录 + GLOBAL_RESULT_DIR = Path('/volume1/stock/sanguo_vnpy/backtest_results') + GLOBAL_RESULT_DIR.mkdir(exist_ok=True, parents=True) + + # 2. 策略目录(和策略放在一起便于查看) + # 找到策略目录 + strategy_file = sys.modules[strategy_class.__module__].__file__ + strategy_dir = Path(strategy_file).parent + STRATEGY_RESULT_DIR = strategy_dir / "backtest_results" + STRATEGY_RESULT_DIR.mkdir(exist_ok=True, parents=True) + + print("=" * 60) + print(f" 开始回测: {strategy_name}") + print(f" 结果保存:") + print(f" 全局: {GLOBAL_RESULT_DIR}") + print(f" 策略: {STRATEGY_RESULT_DIR}") + print("=" * 60) + print() + + # 打印配置 + print(f"📋 回测配置:") + print(f" 标的: {vt_symbol}") + print(f" 周期: {interval}") + print(f" 时间: {start_date} - {end_date}") + print(f" 初始资金: {capital:,}") + print() + + # 创建回测引擎 + print("🚀 创建回测引擎...") + engine = BacktesterEngine() + + # 添加策略 + print("🧩 添加策略...") + if strategy_config is None: + strategy_config = {} + engine.add_strategy(strategy_class, strategy_config) + + # 运行回测 + print("🔄 开始运行回测...") + start_time = datetime.now() + + engine.run_backtest( + vt_symbol=vt_symbol, + interval=interval, + start=start_date, + end=end_date, + rate=rate, + slippage=slippage, + size=size, + pricetick=pricetick, + capital=capital + ) + + end_time = datetime.now() + duration = (end_time - start_time).total_seconds() + + print(f"✅ 回测完成!耗时: {duration:.2f} 秒") + print() + + # 获取结果文本 + import io + from contextlib import redirect_stdout + + output = io.StringIO() + with redirect_stdout(output): + engine.show_results() + result_text = output.getvalue() + + # 保存结果到全局目录 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + global_result_file = GLOBAL_RESULT_DIR / f"{strategy_name}_{timestamp}.txt" + + with open(global_result_file, 'w', encoding='utf-8') as f: + f.write(f"{strategy_name} 回测结果\n") + f.write("=" * 50 + "\n") + f.write(f"时间: {timestamp}\n") + f.write(f"标的: {vt_symbol}\n") + f.write(f"周期: {interval}\n") + f.write(f"时间范围: {start_date} - {end_date}\n") + f.write(f"初始资金: {capital:,}\n") + f.write("\n") + f.write(result_text) + + print(f"💾 全局结果已保存: {global_result_file}") + + # 保存结果到策略目录 + strategy_result_file = STRATEGY_RESULT_DIR / f"{strategy_name}_{timestamp}.txt" + with open(strategy_result_file, 'w', encoding='utf-8') as f: + f.write(f"{strategy_name} 回测结果\n") + f.write("=" * 50 + "\n") + f.write(f"时间: {timestamp}\n") + f.write(f"标的: {vt_symbol}\n") + f.write(f"周期: {interval}\n") + f.write(f"时间范围: {start_date} - {end_date}\n") + f.write(f"初始资金: {capital:,}\n") + f.write("\n") + f.write(result_text) + + print(f"📂 策略结果已保存: {strategy_result_file}") + print() + + # 保存图表 + print("📈 绘制资金曲线...") + plt.figure(figsize=(12, 6)) + engine.plot_chart() + + # 保存到全局目录 + global_chart_file = GLOBAL_RESULT_DIR / f"{strategy_name}_{timestamp}.png" + plt.savefig(global_chart_file) + print(f"📊 全局图表已保存: {global_chart_file}") + + # 保存到策略目录 + strategy_chart_file = STRATEGY_RESULT_DIR / f"{strategy_name}_{timestamp}.png" + plt.savefig(strategy_chart_file) + print(f"📊 策略图表已保存: {strategy_chart_file}") + + print() + print("=" * 60) + print(f" 🎉 回测完成!") + print(f" 📄 全局结果: {global_result_file}") + print(f" 📂 策略结果: {strategy_result_file}") + print("=" * 60) + + return global_result_file, strategy_result_file + + +def main(): + """主函数""" + + parser = argparse.ArgumentParser(description='自动化运行策略回测') + parser.add_argument('strategy', help='策略模块名,例如: strategies.guanyu_value_tech_strategy') + parser.add_argument('--symbol', default='IF888.CFFEX', help='回测标的') + parser.add_argument('--start', default='20200101', help='开始日期') + parser.add_argument('--end', default='20251231', help='结束日期') + parser.add_argument('--capital', type=int, default=1000000, help='初始资金') + + args = parser.parse_args() + + # 导入策略模块 + import importlib + module = importlib.import_module(args.strategy) + + # 找到策略类(假设第一个类就是策略类) + strategy_class = None + for name, obj in module.__dict__.items(): + if isinstance(obj, type) and 'Strategy' in name: + strategy_class = obj + break + + if strategy_class is None: + print(f"❌ 在模块 {args.strategy} 中没找到策略类") + sys.exit(1) + + strategy_name = strategy_class.__name__ + + # 获取策略配置(如果有) + strategy_config = {} + if hasattr(module, 'STRATEGY_CONFIG'): + strategy_config = module.STRATEGY_CONFIG + + # 运行回测 + run_backtest( + strategy_class=strategy_class, + strategy_name=strategy_name, + vt_symbol=args.symbol, + start_date=args.start, + end_date=args.end, + capital=args.capital, + strategy_config=strategy_config, + ) + + +if __name__ == "__main__": + main() diff --git a/management/cicd/sanguo_nas_ci_cd.sh b/management/cicd/sanguo_nas_ci_cd.sh new file mode 100755 index 000000000..cc84d2678 --- /dev/null +++ b/management/cicd/sanguo_nas_ci_cd.sh @@ -0,0 +1,209 @@ +#!/bin/bash + +# ============================================ +# sanguo_vnpy 端到端全自动化 CI/CD 回测 +# 作者:姜维 伯约 +# 日期:2026年3月28日 +# ============================================ + +set -e + +# 配置信息 +NAS_IP="192.168.2.154" +NAS_USER="admin" +SANGUO_PROJECT_DIR="/Users/chufeng/.openclaw/sanguo_projects/sanguo_quant_live" +NAS_TARGET_DIR="/volume1/stock/sanguo_vnpy" +DOCKER_CONTAINER_NAME="sanguo_vnpy" + +# 说明: +# 这个方案会同步整个 sanguo_quant_live 项目,包括: +# - strategies/ 所有策略 +# - sanguo_vnpy/ 框架代码(包括修改后的vnpy) +# - jiangwei-platform/ 三国量化实战项目 +# - pangtong-value/ 价值投资研究 +# - 所有其他模块 + +# 颜色 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 步骤 1: 拉取最新代码 +pull_latest_code() { + log_info "步骤 1/6: 拉取最新代码" + + cd "$SANGUO_PROJECT_DIR" + + if git pull origin main; then + log_info "✅ 代码已更新到最新版本" + return 0 + else + log_error "❌ 拉取代码失败" + return 1 + fi +} + +# 步骤 2: 同步所有文件到 NAS +sync_to_nas() { + log_info "步骤 2/6: 同步所有文件到 NAS" + + # 确保目标目录存在 + ssh "$NAS_USER@$NAS_IP" "mkdir -p $NAS_TARGET_DIR" + + # 使用 rsync 同步所有文件(包括策略和框架代码) + # 会同步整个 sanguo_quant_live 项目,包括: + # - strategies/ 所有策略 + # - sanguo_vnpy/ 框架代码(包括您修改后的 vnpy) + # - jiangwei-platform/ 三国量化实战 + # - pangtong-value/ 价值投资研究 + # - 所有其他模块 + rsync -av --delete \ + "$SANGUO_PROJECT_DIR"/ \ + "$NAS_USER@$NAS_IP:$NAS_TARGET_DIR"/ \ + --exclude='.git' \ + --exclude='__pycache__' \ + --exclude='*.log' \ + --exclude='*.pyc' \ + --exclude='.ipynb_checkpoints' \ + --exclude='backtest_results/*.png' \ + --exclude='.DS_Store' + + log_info "✅ 文件同步完成" +} + +# 步骤 3: 重建 Docker 容器 +rebuild_docker() { + log_info "步骤 3/6: 重建 Docker 容器" + + ssh "$NAS_USER@$NAS_IP" << 'EOF' +export PATH=$PATH:/var/packages/Docker/target/usr/bin +cd /volume1/stock/sanguo_vnpy/docker + +# 停止并删除旧容器 +if docker ps -a | grep -q sanguo_vnpy; then + docker stop sanguo_vnpy + docker rm sanguo_vnpy +fi + +# 构建新镜像 +docker-compose build --no-cache + +# 启动新容器 +docker-compose up -d + +# 等待容器启动 +sleep 10 + +# 检查容器状态 +if docker ps | grep -q sanguo_vnpy; then + echo "✅ 容器启动成功" +else + echo "❌ 容器启动失败" + exit 1 +fi +EOF + + if [ $? -eq 0 ]; then + log_info "✅ Docker 容器重建完成" + return 0 + else + log_error "❌ Docker 容器重建失败" + return 1 + fi +} + +# 步骤 4: 运行自动化回测 +run_backtest() { + log_info "步骤 4/6: 运行自动化回测" + + ssh "$NAS_USER@$NAS_IP" << 'EOF' +cd /volume1/stock/sanguo_vnpy/scripts + +# 运行全自动化回测 +./run_backtest_auto.sh +EOF + + if [ $? -eq -ne 0 ]; then + log_warn "回测运行中,请等待完成..." + fi + + log_info "✅ 回测已启动" +} + +# 步骤 5: 获取结果 +get_results() { + log_info "步骤 5/6: 获取回测结果" + + # 创建本地结果目录 + mkdir -p "./backtest_results" + + # 下载最新结果 + rsync -av --delete \ + "$NAS_USER@$NAS_IP:$NAS_TARGET_DIR/backtest_results/"* \ + "./backtest_results"/ + + log_info "✅ 结果已下载到本地: ./backtest_results/" + + # 显示最新结果 + LATEST_RESULT=$(ls -t ./backtest_results/*.txt | head -1) + if [ -f "$LATEST_RESULT" ]; then + echo "" + echo "============================================" + echo " 📊 最新回测结果" + echo "============================================" + echo "" + cat "$LATEST_RESULT" + echo "" + echo "============================================" + fi +} + +# 步骤 6: 完成通知 +finish() { + log_info "步骤 6/6: 完成!" + + echo "" + echo "╔═════════════════════════════════════════════════════════════════╗" + echo "║ 🎉 端到端自动化回测完成! ║" + echo "╚═════════════════════════════════════════════════════════════════╝" + echo "" + echo "📍 结果位置:" + echo " NAS: $NAS_TARGET_DIR/backtest_results/" + echo " 本地: ./backtest_results/" + echo "" + echo "🚀 下次更新代码后,直接运行 ./sanguo_nas_ci_cd.sh 就能全自动完成!" + echo "" +} + +# 主函数 +main() { + echo "============================================" + echo " sanguo_vnpy 端到端全自动化回测" + echo "============================================" + echo "" + + pull_latest_code && \ + sync_to_nas && \ + rebuild_docker && \ + run_backtest && \ + get_results && \ + finish + + echo "" +} + +# 运行 +main diff --git a/management/cicd/sync_and_redeploy.sh b/management/cicd/sync_and_redeploy.sh new file mode 100755 index 000000000..2687ac7f9 --- /dev/null +++ b/management/cicd/sync_and_redeploy.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +# ============================================ +# 增量同步代码到 NAS 并重新部署 +# 当修复 bug 后,运行这个脚本自动同步并重新部署 +# ============================================ + +set -e + +# 配置信息 +NAS_IP="192.168.2.154" +NAS_USER="admin" +LOCAL_PROJECT="/Users/chufeng/.openclaw/sanguo_projects/sanguo_quant_live" +NAS_TARGET="/volume1/stock/sanguo_vnpy" + +# 颜色 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +echo "============================================" +echo " 增量同步 + 自动重新部署" +echo "============================================" +echo "" + +# 拉取最新代码 +log_info "1/4: 拉取最新代码" +cd "$LOCAL_PROJECT" +git pull origin main +log_info "✅ 代码已更新" +echo "" + +# 增量同步到 NAS +log_info "2/4: 增量同步到 NAS" +# 同步整个项目,包括策略和框架代码 +# 任何文件修改(包括 vnpy 框架代码)都会同步到 NAS +rsync -av --delete \ + "$LOCAL_PROJECT"/ \ + "$NAS_USER@$NAS_IP:$NAS_TARGET"/ \ + --exclude='.git' \ + --exclude='__pycache__' \ + --exclude='*.log' \ + --exclude='*.pyc' \ + --exclude='.ipynb_checkpoints' \ + --exclude='backtest_results/*.png' \ + --exclude='.DS_Store' + +log_info "✅ 同步完成" +echo "" + +# 重启 Docker 容器 +log_info "3/4: 重启 Docker 容器" +ssh "$NAS_USER@$NAS_IP" << 'EOF' +cd /volume1/stock/sanguo_vnpy + +# 添加 docker 到 PATH +export PATH=$PATH:/var/packages/Docker/target/usr/bin + +# 停止旧容器 +if docker ps -q -f name=sanguo_vnpy | grep -q .; then + docker stop sanguo_vnpy +fi + +# 重新构建启动 +/var/packages/Docker/target/usr/bin/docker-compose up -d --build + +# 等待启动 +sleep 10 + +# 检查状态 +if docker ps -q -f name=sanguo_vnpy | grep -q .; then + echo "✅ 容器重启成功" +else + echo "❌ 容器启动失败" + exit 1 +fi +EOF + +log_info "✅ 容器重启完成" +echo "" + +# 运行回测 +log_info "4/4: 启动自动化回测" +ssh "$NAS_USER@$NAS_IP" "cd /volume1/stock/sanguo_vnpy/scripts && ./run_backtest_auto.sh" + +echo "" +log_info "🎉 完成!代码已同步,容器已重启,回测已启动!" +echo "" diff --git a/management/cicd/sync_and_run_backtest.sh b/management/cicd/sync_and_run_backtest.sh new file mode 100755 index 000000000..37b2c0e4a --- /dev/null +++ b/management/cicd/sync_and_run_backtest.sh @@ -0,0 +1,122 @@ +#!/bin/bash + +# ============================================ +# 同步策略 + 全自动回测,不需要重启 Docker! +# 使用方法: +# ./sync_and_run_backtest.sh --strategy strategies.pangtong-value.strategy_A +# ============================================ + +set -e + +# 配置信息 +NAS_IP="192.168.2.154" +NAS_USER="admin" +LOCAL_PROJECT="/Users/chufeng/.openclaw/sanguo_projects/sanguo_quant_live" +NAS_TARGET="/volume1/stock/sanguo_vnpy" +DOCKER_CONTAINER="sanguo_vnpy" + +# 颜色 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 解析参数 +while [[ $# -gt 0 ]]; do + case $1 in + --strategy) + STRATEGY="$2" + shift + shift + ;; + *) + shift + ;; + esac +done + +# 检查参数 +if [[ -z "$STRATEGY" ]]; then + log_error "必须指定 --strategy 参数" + echo "" + echo "使用方法:" + echo " $0 --strategy strategies.pangtong-value.strategy_A" + echo "" + exit 1 +fi + +# 提取策略路径(用于同步) +# strategies.pangtong-value.strategy_A → strategies/pangtong-value +STRATEGY_PATH=$(echo "$STRATEGY" | tr '.' '/') +STRATEGY_PATH=$(dirname "$STRATEGY_PATH") + +echo "============================================" +echo " 同步策略 + 全自动回测(无需重启 Docker)" +echo "============================================" +echo "" +log_info "策略: $STRATEGY" +log_info "策略路径: $STRATEGY_PATH" +echo "" + +# 第一步:同步策略代码到 NAS +log_info "1/3: 同步策略代码到 NAS" +rsync -av --delete \ + "$LOCAL_PROJECT/$STRATEGY_PATH"/ \ + "$NAS_USER@$NAS_IP:$NAS_TARGET/$STRATEGY_PATH"/ \ + --exclude='__pycache__' \ + --exclude='*.pyc' \ + --exclude='.ipynb_checkpoints' \ + --exclude='.DS_Store' + +log_info "✅ 同步完成" +echo "" + +# 第二步:在容器中运行回测 +log_info "2/3: 在运行的容器中执行回测" +echo "" + +ssh "$NAS_USER@$NAS_IP" "export PATH=\$PATH:/var/packages/Docker/target/usr/bin && docker exec $DOCKER_CONTAINER python /app/scripts/run_backtest_auto.py $STRATEGY" + +log_info "✅ 回测执行完成" +echo "" + +# 第三步:同步回测结果回本地 +log_info "3/3: 同步回测结果回本地" +echo "" + +# 结果目录 +RESULT_PATH="$STRATEGY_PATH/backtest_results" +mkdir -p "$LOCAL_PROJECT/$RESULT_PATH" + +rsync -av \ + "$NAS_USER@$NAS_IP:$NAS_TARGET/$RESULT_PATH"/ \ + "$LOCAL_PROJECT/$RESULT_PATH"/ \ + --exclude='__pycache__' + +log_info "✅ 结果同步完成" +echo "" + +echo "============================================" +echo " 🎉 回测完成!" +echo "============================================" +echo "" +echo "📄 结果位置:" +echo " NAS: $NAS_TARGET/$RESULT_PATH/" +echo " 本地: $LOCAL_PROJECT/$RESULT_PATH/" +echo "" +echo "📊 直接查看结果:" +echo " cat $LOCAL_PROJECT/$RESULT_PATH/*.txt" +echo " open $LOCAL_PROJECT/$RESULT_PATH/*.png" +echo "" diff --git a/management/cicd/sync_strategy_only.sh b/management/cicd/sync_strategy_only.sh new file mode 100755 index 000000000..3cf2914bf --- /dev/null +++ b/management/cicd/sync_strategy_only.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# ============================================ +# 只同步策略,不需要重启 Docker +# vnpy 支持动态加载策略,所以同步完成后直接就能在 UI 里加载新策略 +# ============================================ + +set -e + +# 配置信息 +NAS_IP="192.168.2.154" +NAS_USER="admin" +LOCAL_PROJECT="/Users/chufeng/.openclaw/sanguo_projects/sanguo_quant_live" +NAS_TARGET="/volume1/stock/sanguo_vnpy" + +# 颜色 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +echo "============================================" +echo " 同步策略到 NAS(不重启 Docker)" +echo " vnpy 支持动态加载,同步完直接在 UI 加载新策略" +echo "============================================" +echo "" + +# 检查参数 +if [ $# -eq 0 ]; then + log_error "请指定要同步的策略目录" + echo "" + echo "使用方法:" + echo " ./sync_strategy_only.sh strategies/pangtong-value/strategy_A" + echo "" + exit 1 +fi + +STRATEGY_PATH="$1" + +log_info "1/2: 同步策略 $STRATEGY_PATH 到 NAS" +rsync -av --delete \ + "$LOCAL_PROJECT/$STRATEGY_PATH"/ \ + "$NAS_USER@$NAS_IP:$NAS_TARGET/$STRATEGY_PATH"/ \ + --exclude='__pycache__' \ + --exclude='*.pyc' \ + --exclude='.ipynb_checkpoints' \ + --exclude='.DS_Store' + +log_info "✅ 同步完成" +echo "" +log_info "2/2: 完成!" +echo "" +echo "🎯 下一步:" +echo " 1. 在 vnpy UI 里刷新策略" +echo " 2. 加载你的策略 $STRATEGY_PATH" +echo " 3. 运行回测" +echo " 4. 结果自动保存到 $STRATEGY_PATH/backtest_results/" +echo "" +echo "✅ 完成!不需要重启 Docker!直接用就行!" +echo "" diff --git a/management/cicd/sync_with_tar.sh b/management/cicd/sync_with_tar.sh new file mode 100755 index 000000000..9055e5038 --- /dev/null +++ b/management/cicd/sync_with_tar.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# ============================================ +# 使用 tar 打包方式同步文件到 NAS +# 绕过 rsync 权限问题 +# ============================================ + +set -e + +# 配置信息 +NAS_IP="192.168.2.154" +NAS_USER="admin" +LOCAL_PROJECT="/Users/chufeng/.openclaw/sanguo_projects/sanguo_quant_live" +NAS_TARGET="/volume1/stock/sanguo_vnpy" + +# 颜色 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +echo "============================================" +echo " 使用 tar 打包方式同步文件到 NAS" +echo "============================================" +echo "" + +cd "$LOCAL_PROJECT" + +log_info "1/3: 打包本地项目" +# 打包排除一些文件 +tar -czf /tmp/sanguo_quant_live.tar.gz \ + --exclude='.git' \ + --exclude='__pycache__' \ + --exclude='*.log' \ + --exclude='*.pyc' \ + --exclude='.ipynb_checkpoints' \ + --exclude='backtest_results/*.png' \ + --exclude='.DS_Store' \ + . + +log_info "✅ 打包完成" +echo "" + +log_info "2/3: 上传并解压到 NAS" +# 上传并解压 +cat /tmp/sanguo_quant_live.tar.gz | ssh "$NAS_USER@$NAS_IP" "cd '$NAS_TARGET' && tar -xzf - && echo '✅ 解压完成'" + +log_info "✅ 同步完成" +echo "" + +log_info "3/3: 清理本地临时文件" +rm /tmp/sanguo_quant_live.tar.gz +log_info "✅ 清理完成" +echo "" + +log_info "🎉 文件同步完成!" +echo "" diff --git a/management/sanguo_auto_sync/watcher.pid b/management/sanguo_auto_sync/watcher.pid index 300a982e6..844039bc7 100644 --- a/management/sanguo_auto_sync/watcher.pid +++ b/management/sanguo_auto_sync/watcher.pid @@ -1 +1 @@ -35771 +5279 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..4df2774ec --- /dev/null +++ b/requirements.txt @@ -0,0 +1,28 @@ +vnpy>=4.0.0 +vnpy_ctabacktester +vnpy_ctastrategy +vnpy_datamanager +vnpy_datarecorder +vnpy_rpcservice +vnpy_webtrader +vnpy_sqlite + +pandas>=2.0.0 +numpy>=1.24.0 +scipy>=1.10.0 + +matplotlib>=3.7.0 +seaborn>=0.12.0 +plotly>=5.14.0 + +scikit-learn>=1.3.0 +lightgbm>=4.0.0 +xgboost>=2.0.0 + +TA-Lib>=0.4.28 + +python-dotenv>=1.0.0 +requests>=2.31.0 +aiohttp>=3.8.0 +websockets>=11.0.0 +pytest>=7.4.0 diff --git a/scripts/run_backtest_auto.py b/scripts/run_backtest_auto.py new file mode 100755 index 000000000..39c70a45e --- /dev/null +++ b/scripts/run_backtest_auto.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +通用自动化回测脚本 +自动保存结果到: +1. 全局目录: /volume1/stock/sanguo_vnpy/backtest_results/ +2. 策略目录: ./strategies/{your_strategy}/backtest_results/ 便于和策略放在一起查看 +""" + +import sys +import os +import argparse +from datetime import datetime +from pathlib import Path + +# 添加路径 +sys.path.insert(0, '/volume1/stock/sanguo_vnpy') +sys.path.insert(0, '/volume1/stock/sanguo_vnpy/strategies') + +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt + +from vnpy.trader.constant import Interval +from vnpy_ctabacktester import BacktesterEngine + + +def run_backtest( + strategy_class, + strategy_name: str, + vt_symbol: str = "IF888.CFFEX", + interval: Interval = Interval.DAILY, + start_date: str = "20200101", + end_date: str = "20251231", + capital: int = 1000000, + rate: float = 0.3 / 10000, + slippage: float = 0.2, + size: int = 300, + pricetick: float = 0.2, + strategy_config: dict = None, +): + """运行单个策略回测""" + + # 创建结果保存目录 + # 1. 全局目录 + GLOBAL_RESULT_DIR = Path('/volume1/stock/sanguo_vnpy/backtest_results') + GLOBAL_RESULT_DIR.mkdir(exist_ok=True, parents=True) + + # 2. 策略目录(和策略放在一起便于查看) + # 找到策略目录 + strategy_file = sys.modules[strategy_class.__module__].__file__ + strategy_dir = Path(strategy_file).parent + STRATEGY_RESULT_DIR = strategy_dir / "backtest_results" + STRATEGY_RESULT_DIR.mkdir(exist_ok=True, parents=True) + + print("=" * 60) + print(f" 开始回测: {strategy_name}") + print(f" 结果保存:") + print(f" 全局: {GLOBAL_RESULT_DIR}") + print(f" 策略: {STRATEGY_RESULT_DIR}") + print("=" * 60) + print() + + # 打印配置 + print(f"📋 回测配置:") + print(f" 标的: {vt_symbol}") + print(f" 周期: {interval}") + print(f" 时间: {start_date} - {end_date}") + print(f" 初始资金: {capital:,}") + print() + + # 创建回测引擎 + print("🚀 创建回测引擎...") + engine = BacktesterEngine() + + # 添加策略 + print("🧩 添加策略...") + if strategy_config is None: + strategy_config = {} + engine.add_strategy(strategy_class, strategy_config) + + # 运行回测 + print("🔄 开始运行回测...") + start_time = datetime.now() + + engine.run_backtest( + vt_symbol=vt_symbol, + interval=interval, + start=start_date, + end=end_date, + rate=rate, + slippage=slippage, + size=size, + pricetick=pricetick, + capital=capital + ) + + end_time = datetime.now() + duration = (end_time - start_time).total_seconds() + + print(f"✅ 回测完成!耗时: {duration:.2f} 秒") + print() + + # 获取结果文本 + import io + from contextlib import redirect_stdout + + output = io.StringIO() + with redirect_stdout(output): + engine.show_results() + result_text = output.getvalue() + + # 保存结果到全局目录 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + global_result_file = GLOBAL_RESULT_DIR / f"{strategy_name}_{timestamp}.txt" + + with open(global_result_file, 'w', encoding='utf-8') as f: + f.write(f"{strategy_name} 回测结果\n") + f.write("=" * 50 + "\n") + f.write(f"时间: {timestamp}\n") + f.write(f"标的: {vt_symbol}\n") + f.write(f"周期: {interval}\n") + f.write(f"时间范围: {start_date} - {end_date}\n") + f.write(f"初始资金: {capital:,}\n") + f.write("\n") + f.write(result_text) + + print(f"💾 全局结果已保存: {global_result_file}") + + # 保存结果到策略目录 + strategy_result_file = STRATEGY_RESULT_DIR / f"{strategy_name}_{timestamp}.txt" + with open(strategy_result_file, 'w', encoding='utf-8') as f: + f.write(f"{strategy_name} 回测结果\n") + f.write("=" * 50 + "\n") + f.write(f"时间: {timestamp}\n") + f.write(f"标的: {vt_symbol}\n") + f.write(f"周期: {interval}\n") + f.write(f"时间范围: {start_date} - {end_date}\n") + f.write(f"初始资金: {capital:,}\n") + f.write("\n") + f.write(result_text) + + print(f"📂 策略结果已保存: {strategy_result_file}") + print() + + # 保存图表 + print("📈 绘制资金曲线...") + plt.figure(figsize=(12, 6)) + engine.plot_chart() + + # 保存到全局目录 + global_chart_file = GLOBAL_RESULT_DIR / f"{strategy_name}_{timestamp}.png" + plt.savefig(global_chart_file) + print(f"📊 全局图表已保存: {global_chart_file}") + + # 保存到策略目录 + strategy_chart_file = STRATEGY_RESULT_DIR / f"{strategy_name}_{timestamp}.png" + plt.savefig(strategy_chart_file) + print(f"📊 策略图表已保存: {strategy_chart_file}") + + print() + print("=" * 60) + print(f" 🎉 回测完成!") + print(f" 📄 全局结果: {global_result_file}") + print(f" 📂 策略结果: {strategy_result_file}") + print("=" * 60) + + return global_result_file, strategy_result_file + + +def main(): + """主函数""" + + parser = argparse.ArgumentParser(description='自动化运行策略回测') + parser.add_argument('strategy', help='策略模块名,例如: strategies.guanyu_value_tech_strategy') + parser.add_argument('--symbol', default='IF888.CFFEX', help='回测标的') + parser.add_argument('--start', default='20200101', help='开始日期') + parser.add_argument('--end', default='20251231', help='结束日期') + parser.add_argument('--capital', type=int, default=1000000, help='初始资金') + + args = parser.parse_args() + + # 导入策略模块 + import importlib + module = importlib.import_module(args.strategy) + + # 找到策略类(假设第一个类就是策略类) + strategy_class = None + for name, obj in module.__dict__.items(): + if isinstance(obj, type) and 'Strategy' in name: + strategy_class = obj + break + + if strategy_class is None: + print(f"❌ 在模块 {args.strategy} 中没找到策略类") + sys.exit(1) + + strategy_name = strategy_class.__name__ + + # 获取策略配置(如果有) + strategy_config = {} + if hasattr(module, 'STRATEGY_CONFIG'): + strategy_config = module.STRATEGY_CONFIG + + # 运行回测 + run_backtest( + strategy_class=strategy_class, + strategy_name=strategy_name, + vt_symbol=args.symbol, + start_date=args.start, + end_date=args.end, + capital=args.capital, + strategy_config=strategy_config, + ) + + +if __name__ == "__main__": + main() diff --git a/zhaoyun-data/create-symlinks.sh b/zhaoyun-data/create-symlinks.sh new file mode 100755 index 000000000..4020bb59c --- /dev/null +++ b/zhaoyun-data/create-symlinks.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# 创建软链接脚本 +# 将本地data目录中的文件链接到NAS + +SOURCE_ROOT="/Users/chufeng/.openclaw/sanguo_projects/sanguo_quant_live/zhaoyun-data/data/" +TARGET_ROOT="/Users/chufeng/nas/stock-data/sanguo_quant_live/zhaoyun-data/data/" + +echo "🚀 开始创建软链接..." +echo " 源目录: $SOURCE_ROOT" +echo " 目标目录: $TARGET_ROOT" + +# 创建目录结构 +find "$SOURCE_ROOT" -type d | while read dir; do + target_dir="$TARGET_ROOT$dir" + mkdir -p "$target_dir" + echo " 创建目录: $target_dir" +done + +# 创建文件软链接 +count=0 +find "$SOURCE_ROOT" -type f | while read file; do + target_file="$TARGET_ROOT$file" + target_dir=$(dirname "$target_file") + mkdir -p "$target_dir" + rm -f "$target_file" + ln -s "$file" "$target_file" + count=$((count+1)) + if [ $((count % 100)) -eq 0 ]; then + echo " 已创建 $count 个软链接..." + fi +done + +echo "✅ 软链接创建完成!" +echo " 总计: $count 个软链接" +echo diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002004_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002004_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002004_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002005_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002005_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002005_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002006_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002006_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002006_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002007_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002007_valuation.parquet new file mode 100644 index 000000000..9146cc61a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002007_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002008_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002008_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002008_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002009_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002009_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002009_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002010_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002010_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002010_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002011_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002011_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002011_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002012_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002012_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002012_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002014_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002014_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002014_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002015_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002015_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002015_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002016_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002016_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002016_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002017_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002017_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002017_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002019_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002019_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002019_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002020_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002020_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002020_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002021_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002021_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002021_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002022_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002022_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002022_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002023_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002023_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002023_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002024_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002024_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002024_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002025_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002025_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002025_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002026_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002026_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002026_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002027_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002027_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002027_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002028_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002028_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002028_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002029_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002029_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002029_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002030_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002030_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002030_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002031_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002031_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002031_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002032_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002032_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002032_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002033_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002033_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002033_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002034_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002034_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002034_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002035_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002035_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002035_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002036_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002036_valuation.parquet new file mode 100644 index 000000000..9146cc61a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002036_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002037_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002037_valuation.parquet new file mode 100644 index 000000000..9146cc61a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002037_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002038_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002038_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002038_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002039_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002039_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002039_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002040_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002040_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002040_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002041_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002041_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002041_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002042_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002042_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002042_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002043_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002043_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002043_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002044_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002044_valuation.parquet new file mode 100644 index 000000000..9146cc61a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002044_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002045_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002045_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002045_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002046_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002046_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002046_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002047_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002047_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002047_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002048_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002048_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002048_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002049_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002049_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002049_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002050_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002050_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002050_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002051_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002051_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002051_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002052_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002052_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002052_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002053_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002053_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002053_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002054_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002054_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002054_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002055_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002055_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002055_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002056_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002056_valuation.parquet new file mode 100644 index 000000000..9146cc61a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002056_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002057_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002057_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002057_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002058_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002058_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002058_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002059_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002059_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002059_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002060_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002060_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002060_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002061_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002061_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002061_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002062_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002062_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002062_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002063_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002063_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002063_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002064_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002064_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002064_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002065_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002065_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002065_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002066_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002066_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002066_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002067_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002067_valuation.parquet new file mode 100644 index 000000000..9146cc61a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002067_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002068_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002068_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002068_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002069_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002069_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002069_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002072_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002072_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002072_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002073_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002073_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002073_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002074_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002074_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002074_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002075_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002075_valuation.parquet new file mode 100644 index 000000000..36ed3508d Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002075_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002076_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002076_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002076_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002077_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002077_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002077_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002078_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002078_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002078_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002079_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002079_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002079_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002080_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002080_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002080_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002081_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002081_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002081_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002082_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002082_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002082_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002083_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002083_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002083_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002084_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002084_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002084_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002085_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002085_valuation.parquet new file mode 100644 index 000000000..36ed3508d Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002085_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002086_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002086_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002086_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002088_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002088_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002088_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002090_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002090_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002090_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002091_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002091_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002091_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002092_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002092_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002092_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002093_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002093_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002093_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002094_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002094_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002094_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002095_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002095_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002095_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002096_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002096_valuation.parquet new file mode 100644 index 000000000..9146cc61a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002096_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002097_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002097_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002097_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/financial/valuation/sz002098_valuation.parquet b/zhaoyun-data/data/raw/financial/valuation/sz002098_valuation.parquet new file mode 100644 index 000000000..bd7eb990a Binary files /dev/null and b/zhaoyun-data/data/raw/financial/valuation/sz002098_valuation.parquet differ diff --git a/zhaoyun-data/data/raw/running_data/financial_download_stats.json b/zhaoyun-data/data/raw/running_data/financial_download_stats.json index 1b269b790..fca6f2dd1 100644 --- a/zhaoyun-data/data/raw/running_data/financial_download_stats.json +++ b/zhaoyun-data/data/raw/running_data/financial_download_stats.json @@ -1,6 +1,6 @@ { "total_stocks": 5192, - "downloaded_stocks": 350, + "downloaded_stocks": 450, "failed_stocks": 150, "total_reports": 0, "start_time": "2026-03-28T00:03:55.080027",