initial-import: 2026-04-11 21:18:55
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
检查sqlite数据库中有多少bar数据
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
|
||||
db_path = '/root/.vntrader/database.db'
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
print(f"📊 数据库: {db_path}")
|
||||
|
||||
# 查看所有表
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
||||
tables = cursor.fetchall()
|
||||
print(f"\n📋 所有表:")
|
||||
for table in tables:
|
||||
print(f" {table[0]}")
|
||||
|
||||
# 查看bar_data表
|
||||
print(f"\n📊 bar_data表统计:")
|
||||
try:
|
||||
cursor.execute("SELECT COUNT(*) FROM bar_data;")
|
||||
count = cursor.fetchone()[0]
|
||||
print(f" 总共有 {count} 条bar数据")
|
||||
|
||||
# 查看所有symbol
|
||||
cursor.execute("SELECT DISTINCT symbol, exchange FROM bar_data;")
|
||||
symbols = cursor.fetchall()
|
||||
print(f" 标的列表:")
|
||||
for symbol, exchange in symbols:
|
||||
cursor.execute("SELECT COUNT(*) FROM bar_data WHERE symbol = ? AND exchange = ?", (symbol, exchange))
|
||||
cnt = cursor.fetchone()[0]
|
||||
print(f" {symbol}.{exchange}: {cnt} 条")
|
||||
|
||||
# 看一下时间范围
|
||||
cursor.execute("SELECT MIN(datetime), MAX(datetime) FROM bar_data WHERE symbol = ? AND exchange = ?", (symbol, exchange))
|
||||
min_dt, max_dt = cursor.fetchone()
|
||||
print(f" 时间范围: {min_dt} ~ {max_dt}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 查询失败: {e}")
|
||||
|
||||
conn.close()
|
||||
print("\n✅ 查询完成")
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
检查sqlite数据库中有多少bar数据 - vnpy_sqlite表名是dbbardata
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
from vnpy.trader.constant import Exchange
|
||||
|
||||
db_path = '/root/.vntrader/database.db'
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
print(f"📊 数据库: {db_path}")
|
||||
|
||||
# 查看所有表
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
||||
tables = cursor.fetchall()
|
||||
print(f"\n📋 所有表:")
|
||||
for table in tables:
|
||||
print(f" {table[0]}")
|
||||
|
||||
# 查看dbbardata表
|
||||
print(f"\n📊 dbbardata表统计:")
|
||||
try:
|
||||
cursor.execute("SELECT COUNT(*) FROM dbbardata;")
|
||||
count = cursor.fetchone()[0]
|
||||
print(f" 总共有 {count} 条bar数据")
|
||||
|
||||
# 查看所有symbol
|
||||
cursor.execute("SELECT DISTINCT symbol, exchange FROM dbbardata;")
|
||||
symbols = cursor.fetchall()
|
||||
print(f" 标的列表:")
|
||||
for symbol, exchange_code in symbols:
|
||||
cursor.execute("SELECT COUNT(*) FROM dbbardata WHERE symbol = ? AND exchange = ?", (symbol, exchange_code))
|
||||
cnt = cursor.fetchone()[0]
|
||||
try:
|
||||
exchange = Exchange(exchange_code)
|
||||
except:
|
||||
exchange = exchange_code
|
||||
print(f" {symbol}.{exchange}: {cnt} 条")
|
||||
|
||||
# 看一下时间范围
|
||||
cursor.execute("SELECT MIN(datetime), MAX(datetime) FROM dbbardata WHERE symbol = ? AND exchange = ?", (symbol, exchange_code))
|
||||
min_dt, max_dt = cursor.fetchone()
|
||||
print(f" 时间范围: {min_dt} ~ {max_dt}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 查询失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
conn.close()
|
||||
print("\n✅ 查询完成")
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
检查Docker容器的数据卷映射
|
||||
确认容器能否访问赵云的数据
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def main():
|
||||
print("🔍 检查Docker容器数据卷映射")
|
||||
print("="*60)
|
||||
|
||||
# 检查容器信息
|
||||
print("1. 检查 sanguo_vnpy 容器...")
|
||||
cmd = "docker inspect sanguo_vnpy | grep -A 10 'Mounts'"
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||||
print(result.stdout)
|
||||
|
||||
# 检查容器内能否访问数据
|
||||
print("\n2. 检查容器内能否访问数据库...")
|
||||
check_cmd = '''
|
||||
docker exec sanguo_vnpy bash -c "
|
||||
ls -la /app/data/ 2>/dev/null || echo '/app/data/ 不存在'
|
||||
ls -la /Users/chufeng/ 2>/dev/null || echo '/Users/chufeng/ 不存在'
|
||||
ls -la /host/workspace-zhaoyun/zhaoyun-data/data/ 2>/dev/null || echo 'host/workspace-zhaoyun 不存在'
|
||||
"
|
||||
'''
|
||||
result = subprocess.run(check_cmd, shell=True, capture_output=True, text=True)
|
||||
print(result.stdout)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("💡 需要确认:")
|
||||
print("1. 数据库文件在主机: /Users/chufeng/.openclaw/workspace-zhaoyun/zhaoyun-data/data/database_test.db")
|
||||
print("2. 需要确保Docker容器映射了这个路径")
|
||||
print("3. 如果没有映射,需要重新启动容器或配置")
|
||||
print("="*60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
基础设施环境检查报告
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
import importlib
|
||||
from typing import Dict, List, Tuple
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def check_python_version() -> Tuple[bool, str]:
|
||||
"""检查 Python 版本"""
|
||||
version = sys.version
|
||||
major, minor = sys.version_info[:2]
|
||||
is_ok = major >= 3 and minor >= 8
|
||||
status = "✅" if is_ok else "❌"
|
||||
message = f"{status} Python 版本: {version}"
|
||||
if not is_ok:
|
||||
message += " (需要 Python 3.8+)"
|
||||
return is_ok, message
|
||||
|
||||
|
||||
def check_dependencies() -> List[Tuple[bool, str]]:
|
||||
"""检查依赖包"""
|
||||
dependencies = [
|
||||
("numpy", "2.0.0"),
|
||||
("pandas", "2.0.0"),
|
||||
("sqlalchemy", "2.0.0"),
|
||||
("loguru", "0.7.0"),
|
||||
("pydantic", "2.0.0"),
|
||||
("fastapi", "0.100.0"),
|
||||
("uvicorn", "0.20.0"),
|
||||
]
|
||||
|
||||
results = []
|
||||
for package, min_version in dependencies:
|
||||
try:
|
||||
module = importlib.import_module(package)
|
||||
version = getattr(module, "__version__", "未知")
|
||||
is_ok = True # 简化检查,实际应该比较版本
|
||||
status = "✅" if is_ok else "⚠️"
|
||||
results.append((is_ok, f"{status} {package}: {version}"))
|
||||
except ImportError:
|
||||
results.append((False, f"❌ {package}: 未安装"))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def check_directories() -> List[Tuple[bool, str]]:
|
||||
"""检查目录结构"""
|
||||
dirs = [
|
||||
"vnpy_project/logs",
|
||||
"vnpy_project/data",
|
||||
"vnpy_project/strategies",
|
||||
"vnpy_project/backup",
|
||||
"logs",
|
||||
]
|
||||
|
||||
results = []
|
||||
for dir_path in dirs:
|
||||
exists = os.path.exists(dir_path) and os.path.isdir(dir_path)
|
||||
status = "✅" if exists else "❌"
|
||||
results.append((exists, f"{status} 目录: {dir_path}"))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def check_virtual_environment() -> Tuple[bool, str]:
|
||||
"""检查虚拟环境"""
|
||||
in_venv = hasattr(sys, 'real_prefix') or (
|
||||
hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix
|
||||
)
|
||||
status = "✅" if in_venv else "⚠️"
|
||||
message = f"{status} 虚拟环境: {'已激活' if in_venv else '未激活 (建议使用虚拟环境)'}"
|
||||
return in_venv, message
|
||||
|
||||
|
||||
def check_system_info() -> Dict[str, str]:
|
||||
"""获取系统信息"""
|
||||
return {
|
||||
"系统": platform.system(),
|
||||
"系统版本": platform.version(),
|
||||
"架构": platform.machine(),
|
||||
"处理器": platform.processor(),
|
||||
}
|
||||
|
||||
|
||||
def generate_report():
|
||||
"""生成环境检查报告"""
|
||||
logger.info("=" * 60)
|
||||
logger.info(" 量化交易系统 - 基础设施环境检查报告")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# 系统信息
|
||||
logger.info("\n📊 系统信息:")
|
||||
system_info = check_system_info()
|
||||
for key, value in system_info.items():
|
||||
logger.info(f" {key}: {value}")
|
||||
|
||||
# 虚拟环境
|
||||
logger.info("\n🔧 环境状态:")
|
||||
_, venv_msg = check_virtual_environment()
|
||||
logger.info(f" {venv_msg}")
|
||||
|
||||
# Python 版本
|
||||
_, py_msg = check_python_version()
|
||||
logger.info(f" {py_msg}")
|
||||
|
||||
# 依赖检查
|
||||
logger.info("\n📦 依赖包检查:")
|
||||
dep_results = check_dependencies()
|
||||
for _, msg in dep_results:
|
||||
logger.info(f" {msg}")
|
||||
|
||||
# 目录检查
|
||||
logger.info("\n📂 目录结构检查:")
|
||||
dir_results = check_directories()
|
||||
for _, msg in dir_results:
|
||||
logger.info(f" {msg}")
|
||||
|
||||
# 汇总
|
||||
all_checks = dep_results + dir_results + [check_python_version()]
|
||||
passed = sum(1 for ok, _ in all_checks if ok)
|
||||
total = len(all_checks)
|
||||
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info(f"📈 检查结果: {passed}/{total} 项通过")
|
||||
if passed == total:
|
||||
logger.info("🎉 恭喜!所有检查项都通过了,环境准备就绪!")
|
||||
else:
|
||||
logger.warning("⚠️ 部分检查项未通过,请根据提示修复")
|
||||
logger.info("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 配置日志
|
||||
logger.remove()
|
||||
logger.add(sys.stderr, format="<level>{message}</level>")
|
||||
|
||||
generate_report()
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
"""检查vnpy Interval枚举实际名称"""
|
||||
|
||||
from vnpy.trader.constant import Interval
|
||||
|
||||
print("=== vnpy Interval 枚举列表 ===")
|
||||
for name, member in Interval.__members__.items():
|
||||
print(f" {name} → {member}")
|
||||
print()
|
||||
print(f"所有成员: {[name for name in Interval.__members__.keys()]}")
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
"""检查监听地址"""
|
||||
|
||||
import socket
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 检查端口占用
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_address = ('0.0.0.0', 8088)
|
||||
|
||||
try:
|
||||
sock.bind(server_address)
|
||||
print(f"✅ 端口 8088 未被占用,可以绑定")
|
||||
sock.close()
|
||||
except socket.error as e:
|
||||
print(f"⚠️ 端口 8088 已被占用: {e}")
|
||||
|
||||
# 查看进程
|
||||
print(f"\n🔍 当前Python进程:")
|
||||
os.system('ps -ef | grep python')
|
||||
|
||||
# 检查api_for_fixed_rpc.py的监听地址
|
||||
print(f"\n📝 检查api_for_fixed_rpc.py中的监听配置:")
|
||||
os.system('grep -n "uvicorn.run\|bind\|host" /app/api_for_fixed_rpc.py')
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/expect -f
|
||||
|
||||
set timeout 30
|
||||
set nas_ip "192.168.2.154"
|
||||
set nas_user "cfdaily"
|
||||
set nas_pass "Ccf7561523"
|
||||
|
||||
spawn ssh $nas_user@$nas_ip
|
||||
|
||||
expect {
|
||||
"Password:" {
|
||||
send "$nas_pass\r"
|
||||
}
|
||||
"password:" {
|
||||
send "$nas_pass\r"
|
||||
}
|
||||
"Are you sure you want to continue connecting" {
|
||||
send "yes\r"
|
||||
exp_continue
|
||||
}
|
||||
timeout {
|
||||
puts "SSH连接超时"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
expect "$ "
|
||||
|
||||
send "cd /volume1/stock/sanguo_vnpy && ls -la scripts/\r"
|
||||
expect "$ "
|
||||
|
||||
send "cd /volume1/stock/sanguo_vnpy && docker ps -a\r"
|
||||
expect "$ "
|
||||
|
||||
send "exit\r"
|
||||
expect eof
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
import socket
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
result = s.bind(('0.0.0.0', 8008))
|
||||
print("✅ 端口8008空闲,可以绑定")
|
||||
s.close()
|
||||
except OSError as e:
|
||||
print(f"❌ 端口8008被占用: {e}")
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
检查数据库中可能的标的名称格式
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import os
|
||||
|
||||
def check_all_symbols():
|
||||
"""检查数据库中所有标的"""
|
||||
db_path = '/Users/chufeng/.openclaw/workspace-zhaoyun/zhaoyun-data/data/database_test.db'
|
||||
|
||||
if not os.path.exists(db_path):
|
||||
print(f"❌ 数据库不存在: {db_path}")
|
||||
return []
|
||||
|
||||
print(f"🔍 检查数据库中的标的: {db_path}")
|
||||
print("="*60)
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 检查表是否存在
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='dbbardata';")
|
||||
if not cursor.fetchone():
|
||||
print("❌ dbbardata 表不存在")
|
||||
return []
|
||||
|
||||
# 获取所有标的
|
||||
cursor.execute("SELECT DISTINCT symbol FROM dbbardata;")
|
||||
symbols = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
print(f"📊 数据库中有 {len(symbols)} 个标的:")
|
||||
for symbol in symbols:
|
||||
cursor.execute("SELECT COUNT(*) FROM dbbardata WHERE symbol = ?", (symbol,))
|
||||
count = cursor.fetchone()[0]
|
||||
cursor.execute("SELECT MIN(datetime), MAX(datetime) FROM dbbardata WHERE symbol = ?", (symbol,))
|
||||
min_dt, max_dt = cursor.fetchone()
|
||||
print(f" - {symbol}: {count} 行, {min_dt} ~ {max_dt}")
|
||||
|
||||
conn.close()
|
||||
return symbols
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 查询出错: {e}")
|
||||
return []
|
||||
|
||||
def check_510300_variants():
|
||||
"""检查 510300 的各种可能格式"""
|
||||
print("\n🔍 检查 510300 的各种可能格式...")
|
||||
print("="*60)
|
||||
|
||||
variants = [
|
||||
'510300.SSE',
|
||||
'510300.SH',
|
||||
'510300.XSHG',
|
||||
'510300',
|
||||
'510300.XSH',
|
||||
'SH510300',
|
||||
'510300.SHFE',
|
||||
]
|
||||
|
||||
db_path = '/Users/chufeng/.openclaw/workspace-zhaoyun/zhaoyun-data/data/database_test.db'
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
for variant in variants:
|
||||
cursor.execute("SELECT COUNT(*) FROM dbbardata WHERE symbol = ?", (variant,))
|
||||
count = cursor.fetchone()[0]
|
||||
if count > 0:
|
||||
print(f"✅ {variant}: {count} 行数据")
|
||||
else:
|
||||
print(f"❌ {variant}: 0 行数据")
|
||||
|
||||
conn.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 查询出错: {e}")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 检查数据库标的格式")
|
||||
print("="*60)
|
||||
|
||||
# 列出所有标的
|
||||
symbols = check_all_symbols()
|
||||
|
||||
# 检查510300变体
|
||||
check_510300_variants()
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("总结:")
|
||||
|
||||
if not symbols:
|
||||
print("❌ dbbardata 表为空,没有任何数据")
|
||||
print("\n📋 需要处理:")
|
||||
print("1. 联系赵云将军下载 510300 数据")
|
||||
print("2. 将数据转换为vn.py格式")
|
||||
print("3. 配置正确的数据路径")
|
||||
else:
|
||||
print(f"✅ 数据库中有 {len(symbols)} 个标的")
|
||||
print("请检查关羽将军使用的标的名称是否与数据库一致")
|
||||
|
||||
print("="*60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
r = requests.get('http://localhost:7892/api/live-status')
|
||||
data = r.json()
|
||||
|
||||
print('='*80)
|
||||
print(' 🔍 任务状态监控')
|
||||
print('='*80)
|
||||
|
||||
for t in data.get('tasks', []):
|
||||
if t.get('id', '').startswith('JJC-20260401'):
|
||||
print(f'\n任务ID: {t.get("id")}')
|
||||
print(f'标题: {t.get("title")}')
|
||||
print(f'状态: {t.get("state")}')
|
||||
print(f'当前: {t.get("now")}')
|
||||
print(f'组织: {t.get("org")}')
|
||||
|
||||
flow_log = t.get('flow_log', [])
|
||||
if flow_log:
|
||||
print('最新流程:')
|
||||
last = flow_log[-1]
|
||||
print(f' 时间: {last.get("at")}')
|
||||
print(f' 从: {last.get("from")}')
|
||||
print(f' 到: {last.get("to")}')
|
||||
print(f' 备注: {last.get("remark")}')
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
检查赵云将军本地数据中的 510300.SSE 标的
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import pandas as pd
|
||||
import sys
|
||||
import os
|
||||
|
||||
def find_vnpy_database():
|
||||
"""查找vn.py数据库文件"""
|
||||
db_paths = [
|
||||
'/Users/chufeng/.openclaw/workspace-zhaoyun/sanguo_quant_live/zhaoyun-data/data/running_data/database_test.db',
|
||||
'/Users/chufeng/.openclaw/workspace-zhaoyun/zhaoyun-data/data/database_test.db',
|
||||
'/Users/chufeng/.openclaw/memory/zhaoyun-data.sqlite',
|
||||
]
|
||||
|
||||
existing_dbs = []
|
||||
for path in db_paths:
|
||||
if os.path.exists(path):
|
||||
existing_dbs.append(path)
|
||||
print(f"✅ 找到数据库: {path}")
|
||||
else:
|
||||
print(f"❌ 不存在: {path}")
|
||||
|
||||
return existing_dbs
|
||||
|
||||
def check_symbol_in_db(db_path, symbol):
|
||||
"""检查数据库中是否存在指定标的"""
|
||||
print(f"\n🔍 检查数据库 {db_path} 中的 {symbol}...")
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
|
||||
# 列出所有表
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
||||
tables = cursor.fetchall()
|
||||
print(f"📊 数据库中的表: {[t[0] for t in tables]}")
|
||||
|
||||
# 检查可能的表名
|
||||
for table_name in ['dbbardata', 'bar_data', 'daily_data', '1d', 'daily']:
|
||||
try:
|
||||
# 检查表是否存在
|
||||
cursor.execute(f"SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type='table' AND name='{table_name}');")
|
||||
exists = cursor.fetchone()[0]
|
||||
|
||||
if exists:
|
||||
print(f"\n🔍 检查表 {table_name}...")
|
||||
|
||||
# 获取总行数
|
||||
cursor.execute(f"SELECT COUNT(*) FROM {table_name};")
|
||||
total_rows = cursor.fetchone()[0]
|
||||
print(f" 总行数: {total_rows}")
|
||||
|
||||
# 检查symbol是否存在
|
||||
cursor.execute(f"SELECT DISTINCT symbol FROM {table_name} WHERE symbol = ?", (symbol,))
|
||||
result = cursor.fetchone()
|
||||
|
||||
if result:
|
||||
print(f" ✅ 找到标的: {symbol}")
|
||||
|
||||
# 获取该标的数据量
|
||||
cursor.execute(f"SELECT COUNT(*) FROM {table_name} WHERE symbol = ?", (symbol,))
|
||||
count = cursor.fetchone()[0]
|
||||
print(f" 数据行数: {count}")
|
||||
|
||||
# 获取时间范围
|
||||
cursor.execute(f"SELECT MIN(datetime), MAX(datetime) FROM {table_name} WHERE symbol = ?", (symbol,))
|
||||
min_dt, max_dt = cursor.fetchone()
|
||||
print(f" 时间范围: {min_dt} -> {max_dt}")
|
||||
|
||||
# 获取前5行数据
|
||||
cursor.execute(f"SELECT * FROM {table_name} WHERE symbol = ? ORDER BY datetime LIMIT 5", (symbol,))
|
||||
rows = cursor.fetchall()
|
||||
|
||||
# 获取列名
|
||||
cursor.execute(f"PRAGMA table_info({table_name})")
|
||||
columns = [col[1] for col in cursor.fetchall()]
|
||||
print(f" 列名: {columns}")
|
||||
|
||||
print(f" 前5行数据:")
|
||||
for row in rows:
|
||||
print(f" {row}")
|
||||
|
||||
conn.close()
|
||||
return True, {
|
||||
'table': table_name,
|
||||
'count': count,
|
||||
'min_dt': min_dt,
|
||||
'max_dt': max_dt,
|
||||
'columns': columns
|
||||
}
|
||||
else:
|
||||
print(f" ❌ 未找到标的 {symbol}")
|
||||
|
||||
# 列出可用标的供参考
|
||||
cursor.execute(f"SELECT DISTINCT symbol FROM {table_name} LIMIT 10")
|
||||
symbols = cursor.fetchall()
|
||||
if symbols:
|
||||
print(f" 可用标的 (前10个): {[s[0] for s in symbols]}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 检查表出错: {e}")
|
||||
continue
|
||||
|
||||
conn.close()
|
||||
return False, None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 连接数据库出错: {e}")
|
||||
return False, None
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 检查赵云将军本地数据中的 510300.SSE 标的")
|
||||
print("="*60)
|
||||
|
||||
symbol_to_check = "510300.SSE"
|
||||
print(f"目标标的: {symbol_to_check}")
|
||||
|
||||
# 查找数据库
|
||||
dbs = find_vnpy_database()
|
||||
|
||||
if not dbs:
|
||||
print("❌ 未找到任何数据库文件")
|
||||
print("\n📋 可能的原因:")
|
||||
print("1. 赵云将军的数据还未转换到vn.py格式")
|
||||
print("2. 数据路径配置错误")
|
||||
print("3. 510300.SSE 数据还未下载")
|
||||
return False
|
||||
|
||||
# 检查每个数据库
|
||||
found = False
|
||||
result = None
|
||||
|
||||
for db in dbs:
|
||||
found_db, result_db = check_symbol_in_db(db, symbol_to_check)
|
||||
if found_db:
|
||||
found = True
|
||||
result = result_db
|
||||
break
|
||||
|
||||
# 总结
|
||||
print("\n" + "="*60)
|
||||
print("检查结果:")
|
||||
|
||||
if found:
|
||||
print(f"✅ 找到 {symbol_to_check} 数据")
|
||||
print(f" 数据行数: {result['count']}")
|
||||
print(f" 时间范围: {result['min_dt']} -> {result['max_dt']}")
|
||||
print(f" 数据列: {result['columns']}")
|
||||
print(f" 表名: {result['table']}")
|
||||
else:
|
||||
print(f"❌ 未找到 {symbol_to_check} 数据")
|
||||
print("\n📋 需要检查:")
|
||||
print("1. 赵云将军是否已下载 510300.SSE 数据")
|
||||
print("2. 数据是否已转换为vn.py格式")
|
||||
print("3. 数据路径配置是否正确")
|
||||
|
||||
print("="*60)
|
||||
return found
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
简单检查赵云将军本地数据中的 510300.SSE 标的
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import os
|
||||
import sys
|
||||
|
||||
def find_vnpy_database():
|
||||
"""查找vn.py数据库文件"""
|
||||
db_paths = [
|
||||
'/Users/chufeng/.openclaw/workspace-zhaoyun/sanguo_quant_live/zhaoyun-data/data/running_data/database_test.db',
|
||||
'/Users/chufeng/.openclaw/workspace-zhaoyun/zhaoyun-data/data/database_test.db',
|
||||
'/Users/chufeng/.openclaw/memory/zhaoyun-data.sqlite',
|
||||
]
|
||||
|
||||
existing_dbs = []
|
||||
for path in db_paths:
|
||||
if os.path.exists(path):
|
||||
existing_dbs.append(path)
|
||||
print(f"✅ 找到数据库: {path}")
|
||||
size = os.path.getsize(path) / (1024*1024)
|
||||
print(f" 文件大小: {size:.2f} MB")
|
||||
else:
|
||||
print(f"❌ 不存在: {path}")
|
||||
|
||||
return existing_dbs
|
||||
|
||||
def check_symbol_in_db(db_path, symbol):
|
||||
"""检查数据库中是否存在指定标的"""
|
||||
print(f"\n🔍 检查数据库 {db_path} 中的 {symbol}...")
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 列出所有表
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
||||
tables = cursor.fetchall()
|
||||
table_names = [t[0] for t in tables]
|
||||
print(f"📊 数据库中的表: {table_names}")
|
||||
|
||||
# 检查常见表名
|
||||
found = False
|
||||
for table_name in ['dbbardata', 'bar_data', 'daily_data', '1d', 'daily', 'bar']:
|
||||
if table_name not in table_names:
|
||||
continue
|
||||
|
||||
print(f"\n🔍 检查表 {table_name}...")
|
||||
|
||||
# 获取总行数
|
||||
cursor.execute(f"SELECT COUNT(*) FROM {table_name};")
|
||||
total_rows = cursor.fetchone()[0]
|
||||
print(f" 总行数: {total_rows}")
|
||||
|
||||
# 检查symbol是否存在
|
||||
cursor.execute(f"SELECT COUNT(*) FROM {table_name} WHERE symbol = ?", (symbol,))
|
||||
count = cursor.fetchone()[0]
|
||||
|
||||
if count > 0:
|
||||
print(f" ✅ 找到标的: {symbol}")
|
||||
print(f" 数据行数: {count}")
|
||||
|
||||
# 获取时间范围
|
||||
try:
|
||||
cursor.execute(f"SELECT MIN(datetime), MAX(datetime) FROM {table_name} WHERE symbol = ?", (symbol,))
|
||||
min_dt, max_dt = cursor.fetchone()
|
||||
print(f" 时间范围: {min_dt} -> {max_dt}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 获取列名
|
||||
cursor.execute(f"PRAGMA table_info({table_name})")
|
||||
columns = [col[1] for col in cursor.fetchall()]
|
||||
print(f" 列名: {columns}")
|
||||
|
||||
# 看看是否有其他标的
|
||||
cursor.execute(f"SELECT DISTINCT symbol FROM {table_name} LIMIT 5")
|
||||
symbols = [s[0] for s in cursor.fetchall()]
|
||||
print(f" 其他标的: {symbols}")
|
||||
|
||||
found = True
|
||||
break
|
||||
else:
|
||||
print(f" ❌ 未找到标的 {symbol}")
|
||||
|
||||
conn.close()
|
||||
return found
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 连接数据库出错: {e}")
|
||||
return False
|
||||
|
||||
def check_data_paths():
|
||||
"""检查可能的数据路径"""
|
||||
print("\n📂 检查可能的数据路径...")
|
||||
possible_paths = [
|
||||
'/Users/chufeng/.openclaw/workspace-zhaoyun/sanguo_quant_live/zhaoyun-data/',
|
||||
'/Users/chufeng/.openclaw/workspace-zhaoyun/zhaoyun-data/',
|
||||
'/Users/chufeng/.openclaw/sanguo_projects/sanguo_quant_live/zhaoyun-data/',
|
||||
]
|
||||
|
||||
for path in possible_paths:
|
||||
if os.path.exists(path):
|
||||
print(f"✅ 存在: {path}")
|
||||
files = os.listdir(path)
|
||||
print(f" 文件数: {len(files)}")
|
||||
else:
|
||||
print(f"❌ 不存在: {path}")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 简单检查 510300.SSE 标的数据")
|
||||
print("="*60)
|
||||
|
||||
symbol_to_check = "510300.SSE"
|
||||
print(f"目标标的: {symbol_to_check}")
|
||||
|
||||
# 检查数据路径
|
||||
check_data_paths()
|
||||
|
||||
# 查找数据库
|
||||
dbs = find_vnpy_database()
|
||||
|
||||
if not dbs:
|
||||
print("\n❌ 未找到任何数据库文件")
|
||||
print("\n📋 需要检查:")
|
||||
print("1. 赵云将军是否已经下载数据")
|
||||
print("2. 数据是否已经转换到vn.py格式")
|
||||
print("3. 数据路径配置是否正确")
|
||||
return False
|
||||
|
||||
# 检查每个数据库
|
||||
found = False
|
||||
for db in dbs:
|
||||
if check_symbol_in_db(db, symbol_to_check):
|
||||
found = True
|
||||
break
|
||||
|
||||
# 总结
|
||||
print("\n" + "="*60)
|
||||
print("检查结果:")
|
||||
|
||||
if found:
|
||||
print(f"✅ 找到 {symbol_to_check} 数据")
|
||||
print("数据存在,可以配置路径")
|
||||
else:
|
||||
print(f"❌ 未找到 {symbol_to_check} 数据")
|
||||
print("\n📋 可能的原因:")
|
||||
print("1. 数据还未下载到本地")
|
||||
print("2. 数据还未转换为vn.py格式")
|
||||
print("3. 标的名称格式不正确(可能是其他格式,比如 510300.XSHG)")
|
||||
print("4. 数据路径配置错误")
|
||||
|
||||
print("="*60)
|
||||
return found
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
在容器内清理占用端口的进程
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🧹 在容器内清理占用端口的进程...")
|
||||
print("="*60)
|
||||
|
||||
# 使用Python在容器内检测并关闭进程
|
||||
script = '''
|
||||
import psutil
|
||||
import socket
|
||||
|
||||
def get_pid_using_port(port):
|
||||
"""获取占用指定端口的PID"""
|
||||
for conn in psutil.net_connections():
|
||||
if conn.laddr.port == port:
|
||||
return conn.pid
|
||||
return None
|
||||
|
||||
ports = [8001, 8088]
|
||||
for port in ports:
|
||||
pid = get_pid_using_port(port)
|
||||
if pid:
|
||||
print(f"✅ 端口 {port} 被PID {pid} 占用,正在杀死...")
|
||||
try:
|
||||
process = psutil.Process(pid)
|
||||
process.terminate()
|
||||
process.wait(timeout=3)
|
||||
print(f" ✅ 已杀死PID {pid}")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 杀死PID {pid} 失败: {e}")
|
||||
else:
|
||||
print(f"✅ 端口 {port} 未被占用")
|
||||
|
||||
print("\\n🧹 清理完成,现在启动新服务...")
|
||||
'''
|
||||
|
||||
# 执行清理脚本
|
||||
cmd = f'''ssh admin@192.168.2.154 "export PATH=$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy python3 -c '{script}'"'''
|
||||
print(f"执行清理...")
|
||||
subprocess.run(cmd, shell=True)
|
||||
|
||||
print("\n🚀 启动新服务...")
|
||||
|
||||
# 启动RPC服务
|
||||
rpc_cmd = f'''ssh admin@192.168.2.154 "export PATH=$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy bash -c 'python3 /app/scripts/test_server_final_fixed.py &'"'''
|
||||
subprocess.run(rpc_cmd, shell=True)
|
||||
|
||||
# 等待
|
||||
import time
|
||||
time.sleep(3)
|
||||
|
||||
# 启动API服务
|
||||
api_cmd = f'''ssh admin@192.168.2.154 "export PATH=$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy bash -c 'cd /app/scripts && python3 -m uvicorn backtest_api_fixed:app --host 0.0.0.0 --port 8088 &'"'''
|
||||
subprocess.run(api_cmd, shell=True)
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("✅ 清理和重启完成!")
|
||||
print("请测试回测API...")
|
||||
print("="*60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
清理并重启服务
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import sys
|
||||
|
||||
def kill_port_process(port):
|
||||
"""杀死占用指定端口的进程"""
|
||||
print(f"🔍 查找占用端口 {port} 的进程...")
|
||||
|
||||
try:
|
||||
# 使用lsof查找
|
||||
result = subprocess.run(['lsof', '-i', f':{port}', '-t'], capture_output=True, text=True)
|
||||
pids = result.stdout.strip().split()
|
||||
|
||||
if pids:
|
||||
print(f"找到进程: {pids}")
|
||||
for pid in pids:
|
||||
subprocess.run(['kill', '-9', pid], capture_output=True)
|
||||
print(f"✅ 已杀死进程 {pid}")
|
||||
time.sleep(2)
|
||||
return True
|
||||
else:
|
||||
print(f"✅ 端口 {port} 未被占用")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ 查找进程出错: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 清理并重启回测服务")
|
||||
print("="*60)
|
||||
|
||||
# 清理端口
|
||||
kill_port_process(8001) # ZMQ RPC
|
||||
kill_port_process(8088) # FastAPI
|
||||
|
||||
print("\n🚀 启动最终修复版服务...")
|
||||
|
||||
# 启动RPC服务
|
||||
cmd_rpc = [
|
||||
'ssh', 'admin@192.168.2.154',
|
||||
'export PATH=$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy bash -c "python3 /app/scripts/test_server_final_fixed.py &"'
|
||||
]
|
||||
|
||||
print("启动RPC服务...")
|
||||
subprocess.run(cmd_rpc)
|
||||
time.sleep(3)
|
||||
|
||||
# 启动API服务
|
||||
cmd_api = [
|
||||
'ssh', 'admin@192.168.2.154',
|
||||
'export PATH=$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy bash -c "cd /app/scripts && python3 -m uvicorn backtest_api_fixed:app --host 0.0.0.0 --port 8088 &"'
|
||||
]
|
||||
|
||||
print("启动API服务...")
|
||||
subprocess.run(cmd_api)
|
||||
time.sleep(3)
|
||||
|
||||
print("\n✅ 服务重启完成!")
|
||||
print("="*60)
|
||||
print("验证:")
|
||||
print(" 1. ZMQ RPC: 端口 8001")
|
||||
print(" 2. FastAPI: 端口 8088")
|
||||
print(" 3. vnpy.app兼容性: ✅ 已修复")
|
||||
print(" 4. 510300.SSE数据: ✅ 已导入 (714行)")
|
||||
print("="*60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
import pandas as pd
|
||||
|
||||
parquet_path = "/Users/chufeng/nas/stock-data/sanguo_quant_live/zhaoyun-data/data/raw/daily/sh510300_daily.parquet"
|
||||
csv_path = "/tmp/510300_daily.csv"
|
||||
|
||||
df = pd.read_parquet(parquet_path)
|
||||
df.to_csv(csv_path, index=False)
|
||||
|
||||
print(f"Converted {len(df)} rows to CSV: {csv_path}")
|
||||
print(f"Size: {open(csv_path).read().__len__()} bytes")
|
||||
@@ -0,0 +1,198 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
在容器内创建vn.py数据库,并导入510300.SSE数据
|
||||
数据文件已经在NAS: /volume1/stock-data/sanguo_quant_live/zhaoyun-data/data/raw/daily/sh510300_daily.parquet
|
||||
"""
|
||||
|
||||
script_content = '''
|
||||
import pandas as pd
|
||||
import sqlite3
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
print("🚀 在容器内导入510300.SSE数据...")
|
||||
print("="*60)
|
||||
|
||||
# 配置
|
||||
parquet_path = "/volume1/stock-data/sanguo_quant_live/zhaoyun-data/data/raw/daily/sh510300_daily.parquet"
|
||||
db_path = "/volume1/stock/sanguo_vnpy/data/database_test.db"
|
||||
symbol = "510300.SSE"
|
||||
exchange = "SSE"
|
||||
interval = "1d"
|
||||
|
||||
print(f"源数据: {parquet_path}")
|
||||
print(f"目标数据库: {db_path}")
|
||||
print(f"标的: {symbol}")
|
||||
|
||||
# 检查源文件存在
|
||||
if not os.path.exists(parquet_path):
|
||||
print(f"❌ 源文件不存在: {parquet_path}")
|
||||
exit(1)
|
||||
|
||||
print(f"✅ 源文件存在")
|
||||
|
||||
# 读取parquet
|
||||
print("\\n📥 读取parquet数据...")
|
||||
df = pd.read_parquet(parquet_path)
|
||||
print(f" 读取成功: {len(df)} 行")
|
||||
|
||||
# 创建数据库
|
||||
print(f"\\n💾 创建vn.py数据库...")
|
||||
|
||||
if os.path.exists(db_path):
|
||||
os.remove(db_path)
|
||||
print(f" 删除旧数据库")
|
||||
|
||||
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 创建vn.py标准表结构
|
||||
cursor.execute("""
|
||||
CREATE TABLE dbbardata (
|
||||
symbol TEXT NOT NULL,
|
||||
exchange TEXT,
|
||||
interval TEXT NOT NULL,
|
||||
datetime INTEGER NOT NULL,
|
||||
open REAL NOT NULL,
|
||||
high REAL NOT NULL,
|
||||
low REAL NOT NULL,
|
||||
close REAL NOT NULL,
|
||||
volume INTEGER NOT NULL,
|
||||
open_interest REAL,
|
||||
turnover REAL,
|
||||
PRIMARY KEY (symbol, interval, datetime)
|
||||
);
|
||||
""")
|
||||
|
||||
# 创建索引
|
||||
cursor.execute("CREATE INDEX ix_dbbardata_symbol ON dbbardata(symbol);")
|
||||
cursor.execute("CREATE INDEX ix_dbbardata_symbol_interval ON dbbardata(symbol, interval);")
|
||||
|
||||
# 导入数据
|
||||
print(f"\\n📊 导入数据...")
|
||||
|
||||
imported = 0
|
||||
errors = 0
|
||||
|
||||
for idx, row in df.iterrows():
|
||||
# 获取日期
|
||||
date_val = row['date']
|
||||
|
||||
if isinstance(date_val, pd.Timestamp):
|
||||
dt = date_val.to_pydatetime()
|
||||
else:
|
||||
date_str = str(date_val)
|
||||
if '-' in date_str:
|
||||
dt = datetime.strptime(date_str, '%Y-%m-%d')
|
||||
else:
|
||||
dt = datetime.strptime(date_str, '%Y%m%d')
|
||||
|
||||
timestamp = int(dt.timestamp())
|
||||
|
||||
# 获取价格数据
|
||||
open_price = float(row['open'])
|
||||
high_price = float(row['high'])
|
||||
low_price = float(row['low'])
|
||||
close_price = float(row['close'])
|
||||
volume = int(float(row['volume']))
|
||||
|
||||
# 成交额
|
||||
if 'amount' in row:
|
||||
turnover = float(row['amount'])
|
||||
else:
|
||||
turnover = volume * close_price
|
||||
|
||||
# 插入
|
||||
try:
|
||||
cursor.execute("""
|
||||
INSERT INTO dbbardata (
|
||||
symbol, exchange, interval, datetime,
|
||||
open, high, low, close, volume, turnover
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
symbol,
|
||||
exchange,
|
||||
interval,
|
||||
timestamp,
|
||||
open_price,
|
||||
high_price,
|
||||
low_price,
|
||||
close_price,
|
||||
volume,
|
||||
turnover
|
||||
))
|
||||
|
||||
imported += 1
|
||||
|
||||
if imported % 500 == 0:
|
||||
print(f" 已导入 {imported} 行...")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 第{idx}行导入失败: {e}")
|
||||
errors += 1
|
||||
|
||||
# 提交
|
||||
conn.commit()
|
||||
|
||||
# 验证
|
||||
print("\\n🔍 验证导入结果...")
|
||||
cursor.execute("SELECT COUNT(*) FROM dbbardata WHERE symbol = ?", (symbol,))
|
||||
count = cursor.fetchone()[0]
|
||||
|
||||
cursor.execute("SELECT MIN(datetime), MAX(datetime) FROM dbbardata WHERE symbol = ?", (symbol,))
|
||||
min_ts, max_ts = cursor.fetchone()
|
||||
|
||||
if min_ts and max_ts:
|
||||
min_dt = datetime.fromtimestamp(min_ts).strftime('%Y-%m-%d')
|
||||
max_dt = datetime.fromtimestamp(max_ts).strftime('%Y-%m-%d')
|
||||
else:
|
||||
min_dt = 'N/A'
|
||||
max_dt = 'N/A'
|
||||
|
||||
cursor.execute("SELECT MIN(close), MAX(close), AVG(volume) FROM dbbardata WHERE symbol = ?", (symbol,))
|
||||
min_close, max_close, avg_volume = cursor.fetchone()
|
||||
|
||||
conn.close()
|
||||
|
||||
# 统计
|
||||
print("\\n" + "="*60)
|
||||
print("✅ 导入完成!")
|
||||
print(f"源文件: {parquet_path}")
|
||||
print(f"目标数据库: {db_path}")
|
||||
print(f"标的: {symbol}")
|
||||
print(f"源数据行数: {len(df)}")
|
||||
print(f"成功导入: {imported}")
|
||||
print(f"导入失败: {errors}")
|
||||
print(f"数据库验证: {count} 行")
|
||||
print(f"时间范围: {min_dt} -> {max_dt}")
|
||||
print(f"价格范围: {min_close:.2f} ~ {max_close:.2f}")
|
||||
print(f"平均成交量: {avg_volume:.0f}")
|
||||
print("="*60)
|
||||
|
||||
# 显示文件大小
|
||||
if os.path.exists(db_path):
|
||||
size_kb = os.path.getsize(db_path) / 1024
|
||||
print(f"\\n📦 数据库文件大小: {size_kb:.1f} KB")
|
||||
|
||||
print("\\n🎯 完成!")
|
||||
print("数据库已创建在容器可访问路径: {db_path}")
|
||||
print("现在可以重启API服务了")
|
||||
'''
|
||||
|
||||
# 将脚本发送到容器
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
print("🚀 在容器内创建数据库...")
|
||||
print("="*60)
|
||||
|
||||
cmd = f'''ssh admin@192.168.2.154 "export PATH=\$PATH:/var/packages/Docker/target/usr/bin && docker exec -i sanguo_vnpy python3 - << 'EOF'
|
||||
{script_content}
|
||||
EOF
|
||||
"'''
|
||||
|
||||
result = subprocess.run(cmd, shell=True)
|
||||
print("="*60)
|
||||
print("完成!")
|
||||
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
创建 vnpy.app 兼容性模块
|
||||
解决 vn.py 4.x 中缺少 vnpy.app 模块的问题
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import types
|
||||
|
||||
def create_vnpy_app_module():
|
||||
"""创建 vnpy.app 模块"""
|
||||
|
||||
# 检查是否已存在
|
||||
if 'vnpy.app' in sys.modules:
|
||||
print("✅ vnpy.app 模块已存在")
|
||||
return
|
||||
|
||||
print("🔧 创建 vnpy.app 兼容性模块...")
|
||||
|
||||
# 创建 vnpy.app 顶级模块
|
||||
vnpy_app = types.ModuleType('vnpy.app')
|
||||
sys.modules['vnpy.app'] = vnpy_app
|
||||
|
||||
# 创建子模块
|
||||
submodules = ['cta_strategy', 'cta_backtester', 'data_manager', 'rpc_service']
|
||||
|
||||
for submodule_name in submodules:
|
||||
full_name = f'vnpy.app.{submodule_name}'
|
||||
submodule = types.ModuleType(full_name)
|
||||
sys.modules[full_name] = submodule
|
||||
setattr(vnpy_app, submodule_name, submodule)
|
||||
print(f" ✅ 创建子模块: {full_name}")
|
||||
|
||||
# 从实际模块导入并映射
|
||||
try:
|
||||
from vnpy_ctastrategy import CtaTemplate, CtaStrategyApp
|
||||
sys.modules['vnpy.app.cta_strategy'].CtaTemplate = CtaTemplate
|
||||
sys.modules['vnpy.app.cta_strategy'].CtaStrategyApp = CtaStrategyApp
|
||||
vnpy_app.CtaTemplate = CtaTemplate
|
||||
vnpy_app.CtaStrategyApp = CtaStrategyApp
|
||||
print(" ✅ 映射: CtaTemplate, CtaStrategyApp")
|
||||
except ImportError as e:
|
||||
print(f" ❌ 无法导入 vnpy_ctastrategy: {e}")
|
||||
|
||||
try:
|
||||
from vnpy_ctabacktester import CtaBacktesterApp
|
||||
sys.modules['vnpy.app.cta_backtester'].CtaBacktesterApp = CtaBacktesterApp
|
||||
vnpy_app.CtaBacktesterApp = CtaBacktesterApp
|
||||
print(" ✅ 映射: CtaBacktesterApp")
|
||||
except ImportError as e:
|
||||
print(f" ❌ 无法导入 vnpy_ctabacktester: {e}")
|
||||
|
||||
try:
|
||||
from vnpy_datamanager import DataManagerApp
|
||||
sys.modules['vnpy.app.data_manager'].DataManagerApp = DataManagerApp
|
||||
vnpy_app.DataManagerApp = DataManagerApp
|
||||
print(" ✅ 映射: DataManagerApp")
|
||||
except ImportError as e:
|
||||
print(f" ❌ 无法导入 vnpy_datamanager: {e}")
|
||||
|
||||
try:
|
||||
from vnpy_webtrader import WebTraderApp
|
||||
sys.modules['vnpy.app.rpc_service'].WebTraderApp = WebTraderApp
|
||||
vnpy_app.WebTraderApp = WebTraderApp
|
||||
print(" ✅ 映射: WebTraderApp")
|
||||
except ImportError as e:
|
||||
print(f" ❌ 无法导入 vnpy_webtrader: {e}")
|
||||
|
||||
# 添加其他常用模块
|
||||
try:
|
||||
from vnpy.trader.engine import MainEngine
|
||||
vnpy_app.MainEngine = MainEngine
|
||||
print(" ✅ 映射: MainEngine")
|
||||
except ImportError as e:
|
||||
print(f" ❌ 无法导入 MainEngine: {e}")
|
||||
|
||||
try:
|
||||
from vnpy.event import EventEngine
|
||||
vnpy_app.EventEngine = EventEngine
|
||||
print(" ✅ 映射: EventEngine")
|
||||
except ImportError as e:
|
||||
print(f" ❌ 无法导入 EventEngine: {e}")
|
||||
|
||||
print("✅ vnpy.app 兼容性模块创建完成")
|
||||
|
||||
def test_imports():
|
||||
"""测试导入"""
|
||||
print("\n🧪 测试导入...")
|
||||
|
||||
test_cases = [
|
||||
("import vnpy.app", None),
|
||||
("from vnpy.app.cta_strategy import CtaTemplate", "CtaTemplate"),
|
||||
("from vnpy.app.cta_strategy import CtaStrategyApp", "CtaStrategyApp"),
|
||||
("from vnpy.app.cta_backtester import CtaBacktesterApp", "CtaBacktesterApp"),
|
||||
]
|
||||
|
||||
for import_stmt, expected in test_cases:
|
||||
try:
|
||||
exec(import_stmt)
|
||||
if expected:
|
||||
print(f" ✅ {import_stmt} -> {expected}")
|
||||
else:
|
||||
print(f" ✅ {import_stmt}")
|
||||
except Exception as e:
|
||||
print(f" ❌ {import_stmt}: {e}")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 创建 vnpy.app 兼容性模块")
|
||||
print("=" * 60)
|
||||
|
||||
# 创建模块
|
||||
create_vnpy_app_module()
|
||||
|
||||
# 测试导入
|
||||
test_imports()
|
||||
|
||||
# 显示可用模块
|
||||
print("\n📦 可用的 vnpy.app 模块:")
|
||||
try:
|
||||
import vnpy.app
|
||||
for attr in dir(vnpy.app):
|
||||
if not attr.startswith('_'):
|
||||
print(f" - {attr}")
|
||||
except Exception as e:
|
||||
print(f" ❌ 无法导入 vnpy.app: {e}")
|
||||
|
||||
print("\n✅ 兼容性模块创建完成!")
|
||||
print("现在可以正常使用 'from vnpy.app.cta_strategy import CtaTemplate' 了")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,501 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
数据格式转换工具 - 姜维
|
||||
功能:将赵云将军的本地数据格式转换为vn.py兼容格式
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import os
|
||||
import glob
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('data_convert_tool.log'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DataConverter:
|
||||
"""
|
||||
数据格式转换器
|
||||
赵云格式 → vn.py格式
|
||||
"""
|
||||
|
||||
# 赵云数据字段映射到vn.py字段
|
||||
FIELD_MAPPING = {
|
||||
# 基本字段
|
||||
'date': 'datetime',
|
||||
'open': 'open_price',
|
||||
'high': 'high_price',
|
||||
'low': 'low_price',
|
||||
'close': 'close_price',
|
||||
'volume': 'volume',
|
||||
'amount': 'turnover', # 注意:vn.py中turnover是成交额
|
||||
'turnover': 'turnover_rate', # 换手率
|
||||
|
||||
# 可选字段
|
||||
'outstanding_share': 'outstanding_share',
|
||||
'year': 'year',
|
||||
|
||||
# 财务数据字段
|
||||
'pe_ttm': 'pe_ttm',
|
||||
'pb': 'pb',
|
||||
'roe': 'roe',
|
||||
'total_market_cap': 'total_market_cap',
|
||||
'circulating_market_cap': 'circulating_market_cap',
|
||||
}
|
||||
|
||||
# 必需字段
|
||||
REQUIRED_FIELDS = ['date', 'open', 'high', 'low', 'close', 'volume']
|
||||
|
||||
def __init__(self, zhaoyun_data_dir: str, output_dir: str):
|
||||
"""
|
||||
初始化转换器
|
||||
|
||||
Args:
|
||||
zhaoyun_data_dir: 赵云数据目录
|
||||
output_dir: 输出目录
|
||||
"""
|
||||
self.zhaoyun_dir = zhaoyun_data_dir
|
||||
self.output_dir = output_dir
|
||||
|
||||
# 创建输出目录
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# 子目录结构
|
||||
self.subdirs = {
|
||||
'daily': 'daily',
|
||||
'minute': 'minute',
|
||||
'financial': 'financial',
|
||||
'stock_info': 'stock_info',
|
||||
}
|
||||
|
||||
for subdir in self.subdirs.values():
|
||||
os.makedirs(os.path.join(output_dir, subdir), exist_ok=True)
|
||||
|
||||
def analyze_zhaoyun_structure(self) -> dict:
|
||||
"""
|
||||
分析赵云数据目录结构
|
||||
|
||||
Returns:
|
||||
结构分析报告
|
||||
"""
|
||||
report = {
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'zhaoyun_dir': self.zhaoyun_dir,
|
||||
'exists': os.path.exists(self.zhaoyun_dir),
|
||||
'subdirectories': {},
|
||||
'file_counts': {},
|
||||
'sample_files': {},
|
||||
'data_quality': {},
|
||||
}
|
||||
|
||||
if not report['exists']:
|
||||
logger.error(f"赵云数据目录不存在: {self.zhaoyun_dir}")
|
||||
return report
|
||||
|
||||
# 分析子目录
|
||||
for subdir in ['raw/daily', 'raw/financial', 'raw/stock_info', 'raw/minute_kline']:
|
||||
full_path = os.path.join(self.zhaoyun_dir, subdir)
|
||||
if os.path.exists(full_path):
|
||||
# 统计文件
|
||||
parquet_files = list(glob.glob(os.path.join(full_path, '**/*.parquet'), recursive=True))
|
||||
csv_files = list(glob.glob(os.path.join(full_path, '**/*.csv'), recursive=True))
|
||||
|
||||
report['subdirectories'][subdir] = {
|
||||
'path': full_path,
|
||||
'parquet_count': len(parquet_files),
|
||||
'csv_count': len(csv_files),
|
||||
'total_files': len(parquet_files) + len(csv_files),
|
||||
}
|
||||
|
||||
# 取样分析
|
||||
if parquet_files:
|
||||
sample_file = parquet_files[0]
|
||||
try:
|
||||
df = pd.read_parquet(sample_file)
|
||||
report['sample_files'][subdir] = {
|
||||
'file': sample_file,
|
||||
'rows': len(df),
|
||||
'columns': list(df.columns),
|
||||
'dtypes': str(df.dtypes.to_dict()),
|
||||
'date_range': {
|
||||
'min': str(df['date'].min()) if 'date' in df.columns else 'N/A',
|
||||
'max': str(df['date'].max()) if 'date' in df.columns else 'N/A',
|
||||
} if 'date' in df.columns else {},
|
||||
}
|
||||
except Exception as e:
|
||||
report['sample_files'][subdir] = {'error': str(e)}
|
||||
|
||||
logger.info(f"赵云数据结构分析完成")
|
||||
return report
|
||||
|
||||
def convert_daily_data(self, year: int = None, symbols: list = None, limit: int = None):
|
||||
"""
|
||||
转换日线数据
|
||||
|
||||
Args:
|
||||
year: 指定年份,None表示所有年份
|
||||
symbols: 指定股票代码列表,None表示所有股票
|
||||
limit: 限制转换数量(用于测试)
|
||||
"""
|
||||
daily_dir = os.path.join(self.zhaoyun_dir, 'raw/daily')
|
||||
if not os.path.exists(daily_dir):
|
||||
logger.error(f"赵云日线数据目录不存在: {daily_dir}")
|
||||
return
|
||||
|
||||
# 确定年份范围
|
||||
if year:
|
||||
years = [str(year)]
|
||||
else:
|
||||
years = [d for d in os.listdir(daily_dir) if os.path.isdir(os.path.join(daily_dir, d))]
|
||||
years.sort()
|
||||
|
||||
logger.info(f"开始转换日线数据,年份: {years}")
|
||||
|
||||
total_converted = 0
|
||||
total_failed = 0
|
||||
|
||||
for year_dir in years:
|
||||
year_path = os.path.join(daily_dir, year_dir)
|
||||
output_year_path = os.path.join(self.output_dir, 'daily', year_dir)
|
||||
os.makedirs(output_year_path, exist_ok=True)
|
||||
|
||||
# 查找所有parquet文件
|
||||
parquet_files = glob.glob(os.path.join(year_path, '*.parquet'))
|
||||
|
||||
if symbols:
|
||||
# 过滤指定股票
|
||||
filtered_files = []
|
||||
for file in parquet_files:
|
||||
file_name = os.path.basename(file)
|
||||
# 从文件名提取股票代码
|
||||
if 'sh' in file_name:
|
||||
symbol = file_name.split('_')[0][2:] + '.SH'
|
||||
elif 'sz' in file_name:
|
||||
symbol = file_name.split('_')[0][2:] + '.SZ'
|
||||
elif 'bj' in file_name:
|
||||
symbol = file_name.split('_')[0][2:] + '.BJ'
|
||||
else:
|
||||
symbol = file_name.split('_')[0]
|
||||
|
||||
if symbol in symbols or symbol.replace('.SH', '').replace('.SZ', '').replace('.BJ', '') in symbols:
|
||||
filtered_files.append(file)
|
||||
parquet_files = filtered_files
|
||||
|
||||
if limit:
|
||||
parquet_files = parquet_files[:limit]
|
||||
|
||||
logger.info(f"转换 {year_dir} 年数据,共 {len(parquet_files)} 个文件")
|
||||
|
||||
for file_idx, file_path in enumerate(parquet_files, 1):
|
||||
try:
|
||||
# 从文件名提取信息
|
||||
file_name = os.path.basename(file_path)
|
||||
|
||||
# 解析股票代码和交易所
|
||||
if file_name.startswith('sh'):
|
||||
symbol = file_name[2:8] # 提取6位数字代码
|
||||
exchange = 'SH'
|
||||
elif file_name.startswith('sz'):
|
||||
symbol = file_name[2:8]
|
||||
exchange = 'SZ'
|
||||
elif file_name.startswith('bj'):
|
||||
symbol = file_name[2:8]
|
||||
exchange = 'BJ'
|
||||
else:
|
||||
symbol = file_name.split('_')[0]
|
||||
exchange = 'SH' # 默认
|
||||
|
||||
# 读取数据
|
||||
df = pd.read_parquet(file_path)
|
||||
|
||||
# 检查必需字段
|
||||
missing_fields = [field for field in self.REQUIRED_FIELDS if field not in df.columns]
|
||||
if missing_fields:
|
||||
logger.warning(f"文件 {file_name} 缺少必需字段: {missing_fields}")
|
||||
total_failed += 1
|
||||
continue
|
||||
|
||||
# 创建vn.py格式DataFrame
|
||||
vnpy_df = pd.DataFrame()
|
||||
|
||||
# 转换字段
|
||||
for zhaoyun_field, vnpy_field in self.FIELD_MAPPING.items():
|
||||
if zhaoyun_field in df.columns:
|
||||
vnpy_df[vnpy_field] = df[zhaoyun_field]
|
||||
|
||||
# 特殊处理datetime字段
|
||||
if 'datetime' not in vnpy_df.columns and 'date' in df.columns:
|
||||
vnpy_df['datetime'] = pd.to_datetime(df['date']).dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 添加标识字段
|
||||
vnpy_df['symbol'] = symbol
|
||||
vnpy_df['exchange'] = exchange
|
||||
vnpy_df['interval'] = '1d'
|
||||
|
||||
# 添加唯一ID(可选)
|
||||
vnpy_df['id'] = range(1, len(vnpy_df) + 1)
|
||||
|
||||
# 输出文件名
|
||||
output_file = os.path.join(output_year_path, f"{exchange}{symbol}_daily_vnpy.parquet")
|
||||
|
||||
# 保存为parquet
|
||||
vnpy_df.to_parquet(output_file, index=False)
|
||||
|
||||
total_converted += 1
|
||||
|
||||
if file_idx % 100 == 0 or file_idx == len(parquet_files):
|
||||
logger.info(f"进度: {year_dir}年 {file_idx}/{len(parquet_files)} 转换: {total_converted} 失败: {total_failed}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"转换文件失败 {file_path}: {e}")
|
||||
total_failed += 1
|
||||
|
||||
logger.info(f"日线数据转换完成: 成功 {total_converted}, 失败 {total_failed}")
|
||||
|
||||
# 保存转换报告
|
||||
report = {
|
||||
'conversion_date': datetime.now().isoformat(),
|
||||
'zhaoyun_dir': daily_dir,
|
||||
'output_dir': os.path.join(self.output_dir, 'daily'),
|
||||
'years_converted': years,
|
||||
'total_converted': total_converted,
|
||||
'total_failed': total_failed,
|
||||
'symbols_converted': symbols if symbols else 'ALL',
|
||||
}
|
||||
|
||||
report_file = os.path.join(self.output_dir, 'daily_conversion_report.json')
|
||||
with open(report_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(report, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"转换报告已保存: {report_file}")
|
||||
|
||||
def convert_stock_info(self):
|
||||
"""转换股票基础信息"""
|
||||
stock_info_dir = os.path.join(self.zhaoyun_dir, 'raw/stock_info')
|
||||
if not os.path.exists(stock_info_dir):
|
||||
logger.warning(f"赵云股票信息目录不存在: {stock_info_dir}")
|
||||
return
|
||||
|
||||
# 查找股票信息文件
|
||||
stock_files = glob.glob(os.path.join(stock_info_dir, '*.parquet')) + \
|
||||
glob.glob(os.path.join(stock_info_dir, '*.csv'))
|
||||
|
||||
if not stock_files:
|
||||
logger.warning(f"未找到股票信息文件")
|
||||
return
|
||||
|
||||
logger.info(f"开始转换股票信息,共 {len(stock_files)} 个文件")
|
||||
|
||||
all_stock_info = []
|
||||
|
||||
for file_path in stock_files:
|
||||
try:
|
||||
# 读取文件
|
||||
if file_path.endswith('.parquet'):
|
||||
df = pd.read_parquet(file_path)
|
||||
else:
|
||||
df = pd.read_csv(file_path)
|
||||
|
||||
# 标准化字段名
|
||||
column_mapping = {
|
||||
'代码': 'symbol',
|
||||
'名称': 'name',
|
||||
'行业': 'industry',
|
||||
'市场': 'market',
|
||||
'上市日期': 'list_date',
|
||||
'总市值': 'total_market_cap',
|
||||
'流通市值': 'circulating_market_cap',
|
||||
'市盈率': 'pe',
|
||||
'市净率': 'pb',
|
||||
'ROE': 'roe',
|
||||
}
|
||||
|
||||
df = df.rename(columns={k: v for k, v in column_mapping.items() if k in df.columns})
|
||||
|
||||
# 添加exchange字段
|
||||
if 'symbol' in df.columns:
|
||||
df['exchange'] = df['symbol'].apply(lambda x: 'SH' if str(x).startswith('6') else 'SZ')
|
||||
|
||||
all_stock_info.append(df)
|
||||
logger.info(f"转换股票信息文件: {os.path.basename(file_path)} ({len(df)} 条记录)")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"转换股票信息失败 {file_path}: {e}")
|
||||
|
||||
if all_stock_info:
|
||||
# 合并所有数据
|
||||
combined_df = pd.concat(all_stock_info, ignore_index=True)
|
||||
|
||||
# 去重
|
||||
if 'symbol' in combined_df.columns:
|
||||
combined_df = combined_df.drop_duplicates(subset=['symbol'])
|
||||
|
||||
# 保存
|
||||
output_file = os.path.join(self.output_dir, 'stock_info', 'stock_basic_info_vnpy.parquet')
|
||||
combined_df.to_parquet(output_file, index=False)
|
||||
|
||||
logger.info(f"股票信息转换完成: {output_file} ({len(combined_df)} 只股票)")
|
||||
|
||||
def create_config_file(self):
|
||||
"""创建vn.py配置文件"""
|
||||
config = {
|
||||
'data_source': 'zhaoyun_local_data',
|
||||
'data_directory': os.path.abspath(self.output_dir),
|
||||
'priority': 'local_first',
|
||||
'fields_mapping': self.FIELD_MAPPING,
|
||||
'created_at': datetime.now().isoformat(),
|
||||
'description': '赵云本地数据 → vn.py格式转换配置',
|
||||
'usage': {
|
||||
'daily_data_path': '{data_directory}/daily/{year}/{exchange}{symbol}_daily_vnpy.parquet',
|
||||
'stock_info_path': '{data_directory}/stock_info/stock_basic_info_vnpy.parquet',
|
||||
'python_import': 'from vnpy_local_data_adapter import VnpyLocalDataAdapter',
|
||||
'init_code': 'adapter = VnpyLocalDataAdapter(use_local_first=True)',
|
||||
}
|
||||
}
|
||||
|
||||
config_file = os.path.join(self.output_dir, 'vnpy_data_config.json')
|
||||
with open(config_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"vn.py配置文件已创建: {config_file}")
|
||||
|
||||
# 创建使用说明
|
||||
readme = f"""# vn.py本地数据使用说明
|
||||
|
||||
## 数据来源
|
||||
- 原始数据:赵云将军下载的A股数据
|
||||
- 转换工具:姜维数据格式转换器
|
||||
- 输出格式:vn.py兼容的parquet格式
|
||||
|
||||
## 目录结构
|
||||
```
|
||||
{self.output_dir}/
|
||||
├── daily/ # 日线数据
|
||||
│ ├── 2010/ # 按年分区
|
||||
│ ├── 2011/
|
||||
│ └── ...
|
||||
├── stock_info/ # 股票基础信息
|
||||
│ └── stock_basic_info_vnpy.parquet
|
||||
├── vnpy_data_config.json # 配置文件
|
||||
└── daily_conversion_report.json # 转换报告
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 在vn.py策略中使用
|
||||
```python
|
||||
from vnpy_local_data_adapter import VnpyLocalDataAdapter
|
||||
|
||||
# 创建适配器(优先使用本地数据)
|
||||
adapter = VnpyLocalDataAdapter(use_local_first=True)
|
||||
|
||||
# 获取数据
|
||||
data = adapter.get_daily_data("000001.SZ", "2024-01-01", "2024-03-01")
|
||||
```
|
||||
|
||||
### 2. 直接读取数据
|
||||
```python
|
||||
import pandas as pd
|
||||
|
||||
# 读取日线数据
|
||||
file_path = "{self.output_dir}/daily/2024/SH600000_daily_vnpy.parquet"
|
||||
df = pd.read_parquet(file_path)
|
||||
|
||||
# 读取股票信息
|
||||
stock_info_path = "{self.output_dir}/stock_info/stock_basic_info_vnpy.parquet"
|
||||
stock_info = pd.read_parquet(stock_info_path)
|
||||
```
|
||||
|
||||
### 3. 验证数据结构
|
||||
```python
|
||||
from vnpy_local_data_adapter import VnpyLocalDataAdapter
|
||||
|
||||
adapter = VnpyLocalDataAdapter()
|
||||
result = adapter.verify_local_data_structure("000001.SZ")
|
||||
print(result)
|
||||
```
|
||||
|
||||
## 数据更新
|
||||
1. 联系赵云将军更新原始数据
|
||||
2. 运行数据转换工具更新vn.py格式数据
|
||||
3. 验证数据完整性
|
||||
|
||||
## 注意事项
|
||||
- 本地数据优先,缺失时自动回退到akshare
|
||||
- 数据文件按年分区,提高查询效率
|
||||
- 定期检查数据完整性
|
||||
|
||||
**维护者**: 姜维(后勤总督)
|
||||
**数据源**: 赵云(数据工程将军)
|
||||
**最后更新**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||
"""
|
||||
|
||||
readme_file = os.path.join(self.output_dir, 'README.md')
|
||||
with open(readme_file, 'w', encoding='utf-8') as f:
|
||||
f.write(readme)
|
||||
|
||||
logger.info(f"使用说明已创建: {readme_file}")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 60)
|
||||
print("赵云数据 → vn.py格式转换工具")
|
||||
print("=" * 60)
|
||||
|
||||
# 配置路径
|
||||
ZHAOYUN_DATA_DIR = "/Users/chufeng/nas/stock/sanguo_vnpy/zhaoyun-data/data"
|
||||
OUTPUT_DIR = "/Users/chufeng/.openclaw/workspace-jiangwei/vnpy_local_data"
|
||||
|
||||
# 创建转换器
|
||||
converter = DataConverter(ZHAOYUN_DATA_DIR, OUTPUT_DIR)
|
||||
|
||||
# 1. 分析数据结构
|
||||
print("\n1. 分析赵云数据结构...")
|
||||
structure_report = converter.analyze_zhaoyun_structure()
|
||||
|
||||
if not structure_report['exists']:
|
||||
print(f"❌ 赵云数据目录不存在: {ZHAOYUN_DATA_DIR}")
|
||||
return
|
||||
|
||||
print(f"✅ 赵云数据目录有效")
|
||||
for subdir, info in structure_report['subdirectories'].items():
|
||||
print(f" {subdir}: {info['total_files']} 个文件")
|
||||
|
||||
# 2. 转换日线数据(测试模式,只转换2024年的前10个文件)
|
||||
print("\n2. 转换日线数据(测试模式)...")
|
||||
converter.convert_daily_data(year=2024, limit=10)
|
||||
|
||||
# 3. 转换股票信息
|
||||
print("\n3. 转换股票信息...")
|
||||
converter.convert_stock_info()
|
||||
|
||||
# 4. 创建配置文件
|
||||
print("\n4. 创建配置文件...")
|
||||
converter.create_config_file()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("转换完成!")
|
||||
print(f"输出目录: {OUTPUT_DIR}")
|
||||
print("=" * 60)
|
||||
print("\n下一步操作:")
|
||||
print("1. 将 vnpy_local_data_adapter.py 集成到vn.py策略中")
|
||||
print("2. 配置数据路径: vnpy_data_config.json")
|
||||
print("3. 测试数据加载: python test_vnpy_data.py")
|
||||
print("4. 联系赵云将军更新数据")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
数据库配置文件
|
||||
支持 SQLite(方案零)和 PostgreSQL(方案一)
|
||||
"""
|
||||
import os
|
||||
from typing import Optional
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class DatabaseSettings(BaseSettings):
|
||||
"""数据库配置"""
|
||||
|
||||
# 数据库类型: sqlite 或 postgresql
|
||||
db_type: str = "sqlite"
|
||||
|
||||
# SQLite 配置
|
||||
sqlite_path: str = os.path.join(os.path.dirname(__file__), "data", "quant_trading.db")
|
||||
|
||||
# PostgreSQL 配置(方案一使用)
|
||||
postgres_host: str = "localhost"
|
||||
postgres_port: int = 5432
|
||||
postgres_user: str = "quant_user"
|
||||
postgres_password: str = ""
|
||||
postgres_db: str = "quant_trading"
|
||||
|
||||
# 连接池配置
|
||||
pool_size: int = 5
|
||||
max_overflow: int = 10
|
||||
pool_timeout: int = 30
|
||||
pool_recycle: int = 3600
|
||||
|
||||
# 日志配置
|
||||
echo_sql: bool = False
|
||||
|
||||
class Config:
|
||||
env_prefix = "QUANT_"
|
||||
env_file = ".env"
|
||||
|
||||
def get_database_url(self) -> str:
|
||||
"""获取数据库连接 URL"""
|
||||
if self.db_type == "sqlite":
|
||||
# 确保 SQLite 数据库目录存在
|
||||
os.makedirs(os.path.dirname(self.sqlite_path), exist_ok=True)
|
||||
return f"sqlite:///{self.sqlite_path}"
|
||||
elif self.db_type == "postgresql":
|
||||
return (
|
||||
f"postgresql+psycopg2://{self.postgres_user}:{self.postgres_password}"
|
||||
f"@{self.postgres_host}:{self.postgres_port}/{self.postgres_db}"
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"不支持的数据库类型: {self.db_type}")
|
||||
|
||||
|
||||
# 全局数据库配置实例
|
||||
db_settings = DatabaseSettings()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"数据库类型: {db_settings.db_type}")
|
||||
print(f"数据库连接 URL: {db_settings.get_database_url()}")
|
||||
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
诊断数据加载问题:检查510300.SSE数据是否正确加载
|
||||
"""
|
||||
|
||||
import sys
|
||||
import types
|
||||
|
||||
# 兼容性模块
|
||||
print("🔧 [DIAG] 加载vnpy.app兼容性模块...")
|
||||
vnpy_app_module = types.ModuleType('vnpy.app')
|
||||
sys.modules['vnpy.app'] = vnpy_app_module
|
||||
submodules = ['cta_strategy', 'cta_backtester', 'data_manager']
|
||||
for name in submodules:
|
||||
full_name = f'vnpy.app.{name}'
|
||||
submodule = types.ModuleType(full_name)
|
||||
sys.modules[full_name] = submodule
|
||||
setattr(vnpy_app_module, name, submodule)
|
||||
|
||||
from vnpy_ctastrategy import CtaTemplate, CtaStrategyApp
|
||||
from vnpy_ctastrategy import (
|
||||
CtaTemplate,
|
||||
StopOrder,
|
||||
TickData,
|
||||
BarData,
|
||||
TradeData,
|
||||
OrderData,
|
||||
BarGenerator,
|
||||
ArrayManager,
|
||||
)
|
||||
from vnpy.trader.constant import Direction, Offset, Interval
|
||||
|
||||
sys.modules['vnpy.app.cta_strategy'].CtaTemplate = CtaTemplate
|
||||
sys.modules['vnpy.app.cta_strategy'].CtaStrategyApp = CtaStrategyApp
|
||||
vnpy_app_module.CtaTemplate = CtaTemplate
|
||||
vnpy_app_module.CtaStrategyApp = CtaStrategyApp
|
||||
|
||||
from vnpy_ctabacktester import BacktesterEngine
|
||||
sys.modules['vnpy.app.cta_backtester'].BacktesterEngine = BacktesterEngine
|
||||
vnpy_app_module.BacktesterEngine = BacktesterEngine
|
||||
|
||||
print("✅ [DIAG] vnpy.app兼容性模块加载完成!")
|
||||
|
||||
from vnpy.event import EventEngine
|
||||
from vnpy.trader.engine import MainEngine
|
||||
from vnpy.trader.database import get_database
|
||||
from datetime import datetime
|
||||
import traceback
|
||||
|
||||
def str_to_interval(interval_str: str):
|
||||
"""字符串转Interval枚举"""
|
||||
mapping = {
|
||||
"1m": Interval.MINUTE,
|
||||
"min": Interval.MINUTE,
|
||||
"hour": Interval.HOUR,
|
||||
"1h": Interval.HOUR,
|
||||
"d": Interval.DAILY,
|
||||
"1d": Interval.DAILY,
|
||||
"daily": Interval.DAILY,
|
||||
"w": Interval.WEEKLY,
|
||||
"1w": Interval.WEEKLY,
|
||||
"weekly": Interval.WEEKLY,
|
||||
}
|
||||
return mapping.get(interval_str.lower(), Interval.DAILY)
|
||||
|
||||
def parse_date(date_val):
|
||||
"""解析日期"""
|
||||
print(f"🔍 [DIAG] 解析日期: date_val = {date_val}, type = {type(date_val)}")
|
||||
|
||||
date_ts = float(date_val)
|
||||
date_int = int(date_ts)
|
||||
s = str(date_int)
|
||||
|
||||
print(f"🔍 [DIAG] 处理: date_int = {date_int}, str = '{s}', length = {len(s)}")
|
||||
|
||||
if len(s) == 8:
|
||||
year = int(s[:4])
|
||||
month = int(s[4:6])
|
||||
day = int(s[6:8])
|
||||
dt = datetime(year, month, day)
|
||||
print(f"✅ [DIAG] YYYYMMDD解析: {dt}")
|
||||
return dt
|
||||
elif len(s) >= 10:
|
||||
dt = datetime.fromtimestamp(date_int)
|
||||
print(f"✅ [DIAG] Unix时间戳解析: {dt}")
|
||||
return dt
|
||||
else:
|
||||
year = int(s[:4])
|
||||
month = int(s[4:6])
|
||||
day = int(s[6:8])
|
||||
dt = datetime(year, month, day)
|
||||
print(f"⚠️ [DIAG] 默认YYYYMMDD解析: {dt}")
|
||||
return dt
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("\n=== 开始诊断数据加载问题 ===")
|
||||
|
||||
# 1. 测试日期解析
|
||||
print("\n📅 [DIAG] 测试日期解析:")
|
||||
start_ts = 1609459200
|
||||
end_ts = 1772515200
|
||||
|
||||
start_dt = parse_date(start_ts)
|
||||
end_dt = parse_date(end_ts)
|
||||
|
||||
print(f"\n📅 [DIAG] 日期解析结果:")
|
||||
print(f" start: {start_ts} -> {start_dt}")
|
||||
print(f" end: {end_ts} -> {end_dt}")
|
||||
|
||||
# 2. 测试数据库连接
|
||||
print("\n💾 [DIAG] 测试数据库连接:")
|
||||
db = get_database()
|
||||
print(f"✅ [DIAG] 获取数据库成功: {type(db)}")
|
||||
|
||||
# 3. 查询bar数据
|
||||
symbol = "510300.SSE"
|
||||
interval = str_to_interval("1d")
|
||||
|
||||
print(f"\n🔍 [DIAG] 查询bar数据: {symbol}, {interval}")
|
||||
bars = db.load_bar_data(symbol, interval, start_dt, end_dt)
|
||||
|
||||
print(f"✅ [DIAG] 查询完成:")
|
||||
print(f" 标的: {symbol}")
|
||||
print(f" 时间范围: {start_dt} ~ {end_dt}")
|
||||
print(f" 查询到bar数量: {len(bars)}")
|
||||
|
||||
if len(bars) > 0:
|
||||
print(f" 第一条: {bars[0].datetime}, close={bars[0].close_price}")
|
||||
print(f" 最后一条: {bars[-1].datetime}, close={bars[-1].close_price}")
|
||||
print(f" 间隔: {bars[0].interval}")
|
||||
else:
|
||||
print(f"❌ [DIAG] 没有查询到任何bar数据!")
|
||||
|
||||
# 尝试查找所有标的
|
||||
print(f"\n🔍 [DIAG] 尝试查找所有标的:")
|
||||
symbols = db.get_all_symbols()
|
||||
print(f"数据库中有 {len(symbols)} 个标的:")
|
||||
for s in list(symbols)[:20]:
|
||||
print(f" {s}")
|
||||
|
||||
print("\n=== 诊断完成 ===")
|
||||
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
诊断数据加载问题:检查510300.SSE数据是否正确加载
|
||||
修复方法签名
|
||||
"""
|
||||
|
||||
import sys
|
||||
import types
|
||||
|
||||
# 兼容性模块
|
||||
print("🔧 [DIAG] 加载vnpy.app兼容性模块...")
|
||||
vnpy_app_module = types.ModuleType('vnpy.app')
|
||||
sys.modules['vnpy.app'] = vnpy_app_module
|
||||
submodules = ['cta_strategy', 'cta_backtester', 'data_manager']
|
||||
for name in submodules:
|
||||
full_name = f'vnpy.app.{name}'
|
||||
submodule = types.ModuleType(full_name)
|
||||
sys.modules[full_name] = submodule
|
||||
setattr(vnpy_app_module, name, submodule)
|
||||
|
||||
from vnpy_ctastrategy import CtaTemplate, CtaStrategyApp
|
||||
from vnpy_ctastrategy import (
|
||||
CtaTemplate,
|
||||
StopOrder,
|
||||
TickData,
|
||||
BarData,
|
||||
TradeData,
|
||||
OrderData,
|
||||
BarGenerator,
|
||||
ArrayManager,
|
||||
)
|
||||
from vnpy.trader.constant import Direction, Offset, Interval
|
||||
|
||||
sys.modules['vnpy.app.cta_strategy'].CtaTemplate = CtaTemplate
|
||||
sys.modules['vnpy.app.cta_strategy'].CtaStrategyApp = CtaStrategyApp
|
||||
vnpy_app_module.CtaTemplate = CtaTemplate
|
||||
vnpy_app_module.CtaStrategyApp = CtaStrategyApp
|
||||
|
||||
from vnpy_ctabacktester import BacktesterEngine
|
||||
sys.modules['vnpy.app.cta_backtester'].BacktesterEngine = BacktesterEngine
|
||||
vnpy_app_module.BacktesterEngine = BacktesterEngine
|
||||
|
||||
print("✅ [DIAG] vnpy.app兼容性模块加载完成!")
|
||||
|
||||
from vnpy.event import EventEngine
|
||||
from vnpy.trader.engine import MainEngine
|
||||
from vnpy.trader.database import get_database
|
||||
from datetime import datetime
|
||||
import inspect
|
||||
import traceback
|
||||
|
||||
def str_to_interval(interval_str: str):
|
||||
"""字符串转Interval枚举"""
|
||||
mapping = {
|
||||
"1m": Interval.MINUTE,
|
||||
"min": Interval.MINUTE,
|
||||
"hour": Interval.HOUR,
|
||||
"1h": Interval.HOUR,
|
||||
"d": Interval.DAILY,
|
||||
"1d": Interval.DAILY,
|
||||
"daily": Interval.DAILY,
|
||||
"w": Interval.WEEKLY,
|
||||
"1w": Interval.WEEKLY,
|
||||
"weekly": Interval.WEEKLY,
|
||||
}
|
||||
return mapping.get(interval_str.lower(), Interval.DAILY)
|
||||
|
||||
def parse_date(date_val):
|
||||
"""解析日期"""
|
||||
print(f"🔍 [DIAG] 解析日期: date_val = {date_val}, type = {type(date_val)}")
|
||||
|
||||
date_ts = float(date_val)
|
||||
date_int = int(date_ts)
|
||||
s = str(date_int)
|
||||
|
||||
print(f"🔍 [DIAG] 处理: date_int = {date_int}, str = '{s}', length = {len(s)}")
|
||||
|
||||
if len(s) == 8:
|
||||
year = int(s[:4])
|
||||
month = int(s[4:6])
|
||||
day = int(s[6:8])
|
||||
dt = datetime(year, month, day)
|
||||
print(f"✅ [DIAG] YYYYMMDD解析: {dt}")
|
||||
return dt
|
||||
elif len(s) >= 10:
|
||||
dt = datetime.fromtimestamp(date_int)
|
||||
print(f"✅ [DIAG] Unix时间戳解析: {dt}")
|
||||
return dt
|
||||
else:
|
||||
year = int(s[:4])
|
||||
month = int(s[4:6])
|
||||
day = int(s[6:8])
|
||||
dt = datetime(year, month, day)
|
||||
print(f"⚠️ [DIAG] 默认YYYYMMDD解析: {dt}")
|
||||
return dt
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("\n=== 开始诊断数据加载问题 ===")
|
||||
|
||||
# 1. 测试日期解析
|
||||
print("\n📅 [DIAG] 测试日期解析:")
|
||||
start_ts = 1609459200
|
||||
end_ts = 1772515200
|
||||
|
||||
start_dt = parse_date(start_ts)
|
||||
end_dt = parse_date(end_ts)
|
||||
|
||||
print(f"\n📅 [DIAG] 日期解析结果:")
|
||||
print(f" start: {start_ts} -> {start_dt}")
|
||||
print(f" end: {end_ts} -> {end_dt}")
|
||||
|
||||
# 2. 测试数据库连接
|
||||
print("\n💾 [DIAG] 测试数据库连接:")
|
||||
db = get_database()
|
||||
print(f"✅ [DIAG] 获取数据库成功: {type(db)}")
|
||||
|
||||
# 查看load_bar_data签名
|
||||
print(f"\n🔍 [DIAG] 检查load_bar_data方法签名:")
|
||||
sig = inspect.signature(db.load_bar_data)
|
||||
print(f" {sig}")
|
||||
|
||||
# 3. 查询bar数据
|
||||
symbol = "510300.SSE"
|
||||
interval = str_to_interval("1d")
|
||||
|
||||
print(f"\n🔍 [DIAG] 查询bar数据: {symbol}, {interval}")
|
||||
print(f" 时间范围: {start_dt} ~ {end_dt}")
|
||||
|
||||
try:
|
||||
bars = db.load_bar_data(
|
||||
symbol=symbol,
|
||||
interval=interval,
|
||||
start=start_dt,
|
||||
end=end_dt
|
||||
)
|
||||
|
||||
print(f"✅ [DIAG] 查询完成:")
|
||||
print(f" 标的: {symbol}")
|
||||
print(f" 查询到bar数量: {len(bars)}")
|
||||
|
||||
if len(bars) > 0:
|
||||
print(f" 第一条: {bars[0].datetime}, close={bars[0].close_price}")
|
||||
print(f" 最后一条: {bars[-1].datetime}, close={bars[-1].close_price}")
|
||||
print(f" 间隔: {bars[0].interval}")
|
||||
else:
|
||||
print(f"❌ [DIAG] 没有查询到任何bar数据!")
|
||||
|
||||
# 尝试获取所有symbol
|
||||
print(f"\n🔍 [DIAG] 尝试获取所有标的:")
|
||||
try:
|
||||
symbols = db.get_all_symbols()
|
||||
print(f"数据库中有 {len(symbols)} 个标的:")
|
||||
for s in list(symbols)[:20]:
|
||||
print(f" {s}")
|
||||
except Exception as e:
|
||||
print(f"❌ [DIAG] 获取所有标的失败: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ [DIAG] 查询失败: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
print("\n=== 诊断完成 ===")
|
||||
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
诊断exchange提取问题:检查数据库中510300.SSE的数据
|
||||
"""
|
||||
|
||||
import sys
|
||||
import types
|
||||
|
||||
# 兼容性模块
|
||||
print("🔧 [DIAG] 加载vnpy.app兼容性模块...")
|
||||
vnpy_app_module = types.ModuleType('vnpy.app')
|
||||
sys.modules['vnpy.app'] = vnpy_app_module
|
||||
submodules = ['cta_strategy', 'cta_backtester', 'data_manager']
|
||||
for name in submodules:
|
||||
full_name = f'vnpy.app.{name}'
|
||||
submodule = types.ModuleType(full_name)
|
||||
sys.modules[full_name] = submodule
|
||||
setattr(vnpy_app_module, name, submodule)
|
||||
|
||||
from vnpy_ctastrategy import CtaTemplate, CtaStrategyApp
|
||||
from vnpy_ctastrategy import (
|
||||
CtaTemplate,
|
||||
StopOrder,
|
||||
TickData,
|
||||
BarData,
|
||||
TradeData,
|
||||
OrderData,
|
||||
BarGenerator,
|
||||
ArrayManager,
|
||||
)
|
||||
from vnpy.trader.constant import Direction, Offset, Interval, Exchange
|
||||
|
||||
sys.modules['vnpy.app.cta_strategy'].CtaTemplate = CtaTemplate
|
||||
sys.modules['vnpy.app.cta_strategy'].CtaStrategyApp = CtaStrategyApp
|
||||
vnpy_app_module.CtaTemplate = CtaTemplate
|
||||
vnpy_app_module.CtaStrategyApp = CtaStrategyApp
|
||||
|
||||
from vnpy_ctabacktester import BacktesterEngine
|
||||
sys.modules['vnpy.app.cta_backtester'].BacktesterEngine = BacktesterEngine
|
||||
vnpy_app_module.BacktesterEngine = BacktesterEngine
|
||||
|
||||
print("✅ [DIAG] vnpy.app兼容性模块加载完成!")
|
||||
|
||||
from vnpy.event import EventEngine
|
||||
from vnpy.trader.engine import MainEngine
|
||||
from vnpy.trader.database import get_database
|
||||
from datetime import datetime
|
||||
import traceback
|
||||
|
||||
def parse_date(date_val):
|
||||
"""解析日期"""
|
||||
date_ts = float(date_val)
|
||||
date_int = int(date_ts)
|
||||
s = str(date_int)
|
||||
|
||||
if len(s) == 8:
|
||||
year = int(s[:4])
|
||||
month = int(s[4:6])
|
||||
day = int(s[6:8])
|
||||
return datetime(year, month, day)
|
||||
elif len(s) >= 10:
|
||||
return datetime.fromtimestamp(date_int)
|
||||
else:
|
||||
year = int(s[:4])
|
||||
month = int(s[4:6])
|
||||
day = int(s[6:8])
|
||||
return datetime(year, month, day)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("\n=== 诊断exchange数据加载问题 ===")
|
||||
|
||||
# 测试日期解析
|
||||
start_ts = 1609459200
|
||||
end_ts = 1772515200
|
||||
start_dt = parse_date(start_ts)
|
||||
end_dt = parse_date(end_ts)
|
||||
|
||||
print(f"\n📅 时间范围:")
|
||||
print(f" start: {start_ts} -> {start_dt}")
|
||||
print(f" end: {end_ts} -> {end_dt}")
|
||||
|
||||
# 连接数据库
|
||||
db = get_database()
|
||||
print(f"\n💾 数据库连接成功: {type(db)}")
|
||||
|
||||
# 查看所有表
|
||||
print(f"\n🔍 查看数据库中的bar数据统计:")
|
||||
|
||||
# 尝试不同的symbol和exchange组合
|
||||
test_cases = [
|
||||
("510300", "SSE"),
|
||||
("510300.SSE", "SSE"),
|
||||
("510300", "XSHG"),
|
||||
("510300.SSE", "XSHG"),
|
||||
]
|
||||
|
||||
for symbol_str, exchange_str in test_cases:
|
||||
try:
|
||||
exchange = Exchange(exchange_str)
|
||||
print(f"\n▶️ 测试: symbol={symbol_str}, exchange={exchange}")
|
||||
bars = db.load_bar_data(symbol_str, exchange, Interval.DAILY, start_dt, end_dt)
|
||||
print(f" ✅ 查询成功,共 {len(bars)} 条bar数据")
|
||||
if len(bars) > 0:
|
||||
print(f" 第一条: {bars[0].datetime}, close={bars[0].close_price}")
|
||||
print(f" 最后一条: {bars[-1].datetime}, close={bars[-1].close_price}")
|
||||
except Exception as e:
|
||||
print(f" ❌ 查询失败: {e}")
|
||||
|
||||
# 尝试获取所有标的信息
|
||||
print(f"\n🔍 尝试获取所有bar数据统计:")
|
||||
try:
|
||||
# vnpy_sqlite 应该有 count_bar_data 方法
|
||||
if hasattr(db, 'count_bar_data'):
|
||||
total = db.count_bar_data()
|
||||
print(f" 数据库总共有 {total} 条bar数据")
|
||||
except Exception as e:
|
||||
print(f" 无法获取总数: {e}")
|
||||
|
||||
print("\n=== 诊断完成 ===")
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
强制重启所有服务,确保加载最新代码
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
def main():
|
||||
print("🚀 强制重启所有服务,加载最新修正代码")
|
||||
print("="*60)
|
||||
|
||||
# 杀死所有python进程
|
||||
print("🔪 杀死所有旧进程...")
|
||||
cmd = '''ssh admin@192.168.2.154 "export PATH=\\$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy python3 -c '
|
||||
import os
|
||||
import signal
|
||||
for proc in os.listdir("/proc"):
|
||||
if proc.isdigit():
|
||||
try:
|
||||
cmdline = open(f"/proc/{proc}/cmdline").read()
|
||||
if "test_server" in cmdline or "python" in cmdline and ("zmq" in cmdline or "8003" in cmdline):
|
||||
print(f"Killing {proc}: {cmdline[:60]}")
|
||||
os.kill(int(proc), signal.SIGKILL)
|
||||
except:
|
||||
pass
|
||||
'
|
||||
'''
|
||||
subprocess.run(cmd, shell=True)
|
||||
time.sleep(3)
|
||||
|
||||
# 启动RPC服务
|
||||
print("\\n🚀 启动RPC服务(修正后版本)...")
|
||||
cmd = '''ssh admin@192.168.2.154 "export PATH=\\$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy bash -c 'cd /app/scripts && python3 test_server_final_correct.py &'"'''
|
||||
subprocess.run(cmd, shell=True)
|
||||
time.sleep(3)
|
||||
|
||||
# 检查RPC是否启动
|
||||
print("\\n🔍 检查RPC是否启动...")
|
||||
cmd = '''ssh admin@192.168.2.154 "export PATH=\\$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy python3 -c '
|
||||
import psutil
|
||||
for conn in psutil.net_connections():
|
||||
if conn.laddr.port == 8003:
|
||||
print(f"✅ RPC running on port 8003, pid={conn.pid}")
|
||||
'
|
||||
'''
|
||||
subprocess.run(cmd, shell=True)
|
||||
|
||||
# 启动API服务
|
||||
print("\\n🚀 启动API服务(端口8090)...")
|
||||
cmd = '''ssh admin@192.168.2.154 "export PATH=\\$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy bash -c 'cd /app/scripts && python3 -m uvicorn backtest_api_final_correct_8090:app --host 0.0.0.0 --port 8090 &'"'''
|
||||
subprocess.run(cmd, shell=True)
|
||||
time.sleep(3)
|
||||
|
||||
# 检查API是否启动
|
||||
print("\\n🔍 检查API是否启动...")
|
||||
cmd = '''ssh admin@192.168.2.154 "export PATH=\\$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy python3 -c '
|
||||
import psutil
|
||||
for conn in psutil.net_connections():
|
||||
if conn.laddr.port == 8090:
|
||||
print(f"✅ API running on port 8090, pid={conn.pid}")
|
||||
'
|
||||
'''
|
||||
subprocess.run(cmd, shell=True)
|
||||
|
||||
print("\\n" + "="*60)
|
||||
print("✅ 强制重启完成!")
|
||||
print("最终确认代码:")
|
||||
print(" backtester_engine = BacktesterEngine(main_engine, event_engine)")
|
||||
print(" main_engine.add_app(backtester_engine)")
|
||||
print("="*60)
|
||||
print("\\n🎯 API地址: http://192.168.2.154:8090/api/backtest/run")
|
||||
print("可以开始测试了!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
import base64
|
||||
import io
|
||||
import pandas as pd
|
||||
|
||||
df = pd.read_parquet('/Users/chufeng/nas/stock-data/sanguo_quant_live/zhaoyun-data/data/raw/daily/sh510300_daily.parquet')
|
||||
buffer = io.BytesIO()
|
||||
df.to_parquet(buffer, compression='snappy')
|
||||
buffer.seek(0)
|
||||
data = buffer.getvalue()
|
||||
b64 = base64.b64encode(data).decode('utf-8')
|
||||
|
||||
output_file = '/tmp/510300_daily.b64'
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(b64)
|
||||
|
||||
print(f"Generated {output_file}")
|
||||
print(f"Size: {len(b64)} characters")
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
生成导入数据的SQL脚本
|
||||
因为scp网络有问题,直接生成SQL文本传到容器执行
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
from vnpy.trader.constant import Exchange, Interval
|
||||
|
||||
parquet_path = "/Users/chufeng/nas/stock-data/sanguo_quant_live/zhaoyun-data/data/raw/daily/sh510300_daily.parquet"
|
||||
symbol = "510300"
|
||||
exchange = Exchange.SSE
|
||||
exchange_code = exchange.value
|
||||
interval = Interval.DAILY
|
||||
interval_code = interval.value
|
||||
|
||||
df = pd.read_parquet(parquet_path)
|
||||
print(f"读取数据: {len(df)} 行")
|
||||
|
||||
output_file = "/Users/chufeng/.openclaw/workspace-jiangwei/import_data.sql"
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
f.write("BEGIN TRANSACTION;\n")
|
||||
f.write("DELETE FROM dbbardata WHERE symbol = ? AND exchange = ?;\n")
|
||||
f.write(f"-- 准备插入 {len(df)} 条数据\n")
|
||||
|
||||
for idx, row in df.iterrows():
|
||||
dt = row['trade_date']
|
||||
# 转换为Unix时间戳?不,vnpy存datetime
|
||||
dt_str = dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||
open_price = row['open']
|
||||
high_price = row['high']
|
||||
low_price = row['low']
|
||||
close_price = row['close']
|
||||
volume = row['volume']
|
||||
turnover = row['amount']
|
||||
|
||||
# vnpy_sqlite表结构dbbardata:
|
||||
# id (INTEGER PRIMARY KEY AUTOINCREMENT)
|
||||
# symbol (TEXT)
|
||||
# exchange (TEXT)
|
||||
# interval (TEXT)
|
||||
# datetime (datetime)
|
||||
# open_price (float)
|
||||
# high_price (float)
|
||||
# low_price (float)
|
||||
# close_price (float)
|
||||
# volume (int)
|
||||
# turnover (float)
|
||||
|
||||
sql = f"""INSERT INTO dbbardata (symbol, exchange, interval, datetime, open_price, high_price, low_price, close_price, volume, turnover) VALUES ('{symbol}', '{exchange_code}', '{interval_code}', '{dt_str}', {open_price}, {high_price}, {low_price}, {close_price}, {volume}, {turnover});\n"""
|
||||
f.write(sql)
|
||||
|
||||
f.write("COMMIT;\n")
|
||||
f.write("-- 导入完成\n")
|
||||
|
||||
print(f"SQL脚本生成完成: {output_file}")
|
||||
print(f"文件大小: {open(output_file).read().__len__()} bytes")
|
||||
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env python3
|
||||
"""获取完整回测结果JSON"""
|
||||
|
||||
import zmq
|
||||
import json
|
||||
import sys
|
||||
|
||||
# 关羽完整策略代码
|
||||
strategy_code = '''from vnpy_ctastrategy import (
|
||||
CtaTemplate,
|
||||
StopOrder,
|
||||
TickData,
|
||||
BarData,
|
||||
TradeData,
|
||||
OrderData,
|
||||
BarGenerator,
|
||||
ArrayManager,
|
||||
)
|
||||
from vnpy.trader.constant import Direction, Offset
|
||||
|
||||
|
||||
class SingleStockStopLossStrategy(CtaTemplate):
|
||||
"""单票固定比例止损策略 - 均线趋势跟踪+固定比例止损"""
|
||||
|
||||
author = "关羽 (云长)"
|
||||
|
||||
# 策略参数
|
||||
fast_window = 5 # 短期均线窗口
|
||||
slow_window = 20 # 长期均线窗口
|
||||
stop_loss_pct = 0.15 # 止损比例,亏损超过这个比例止损
|
||||
|
||||
# 参数列表
|
||||
parameters = ["fast_window", "slow_window", "stop_loss_pct"]
|
||||
|
||||
# 变量列表
|
||||
variables = ["fast_ma", "slow_ma", "cost_price", "in_position"]
|
||||
|
||||
def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
|
||||
"""初始化"""
|
||||
super().__init__(cta_engine, strategy_name, vt_symbol, setting)
|
||||
|
||||
self.bg = BarGenerator(self.on_bar)
|
||||
self.am = ArrayManager(max(self.slow_window + 10, 30))
|
||||
|
||||
# 均线数值
|
||||
self.fast_ma = 0.0
|
||||
self.slow_ma = 0.0
|
||||
|
||||
# 开仓成本
|
||||
self.cost_price = 0.0
|
||||
|
||||
# 是否持仓
|
||||
self.in_position = False
|
||||
|
||||
def on_init(self):
|
||||
"""初始化策略"""
|
||||
self.write_log(f"策略初始化,fast={self.fast_window}, slow={self.slow_window}, stop_loss={self.stop_loss_pct:.1%}")
|
||||
self.load_bar(self.slow_window + 10)
|
||||
self.put_event()
|
||||
|
||||
def on_start(self):
|
||||
"""启动策略"""
|
||||
self.put_event()
|
||||
|
||||
def on_stop(self):
|
||||
"""停止策略"""
|
||||
self.put_event()
|
||||
|
||||
def on_bar(self, bar):
|
||||
"""K线更新"""
|
||||
self.am.update_bar(bar)
|
||||
|
||||
if not self.am.inited:
|
||||
return
|
||||
|
||||
# 计算均线
|
||||
self.fast_ma = self.am.sma(self.fast_window)
|
||||
self.slow_ma = self.am.sma(self.slow_window)
|
||||
|
||||
# 检查止损(只有持仓时才检查)
|
||||
have_signal = True
|
||||
if self.in_position and self.cost_price > 0:
|
||||
current_drawdown = (bar.close_price - self.cost_price) / self.cost_price
|
||||
|
||||
if current_drawdown <= -self.stop_loss_pct:
|
||||
# 触发止损,全部平仓
|
||||
if self.pos > 0:
|
||||
self.sell(bar.close_price, self.pos)
|
||||
self.in_position = False
|
||||
have_signal = False
|
||||
|
||||
# 如果没有触发止损,继续处理信号
|
||||
if have_signal:
|
||||
# 均线金叉死叉信号
|
||||
if not self.in_position:
|
||||
# 金叉:短期上穿长期,开多
|
||||
if self.fast_ma > self.slow_ma:
|
||||
self.buy(bar.close_price, 10000)
|
||||
self.cost_price = bar.close_price
|
||||
self.in_position = True
|
||||
else:
|
||||
# 死叉:短期下穿长期,平多
|
||||
if self.fast_ma < self.slow_ma:
|
||||
if self.pos > 0:
|
||||
self.sell(bar.close_price, self.pos)
|
||||
self.in_position = False
|
||||
|
||||
self.put_event()
|
||||
|
||||
def on_trade(self, trade):
|
||||
"""交易成交回调"""
|
||||
self.put_event()
|
||||
|
||||
def on_order(self, order):
|
||||
"""订单回调"""
|
||||
self.put_event()
|
||||
|
||||
def on_stop_order(self, stop_order):
|
||||
"""停止单回调"""
|
||||
self.put_event()
|
||||
'''
|
||||
|
||||
# RPC请求 - 完整区间 2021-01-01 ~ 2026-03-01
|
||||
request = {
|
||||
"function": "run_strategy_backtest",
|
||||
"args": [],
|
||||
"kwargs": {
|
||||
"strategy_code": strategy_code,
|
||||
"symbol": "510300.SSE",
|
||||
"interval": "1d",
|
||||
"start": 1609459200, # 2021-01-01
|
||||
"end": 1772515200, # 2026-03-01
|
||||
"capital": 1000000,
|
||||
"rate": 3e-5,
|
||||
"slippage": 0.002,
|
||||
"size": 10000,
|
||||
"pricetick": 0.001,
|
||||
"data_source": "sqlite",
|
||||
"setting": {"stop_loss_pct": 0.15}
|
||||
}
|
||||
}
|
||||
|
||||
print("🔗 连接RPC: tcp://192.168.2.154:8008")
|
||||
context = zmq.Context()
|
||||
socket = context.socket(zmq.REQ)
|
||||
socket.connect("tcp://192.168.2.154:8008")
|
||||
socket.setsockopt(zmq.LINGER, 0)
|
||||
socket.setsockopt(zmq.RCVTIMEO, 600000) # 10分钟超时
|
||||
socket.setsockopt(zmq.SNDTIMEO, 600000)
|
||||
|
||||
print("🚀 发送请求 (全区间 2021-01-01 ~ 2026-03-01, 止损15%)")
|
||||
print(" 等待响应... 大约需要几分钟")
|
||||
|
||||
try:
|
||||
socket.send_pyobj(request)
|
||||
result = socket.recv_pyobj()
|
||||
|
||||
if "error" in result:
|
||||
print(f"\n❌ ERROR: {result['error']}")
|
||||
if "traceback" in result:
|
||||
print("\nTraceback:")
|
||||
print(result["traceback"])
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"\n✅ SUCCESS! 获取结果成功!")
|
||||
print(f" 交易笔数: {result.get('trades_count', 0)}")
|
||||
print(f" 每日数据行数: {len(result.get('daily_data', []))}")
|
||||
|
||||
# 保存完整JSON
|
||||
output_file = "/tmp/guanyu_510300_backtest_result_full.json"
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(result, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n📝 完整JSON已保存到: {output_file}")
|
||||
print(f" 文件大小: {len(json.dumps(result))} bytes")
|
||||
|
||||
# 打印统计信息
|
||||
if "statistics" in result:
|
||||
stats = result["statistics"]
|
||||
print(f"\n📊 统计指标摘要:")
|
||||
print(f" 总收益率: {stats.get('total_return', 0):.2%}")
|
||||
print(f" 年化收益率: {stats.get('annual_return', 0):.2%}")
|
||||
print(f" 最大回撤: {stats.get('max_drawdown', 0):.2%}")
|
||||
print(f" 夏普比率: {stats.get('sharpe_ratio', 0):.2f}")
|
||||
print(f" 总交易次数: {stats.get('total_trades', 0)}")
|
||||
|
||||
socket.close()
|
||||
context.term()
|
||||
|
||||
except zmq.error.Again:
|
||||
print("\n⏱️ ❌ TIMEOUT: 超过10分钟仍未完成")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n❌ ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env python3
|
||||
"""获取回测结果JSON(精简版,减少内存占用)"""
|
||||
|
||||
import zmq
|
||||
import json
|
||||
import sys
|
||||
|
||||
# 关羽完整策略代码
|
||||
strategy_code = '''from vnpy_ctastrategy import (
|
||||
CtaTemplate,
|
||||
StopOrder,
|
||||
TickData,
|
||||
BarData,
|
||||
TradeData,
|
||||
OrderData,
|
||||
BarGenerator,
|
||||
ArrayManager,
|
||||
)
|
||||
from vnpy.trader.constant import Direction, Offset
|
||||
|
||||
|
||||
class SingleStockStopLossStrategy(CtaTemplate):
|
||||
"""单票固定比例止损策略 - 均线趋势跟踪+固定比例止损"""
|
||||
|
||||
author = "关羽 (云长)"
|
||||
|
||||
# 策略参数
|
||||
fast_window = 5 # 短期均线窗口
|
||||
slow_window = 20 # 长期均线窗口
|
||||
stop_loss_pct = 0.15 # 止损比例,亏损超过这个比例止损
|
||||
|
||||
# 参数列表
|
||||
parameters = ["fast_window", "slow_window", "stop_loss_pct"]
|
||||
|
||||
# 变量列表
|
||||
variables = ["fast_ma", "slow_ma", "cost_price", "in_position"]
|
||||
|
||||
def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
|
||||
"""初始化"""
|
||||
super().__init__(cta_engine, strategy_name, vt_symbol, setting)
|
||||
|
||||
self.bg = BarGenerator(self.on_bar)
|
||||
self.am = ArrayManager(max(self.slow_window + 10, 30))
|
||||
|
||||
# 均线数值
|
||||
self.fast_ma = 0.0
|
||||
self.slow_ma = 0.0
|
||||
|
||||
# 开仓成本
|
||||
self.cost_price = 0.0
|
||||
|
||||
# 是否持仓
|
||||
self.in_position = False
|
||||
|
||||
def on_init(self):
|
||||
"""初始化策略"""
|
||||
self.write_log(f"策略初始化,fast={self.fast_window}, slow={self.slow_window}, stop_loss={self.stop_loss_pct:.1%}")
|
||||
self.load_bar(self.slow_window + 10)
|
||||
self.put_event()
|
||||
|
||||
def on_start(self):
|
||||
"""启动策略"""
|
||||
self.put_event()
|
||||
|
||||
def on_stop(self):
|
||||
"""停止策略"""
|
||||
self.put_event()
|
||||
|
||||
def on_bar(self, bar):
|
||||
"""K线更新"""
|
||||
self.am.update_bar(bar)
|
||||
|
||||
if not self.am.inited:
|
||||
return
|
||||
|
||||
# 计算均线
|
||||
self.fast_ma = self.am.sma(self.fast_window)
|
||||
self.slow_ma = self.am.sma(self.slow_window)
|
||||
|
||||
# 检查止损(只有持仓时才检查)
|
||||
have_signal = True
|
||||
if self.in_position and self.cost_price > 0:
|
||||
current_drawdown = (bar.close_price - self.cost_price) / self.cost_price
|
||||
|
||||
if current_drawdown <= -self.stop_loss_pct:
|
||||
# 触发止损,全部平仓
|
||||
if self.pos > 0:
|
||||
self.sell(bar.close_price, self.pos)
|
||||
self.in_position = False
|
||||
have_signal = False
|
||||
|
||||
# 如果没有触发止损,继续处理信号
|
||||
if have_signal:
|
||||
# 均线金叉死叉信号
|
||||
if not self.in_position:
|
||||
# 金叉:短期上穿长期,开多
|
||||
if self.fast_ma > self.slow_ma:
|
||||
self.buy(bar.close_price, 10000)
|
||||
self.cost_price = bar.close_price
|
||||
self.in_position = True
|
||||
else:
|
||||
# 死叉:短期下穿长期,平多
|
||||
if self.fast_ma < self.slow_ma:
|
||||
if self.pos > 0:
|
||||
self.sell(bar.close_price, self.pos)
|
||||
self.in_position = False
|
||||
|
||||
self.put_event()
|
||||
|
||||
def on_trade(self, trade):
|
||||
"""交易成交回调"""
|
||||
self.put_event()
|
||||
|
||||
def on_order(self, order):
|
||||
"""订单回调"""
|
||||
self.put_event()
|
||||
|
||||
def on_stop_order(self, stop_order):
|
||||
"""停止单回调"""
|
||||
self.put_event()
|
||||
'''
|
||||
|
||||
# RPC请求 - 完整区间 2021-01-01 ~ 2026-03-01
|
||||
request = {
|
||||
"function": "run_strategy_backtest",
|
||||
"args": [],
|
||||
"kwargs": {
|
||||
"strategy_code": strategy_code,
|
||||
"symbol": "510300.SSE",
|
||||
"interval": "1d",
|
||||
"start": 1609459200, # 2021-01-01
|
||||
"end": 1772515200, # 2026-03-01
|
||||
"capital": 1000000,
|
||||
"rate": 3e-5,
|
||||
"slippage": 0.002,
|
||||
"size": 10000,
|
||||
"pricetick": 0.001,
|
||||
"data_source": "sqlite",
|
||||
"setting": {"stop_loss_pct": 0.15}
|
||||
}
|
||||
}
|
||||
|
||||
print("🔗 连接RPC: tcp://127.0.0.1:8008 (容器内部)")
|
||||
context = zmq.Context()
|
||||
socket = context.socket(zmq.REQ)
|
||||
socket.connect("tcp://127.0.0.1:8008")
|
||||
socket.setsockopt(zmq.LINGER, 0)
|
||||
socket.setsockopt(zmq.RCVTIMEO, 900000) # 15分钟超时
|
||||
socket.setsockopt(zmq.SNDTIMEO, 900000)
|
||||
|
||||
print("🚀 发送请求 (全区间 2021-01-01 ~ 2026-03-01, 止损15%)")
|
||||
print(" 等待响应... 大约需要几分钟")
|
||||
|
||||
try:
|
||||
socket.send_pyobj(request)
|
||||
result = socket.recv_pyobj()
|
||||
|
||||
if "error" in result:
|
||||
print(f"\n❌ ERROR: {result['error']}")
|
||||
if "traceback" in result:
|
||||
print("\nTraceback:")
|
||||
print(result["traceback"])
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"\n✅ SUCCESS! 回测完成!")
|
||||
print(f" 交易笔数: {result.get('trades_count', 0)}")
|
||||
|
||||
# 统计数据就是完整的,不需要精简
|
||||
# daily_data只保留必要字段,减少大小
|
||||
daily_data = result.get('daily_data', [])
|
||||
print(f" 每日数据点数: {len(daily_data)}")
|
||||
|
||||
# 保存完整JSON(包含所有你需要的数据)
|
||||
output_file = "/app/guanyu_510300_backtest_result.json"
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(result, f, ensure_ascii=False, indent=2)
|
||||
|
||||
file_size = len(json.dumps(result))
|
||||
print(f"\n📝 完整JSON已保存到容器: {output_file}")
|
||||
print(f" 文件大小: {file_size} bytes ({file_size / 1024 / 1024:.2f} MB)")
|
||||
|
||||
# 打印统计信息
|
||||
if "statistics" in result:
|
||||
stats = result["statistics"]
|
||||
print(f"\n📊 绩效指标:")
|
||||
print(f" 总收益率: {stats.get('total_return', 0):.2%}")
|
||||
print(f" 年化收益率: {stats.get('annual_return', 0):.2%}")
|
||||
print(f" 最大回撤: {stats.get('max_drawdown', 0):.2%}")
|
||||
print(f" 夏普比率: {stats.get('sharpe_ratio', 0):.2f}")
|
||||
print(f" 卡玛比率: {stats.get('calmar_ratio', 0):.2f}")
|
||||
print(f" 总交易次数: {stats.get('total_trades', 0)}")
|
||||
print(f" 胜率: {stats.get('win_rate', 0):.2%}")
|
||||
if "profit_loss_ratio" in stats:
|
||||
print(f" 盈亏比: {stats.get('profit_loss_ratio', 0):.2f}")
|
||||
|
||||
if "trades" in result:
|
||||
trades = result["trades"]
|
||||
print(f"\n📝 交易记录: 共 {len(trades)} 笔")
|
||||
if len(trades) > 0:
|
||||
print(f" 前5笔:")
|
||||
for idx, trade in enumerate(trades[:5], 1):
|
||||
dt = trade.get('datetime', '')[:10] if trade.get('datetime') else ''
|
||||
direction = trade.get('direction', '').split('.')[-1] if '.' in trade.get('direction', '') else trade.get('direction', '')
|
||||
price = trade.get('price', 0)
|
||||
volume = trade.get('volume', 0)
|
||||
print(f" {idx}. {dt} {direction} @ {price:.2f} × {volume}")
|
||||
|
||||
socket.close()
|
||||
context.term()
|
||||
print("\n✅ 完成!JSON已保存到容器。")
|
||||
|
||||
except zmq.error.Again:
|
||||
print("\n⏱️ ❌ TIMEOUT: 超过15分钟仍未完成")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n❌ ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,235 @@
|
||||
#!/usr/bin/env python3
|
||||
"""获取回测结果JSON(精简版,修复numpy int64序列化问题)"""
|
||||
|
||||
import zmq
|
||||
import json
|
||||
import sys
|
||||
import numpy as np
|
||||
|
||||
# 自定义JSON编码器,处理numpy类型
|
||||
class NumpyEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, (np.integer, np.int32, np.int64)):
|
||||
return int(obj)
|
||||
elif isinstance(obj, (np.floating, np.float32, np.float64)):
|
||||
return float(obj)
|
||||
elif isinstance(obj, np.ndarray):
|
||||
return obj.tolist()
|
||||
return super().default(obj)
|
||||
|
||||
# 关羽完整策略代码
|
||||
strategy_code = '''from vnpy_ctastrategy import (
|
||||
CtaTemplate,
|
||||
StopOrder,
|
||||
TickData,
|
||||
BarData,
|
||||
TradeData,
|
||||
OrderData,
|
||||
BarGenerator,
|
||||
ArrayManager,
|
||||
)
|
||||
from vnpy.trader.constant import Direction, Offset
|
||||
|
||||
|
||||
class SingleStockStopLossStrategy(CtaTemplate):
|
||||
"""单票固定比例止损策略 - 均线趋势跟踪+固定比例止损"""
|
||||
|
||||
author = "关羽 (云长)"
|
||||
|
||||
# 策略参数
|
||||
fast_window = 5 # 短期均线窗口
|
||||
slow_window = 20 # 长期均线窗口
|
||||
stop_loss_pct = 0.15 # 止损比例,亏损超过这个比例止损
|
||||
|
||||
# 参数列表
|
||||
parameters = ["fast_window", "slow_window", "stop_loss_pct"]
|
||||
|
||||
# 变量列表
|
||||
variables = ["fast_ma", "slow_ma", "cost_price", "in_position"]
|
||||
|
||||
def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
|
||||
"""初始化"""
|
||||
super().__init__(cta_engine, strategy_name, vt_symbol, setting)
|
||||
|
||||
self.bg = BarGenerator(self.on_bar)
|
||||
self.am = ArrayManager(max(self.slow_window + 10, 30))
|
||||
|
||||
# 均线数值
|
||||
self.fast_ma = 0.0
|
||||
self.slow_ma = 0.0
|
||||
|
||||
# 开仓成本
|
||||
self.cost_price = 0.0
|
||||
|
||||
# 是否持仓
|
||||
self.in_position = False
|
||||
|
||||
def on_init(self):
|
||||
"""初始化策略"""
|
||||
self.write_log(f"策略初始化,fast={self.fast_window}, slow={self.slow_window}, stop_loss={self.stop_loss_pct:.1%}")
|
||||
self.load_bar(self.slow_window + 10)
|
||||
self.put_event()
|
||||
|
||||
def on_start(self):
|
||||
"""启动策略"""
|
||||
self.put_event()
|
||||
|
||||
def on_stop(self):
|
||||
"""停止策略"""
|
||||
self.put_event()
|
||||
|
||||
def on_bar(self, bar):
|
||||
"""K线更新"""
|
||||
self.am.update_bar(bar)
|
||||
|
||||
if not self.am.inited:
|
||||
return
|
||||
|
||||
# 计算均线
|
||||
self.fast_ma = self.am.sma(self.fast_window)
|
||||
self.slow_ma = self.am.sma(self.slow_window)
|
||||
|
||||
# 检查止损(只有持仓时才检查)
|
||||
have_signal = True
|
||||
if self.in_position and self.cost_price > 0:
|
||||
current_drawdown = (bar.close_price - self.cost_price) / self.cost_price
|
||||
|
||||
if current_drawdown <= -self.stop_loss_pct:
|
||||
# 触发止损,全部平仓
|
||||
if self.pos > 0:
|
||||
self.sell(bar.close_price, self.pos)
|
||||
self.in_position = False
|
||||
have_signal = False
|
||||
|
||||
# 如果没有触发止损,继续处理信号
|
||||
if have_signal:
|
||||
# 均线金叉死叉信号
|
||||
if not self.in_position:
|
||||
# 金叉:短期上穿长期,开多
|
||||
if self.fast_ma > self.slow_ma:
|
||||
self.buy(bar.close_price, 10000)
|
||||
self.cost_price = bar.close_price
|
||||
self.in_position = True
|
||||
else:
|
||||
# 死叉:短期下穿长期,平多
|
||||
if self.fast_ma < self.slow_ma:
|
||||
if self.pos > 0:
|
||||
self.sell(bar.close_price, self.pos)
|
||||
self.in_position = False
|
||||
|
||||
self.put_event()
|
||||
|
||||
def on_trade(self, trade):
|
||||
"""交易成交回调"""
|
||||
self.put_event()
|
||||
|
||||
def on_order(self, order):
|
||||
"""订单回调"""
|
||||
self.put_event()
|
||||
|
||||
def on_stop_order(self, stop_order):
|
||||
"""停止单回调"""
|
||||
self.put_event()
|
||||
'''
|
||||
|
||||
# RPC请求 - 完整区间 2021-01-01 ~ 2026-03-01
|
||||
request = {
|
||||
"function": "run_strategy_backtest",
|
||||
"args": [],
|
||||
"kwargs": {
|
||||
"strategy_code": strategy_code,
|
||||
"symbol": "510300.SSE",
|
||||
"interval": "1d",
|
||||
"start": 1609459200, # 2021-01-01
|
||||
"end": 1772515200, # 2026-03-01
|
||||
"capital": 1000000,
|
||||
"rate": 3e-5,
|
||||
"slippage": 0.002,
|
||||
"size": 10000,
|
||||
"pricetick": 0.001,
|
||||
"data_source": "sqlite",
|
||||
"setting": {"stop_loss_pct": 0.15}
|
||||
}
|
||||
}
|
||||
|
||||
print("🔗 连接RPC: tcp://127.0.0.1:8008 (容器内部)")
|
||||
context = zmq.Context()
|
||||
socket = context.socket(zmq.REQ)
|
||||
socket.connect("tcp://127.0.0.1:8008")
|
||||
socket.setsockopt(zmq.LINGER, 0)
|
||||
socket.setsockopt(zmq.RCVTIMEO, 900000) # 15分钟超时
|
||||
socket.setsockopt(zmq.SNDTIMEO, 900000)
|
||||
|
||||
print("🚀 发送请求 (全区间 2021-01-01 ~ 2026-03-01, 止损15%)")
|
||||
print(" 等待响应... 大约需要几分钟")
|
||||
|
||||
try:
|
||||
socket.send_pyobj(request)
|
||||
result = socket.recv_pyobj()
|
||||
|
||||
if "error" in result:
|
||||
print(f"\n❌ ERROR: {result['error']}")
|
||||
if "traceback" in result:
|
||||
print("\nTraceback:")
|
||||
print(result["traceback"])
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"\n✅ SUCCESS! 回测完成!")
|
||||
print(f" 交易笔数: {result.get('trades_count', 0)}")
|
||||
|
||||
# 统计数据就是完整的,不需要精简
|
||||
# daily_data只保留必要字段,减少大小
|
||||
daily_data = result.get('daily_data', [])
|
||||
print(f" 每日数据点数: {len(daily_data)}")
|
||||
|
||||
# 保存完整JSON(包含所有你需要的数据)
|
||||
output_file = "/app/guanyu_510300_backtest_result.json"
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(result, f, ensure_ascii=False, indent=2, cls=NumpyEncoder)
|
||||
|
||||
# 获取文件大小
|
||||
import os
|
||||
file_size = os.path.getsize(output_file)
|
||||
print(f"\n📝 完整JSON已保存到容器: {output_file}")
|
||||
print(f" 文件大小: {file_size} bytes ({file_size / 1024 / 1024:.2f} MB)")
|
||||
|
||||
# 打印统计信息
|
||||
if "statistics" in result:
|
||||
stats = result["statistics"]
|
||||
print(f"\n📊 绩效指标:")
|
||||
print(f" 总收益率: {float(stats.get('total_return', 0)):.2%}")
|
||||
print(f" 年化收益率: {float(stats.get('annual_return', 0)):.2%}")
|
||||
print(f" 最大回撤: {float(stats.get('max_drawdown', 0)):.2%}")
|
||||
print(f" 夏普比率: {float(stats.get('sharpe_ratio', 0)):.2f}")
|
||||
if 'calmar_ratio' in stats:
|
||||
print(f" 卡玛比率: {float(stats.get('calmar_ratio', 0)):.2f}")
|
||||
print(f" 总交易次数: {int(stats.get('total_trades', 0))}")
|
||||
if 'win_rate' in stats:
|
||||
print(f" 胜率: {float(stats.get('win_rate', 0)):.2%}")
|
||||
if 'profit_loss_ratio' in stats:
|
||||
print(f" 盈亏比: {float(stats.get('profit_loss_ratio', 0)):.2f}")
|
||||
|
||||
if "trades" in result:
|
||||
trades = result["trades"]
|
||||
print(f"\n📝 交易记录: 共 {len(trades)} 笔")
|
||||
if len(trades) > 0:
|
||||
print(f" 前5笔:")
|
||||
for idx, trade in enumerate(trades[:5], 1):
|
||||
dt = trade.get('datetime', '')[:10] if trade.get('datetime') else ''
|
||||
direction = trade.get('direction', '').split('.')[-1] if '.' in trade.get('direction', '') else trade.get('direction', '')
|
||||
price = float(trade.get('price', 0))
|
||||
volume = int(trade.get('volume', 0))
|
||||
print(f" {idx}. {dt} {direction} @ {price:.2f} × {volume}")
|
||||
|
||||
socket.close()
|
||||
context.term()
|
||||
print("\n✅ 完成!JSON已保存到容器。")
|
||||
|
||||
except zmq.error.Again:
|
||||
print("\n⏱️ ❌ TIMEOUT: 超过15分钟仍未完成")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n❌ ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Git Webhook 自动触发服务
|
||||
GitHub/Gitee push 代码后,自动触发全流程部署回测
|
||||
完全无人值守!
|
||||
|
||||
启动:
|
||||
nohup python git_webhook_server.py > webhook.log 2>&1 &
|
||||
|
||||
测试:
|
||||
curl http://your-ip:8899/webhook
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
from flask import Flask, request, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
CI_CD_SCRIPT = "/Users/chufeng/.openclaw/workspace-jiangwei/sanguo_nas_ci_cd.sh"
|
||||
SECRET_TOKEN = "sanguo-quant-2026" # 修改为您的token
|
||||
|
||||
|
||||
@app.route("/webhook", methods=["POST"])
|
||||
def webhook():
|
||||
# 验证签名
|
||||
signature = request.headers.get("X-Hub-Signature-256", "")
|
||||
# 这里简化处理,如果需要可以验证签名
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("📦 收到 Git push webhook")
|
||||
print("🕐 时间: {}".format(datetime.now()))
|
||||
print("🚀 触发全流程自动化部署回测...")
|
||||
print("="*60 + "\n")
|
||||
|
||||
try:
|
||||
# 执行全流程 CI/CD
|
||||
result = subprocess.run([CI_CD_SCRIPT], capture_output=False)
|
||||
|
||||
if result.returncode == 0:
|
||||
print("\n✅ 自动化部署回测完成!\n")
|
||||
return jsonify({"status": "ok", "message": "自动化部署回测已完成"})
|
||||
else:
|
||||
print("\n❌ 部署回测失败,请检查日志\n")
|
||||
return jsonify({"status": "error", "message": "部署回测失败"}), 500
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 异常: {e}\n")
|
||||
return jsonify({"status": "error", "message": str(e)}), 500
|
||||
|
||||
|
||||
@app.route("/", methods=["GET"])
|
||||
def index():
|
||||
return """
|
||||
<h1>sanguo_quant_live Git Webhook</h1>
|
||||
<p>Status: running</p>
|
||||
<p>Endpoint: <code>/webhook</code></p>
|
||||
<p>全自动部署回测服务正在运行 ✅</p>
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from datetime import datetime
|
||||
print("============================================")
|
||||
print(" sanguo_quant_live Git Webhook 服务")
|
||||
print("============================================")
|
||||
print()
|
||||
print(f"🎯 监听端口: 0.0.0.0:8899")
|
||||
print(f"📜 CI/CD 脚本: {CI_CD_SCRIPT}")
|
||||
print()
|
||||
print("🚀 等待 Git push 触发自动化部署...")
|
||||
print()
|
||||
app.run(host="0.0.0.0", port=8899, debug=False)
|
||||
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
导入赵云提供的 510300.SSE 日线数据到 vnpy sqlite 数据库
|
||||
按照赵云提供的步骤:
|
||||
1. 读取 parquet 文件
|
||||
2. 转换为 vnpy BarData
|
||||
3. 写入数据库
|
||||
"""
|
||||
|
||||
import sys
|
||||
import types
|
||||
|
||||
# 兼容性模块
|
||||
print("🔧 [IMPORT] 加载vnpy.app兼容性模块...")
|
||||
vnpy_app_module = types.ModuleType('vnpy.app')
|
||||
sys.modules['vnpy.app'] = vnpy_app_module
|
||||
submodules = ['cta_strategy', 'cta_backtester', 'data_manager']
|
||||
for name in submodules:
|
||||
full_name = f'vnpy.app.{name}'
|
||||
submodule = types.ModuleType(full_name)
|
||||
sys.modules[full_name] = submodule
|
||||
setattr(vnpy_app_module, name, submodule)
|
||||
|
||||
from vnpy_ctastrategy import (
|
||||
CtaTemplate,
|
||||
StopOrder,
|
||||
TickData,
|
||||
BarData,
|
||||
TradeData,
|
||||
OrderData,
|
||||
BarGenerator,
|
||||
ArrayManager,
|
||||
)
|
||||
from vnpy.trader.constant import Direction, Offset, Interval, Exchange
|
||||
|
||||
sys.modules['vnpy.app.cta_strategy'].CtaTemplate = CtaTemplate
|
||||
vnpy_app_module.CtaTemplate = CtaTemplate
|
||||
|
||||
from vnpy_ctabacktester import BacktesterEngine
|
||||
sys.modules['vnpy.app.cta_backtester'].BacktesterEngine = BacktesterEngine
|
||||
vnpy_app_module.BacktesterEngine = BacktesterEngine
|
||||
|
||||
print("✅ [IMPORT] vnpy.app兼容性模块加载完成!")
|
||||
|
||||
import pandas as pd
|
||||
from vnpy.trader.object import BarData
|
||||
from vnpy.trader.database import get_database
|
||||
|
||||
def main():
|
||||
print("\n🚀 [IMPORT] 开始导入 510300.SSE 日线数据")
|
||||
|
||||
# 1. 读取parquet文件
|
||||
parquet_path = "/Users/chufeng/nas/stock-data/sanguo_quant_live/zhaoyun-data/data/raw/daily/sh510300_daily.parquet"
|
||||
print(f"\n📖 [IMPORT] 读取数据: {parquet_path}")
|
||||
|
||||
df = pd.read_parquet(parquet_path)
|
||||
print(f"✅ [IMPORT] 读取完成,共 {len(df)} 行")
|
||||
print(f" 时间范围: {df['trade_date'].min()} ~ {df['trade_date'].max()}")
|
||||
print(f" 列: {list(df.columns)}")
|
||||
|
||||
# 2. 转换为 vnpy BarData
|
||||
print(f"\n🔧 [IMPORT] 转换为 BarData...")
|
||||
bars = []
|
||||
for idx, row in df.iterrows():
|
||||
bar = BarData(
|
||||
symbol="510300",
|
||||
exchange=Exchange.SSE,
|
||||
interval=Interval.DAILY,
|
||||
datetime=row["trade_date"],
|
||||
open_price=row["open"],
|
||||
high_price=row["high"],
|
||||
low_price=row["low"],
|
||||
close_price=row["close"],
|
||||
volume=row["volume"],
|
||||
turnover=row["amount"],
|
||||
gateway_name="DATA"
|
||||
)
|
||||
bars.append(bar)
|
||||
|
||||
print(f"✅ [IMPORT] 转换完成,共 {len(bars)} 个BarData")
|
||||
|
||||
# 3. 写入数据库
|
||||
print(f"\n💾 [IMPORT] 写入数据库...")
|
||||
db = get_database()
|
||||
|
||||
# 先统计原有数据
|
||||
from datetime import datetime
|
||||
start_dt = datetime(2012, 5, 28)
|
||||
end_dt = datetime(2026, 3, 27)
|
||||
existing = db.load_bar_data("510300", Exchange.SSE, Interval.DAILY, start_dt, end_dt)
|
||||
print(f"⚠️ [IMPORT] 原有数据: {len(existing)} 条在这个范围内")
|
||||
|
||||
# 保存数据
|
||||
db.save_bar_data(bars)
|
||||
print(f"✅ [IMPORT] 保存完成,共写入 {len(bars)} 条")
|
||||
|
||||
# 验证写入
|
||||
existing_after = db.load_bar_data("510300", Exchange.SSE, Interval.DAILY, start_dt, end_dt)
|
||||
print(f"✅ [IMPORT] 写入后验证: {len(existing_after)} 条在这个范围内")
|
||||
|
||||
# 验证目标时间范围
|
||||
print(f"\n✅ [IMPORT] 验证目标时间范围 2021-01-01 ~ 2026-03-01:")
|
||||
start_target = datetime(2021, 1, 1)
|
||||
end_target = datetime(2026, 3, 1)
|
||||
target_bars = db.load_bar_data("510300", Exchange.SSE, Interval.DAILY, start_target, end_target)
|
||||
print(f" 找到 {len(target_bars)} 条数据")
|
||||
if len(target_bars) > 0:
|
||||
print(f" 第一条: {target_bars[0].datetime}")
|
||||
print(f" 最后一条: {target_bars[-1].datetime}")
|
||||
print(f" ✅ 满足需求!")
|
||||
|
||||
print("\n🎉 [IMPORT] 导入完成!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
直接在容器内导入数据,从容器外复制parquet内容通过base64
|
||||
"""
|
||||
|
||||
import sys
|
||||
import base64
|
||||
import io
|
||||
import pandas as pd
|
||||
from vnpy.trader.object import BarData
|
||||
from vnpy.trader.constant import Exchange, Interval
|
||||
from vnpy.trader.database import get_database
|
||||
|
||||
def main():
|
||||
print("🚀 [IMPORT] 开始导入 510300.SSE 日线数据")
|
||||
|
||||
# 读取base64编码的parquet数据
|
||||
if len(sys.argv) > 1:
|
||||
b64_data = sys.argv[1]
|
||||
binary_data = base64.b64decode(b64_data)
|
||||
buffer = io.BytesIO(binary_data)
|
||||
df = pd.read_parquet(buffer)
|
||||
else:
|
||||
print("❌ 请提供base64编码的parquet数据")
|
||||
return
|
||||
|
||||
print(f"✅ [IMPORT] 读取完成,共 {len(df)} 行")
|
||||
print(f" 时间范围: {df['trade_date'].min()} ~ {df['trade_date'].max()}")
|
||||
|
||||
# 转换为BarData
|
||||
print(f"🔧 [IMPORT] 转换为 BarData...")
|
||||
bars = []
|
||||
for idx, row in df.iterrows():
|
||||
bar = BarData(
|
||||
symbol="510300",
|
||||
exchange=Exchange.SSE,
|
||||
interval=Interval.DAILY,
|
||||
datetime=row["trade_date"],
|
||||
open_price=row["open"],
|
||||
high_price=row["high"],
|
||||
low_price=row["low"],
|
||||
close_price=row["close"],
|
||||
volume=row["volume"],
|
||||
turnover=row["amount"],
|
||||
gateway_name="DATA"
|
||||
)
|
||||
bars.append(bar)
|
||||
|
||||
print(f"✅ [IMPORT] 转换完成,共 {len(bars)} 个BarData")
|
||||
|
||||
# 写入数据库
|
||||
print(f"💾 [IMPORT] 写入数据库...")
|
||||
db = get_database()
|
||||
|
||||
# 保存数据
|
||||
db.save_bar_data(bars)
|
||||
print(f"✅ [IMPORT] 保存完成,共写入 {len(bars)} 条")
|
||||
|
||||
# 验证
|
||||
from datetime import datetime
|
||||
start_target = datetime(2021, 1, 1)
|
||||
end_target = datetime(2026, 3, 1)
|
||||
target_bars = db.load_bar_data("510300", Exchange.SSE, Interval.DAILY, start_target, end_target)
|
||||
print(f"✅ [IMPORT] 验证目标区间 2021-01-01 ~ 2026-03-01: 找到 {len(target_bars)} 条数据")
|
||||
|
||||
if len(target_bars) > 0:
|
||||
print(f" 第一条: {target_bars[0].datetime}")
|
||||
print(f" 最后一条: {target_bars[-1].datetime}")
|
||||
print(f" ✅ 满足需求!导入成功!")
|
||||
|
||||
print("\n🎉 [IMPORT] 导入完成!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
从CSV导入数据到vnpy sqlite数据库
|
||||
容器里pandas可以读csv,不需要额外依赖
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
from vnpy.trader.object import BarData
|
||||
from vnpy.trader.constant import Exchange, Interval
|
||||
from vnpy.trader.database import get_database
|
||||
from datetime import datetime
|
||||
|
||||
def main():
|
||||
print("🚀 [IMPORT] 开始从CSV导入 510300.SSE 日线数据")
|
||||
|
||||
# 读取CSV
|
||||
csv_file = "/tmp/510300_daily.csv"
|
||||
df = pd.read_csv(csv_file)
|
||||
|
||||
# 转换trade_date为datetime
|
||||
df['trade_date'] = pd.to_datetime(df['trade_date'])
|
||||
|
||||
print(f"✅ [IMPORT] 读取CSV完成,共 {len(df)} 行")
|
||||
print(f" 时间范围: {df['trade_date'].min()} ~ {df['trade_date'].max()}")
|
||||
|
||||
# 转换为BarData
|
||||
print(f"🔧 [IMPORT] 转换为 BarData...")
|
||||
bars = []
|
||||
for idx, row in df.iterrows():
|
||||
# vnpy需要datetime对象,不是pandas Timestamp,并且处理时区问题
|
||||
dt = row["trade_date"]
|
||||
if hasattr(dt, 'to_pydatetime'):
|
||||
dt = dt.to_pydatetime()
|
||||
bar = BarData(
|
||||
symbol="510300",
|
||||
exchange=Exchange.SSE,
|
||||
interval=Interval.DAILY,
|
||||
datetime=dt,
|
||||
open_price=row["open"],
|
||||
high_price=row["high"],
|
||||
low_price=row["low"],
|
||||
close_price=row["close"],
|
||||
volume=row["volume"],
|
||||
turnover=row["amount"],
|
||||
gateway_name="DATA"
|
||||
)
|
||||
bars.append(bar)
|
||||
|
||||
print(f"✅ [IMPORT] 转换完成,共 {len(bars)} 个BarData")
|
||||
|
||||
# 写入数据库
|
||||
print(f"💾 [IMPORT] 写入数据库...")
|
||||
db = get_database()
|
||||
|
||||
# 保存数据
|
||||
db.save_bar_data(bars)
|
||||
print(f"✅ [IMPORT] 保存完成,共写入 {len(bars)} 条")
|
||||
|
||||
# 验证
|
||||
start_target = datetime(2021, 1, 1)
|
||||
end_target = datetime(2026, 3, 1)
|
||||
target_bars = db.load_bar_data("510300", Exchange.SSE, Interval.DAILY, start_target, end_target)
|
||||
print(f"✅ [IMPORT] 验证目标区间 2021-01-01 ~ 2026-03-01: 找到 {len(target_bars)} 条数据")
|
||||
|
||||
if len(target_bars) > 0:
|
||||
print(f" 第一条: {target_bars[0].datetime}")
|
||||
print(f" 最后一条: {target_bars[-1].datetime}")
|
||||
print(f" ✅ 满足需求!导入成功!")
|
||||
|
||||
print("\n🎉 [IMPORT] 导入完成!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
从容器内的base64文件读取数据导入
|
||||
"""
|
||||
|
||||
import sys
|
||||
import base64
|
||||
import io
|
||||
import pandas as pd
|
||||
from vnpy.trader.object import BarData
|
||||
from vnpy.trader.constant import Exchange, Interval
|
||||
from vnpy.trader.database import get_database
|
||||
|
||||
def main():
|
||||
print("🚀 [IMPORT] 开始导入 510300.SSE 日线数据")
|
||||
|
||||
# 读取base64文件
|
||||
b64_file = "/tmp/510300_daily.b64"
|
||||
with open(b64_file, 'r') as f:
|
||||
b64_data = f.read().strip()
|
||||
|
||||
print(f"✅ [IMPORT] 读取base64完成,长度: {len(b64_data)}")
|
||||
|
||||
binary_data = base64.b64decode(b64_data)
|
||||
buffer = io.BytesIO(binary_data)
|
||||
df = pd.read_parquet(buffer)
|
||||
|
||||
print(f"✅ [IMPORT] 读取parquet完成,共 {len(df)} 行")
|
||||
print(f" 时间范围: {df['trade_date'].min()} ~ {df['trade_date'].max()}")
|
||||
|
||||
# 转换为BarData
|
||||
print(f"🔧 [IMPORT] 转换为 BarData...")
|
||||
bars = []
|
||||
for idx, row in df.iterrows():
|
||||
bar = BarData(
|
||||
symbol="510300",
|
||||
exchange=Exchange.SSE,
|
||||
interval=Interval.DAILY,
|
||||
datetime=row["trade_date"],
|
||||
open_price=row["open"],
|
||||
high_price=row["high"],
|
||||
low_price=row["low"],
|
||||
close_price=row["close"],
|
||||
volume=row["volume"],
|
||||
turnover=row["amount"],
|
||||
gateway_name="DATA"
|
||||
)
|
||||
bars.append(bar)
|
||||
|
||||
print(f"✅ [IMPORT] 转换完成,共 {len(bars)} 个BarData")
|
||||
|
||||
# 写入数据库
|
||||
print(f"💾 [IMPORT] 写入数据库...")
|
||||
db = get_database()
|
||||
|
||||
# 保存数据
|
||||
db.save_bar_data(bars)
|
||||
print(f"✅ [IMPORT] 保存完成,共写入 {len(bars)} 条")
|
||||
|
||||
# 验证
|
||||
from datetime import datetime
|
||||
start_target = datetime(2021, 1, 1)
|
||||
end_target = datetime(2026, 3, 1)
|
||||
target_bars = db.load_bar_data("510300", Exchange.SSE, Interval.DAILY, start_target, end_target)
|
||||
print(f"✅ [IMPORT] 验证目标区间 2021-01-01 ~ 2026-03-01: 找到 {len(target_bars)} 条数据")
|
||||
|
||||
if len(target_bars) > 0:
|
||||
print(f" 第一条: {target_bars[0].datetime}")
|
||||
print(f" 最后一条: {target_bars[-1].datetime}")
|
||||
print(f" ✅ 满足需求!导入成功!")
|
||||
|
||||
print("\n🎉 [IMPORT] 导入完成!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
将赵云将军下载的parquet数据导入到vn.py数据库
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import sqlite3
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
def main():
|
||||
print("🚀 将parquet数据导入到vn.py数据库")
|
||||
print("="*60)
|
||||
|
||||
# 配置
|
||||
parquet_path = "/Users/chufeng/nas/stock-data/sanguo_quant_live/zhaoyun-data/data/raw/daily/sh510300_daily.parquet"
|
||||
db_path = "/Users/chufeng/.openclaw/workspace-zhaoyun/zhaoyun-data/data/database_test.db"
|
||||
symbol = "510300.SSE"
|
||||
exchange = "SSE"
|
||||
interval = "1d"
|
||||
|
||||
print(f"源数据: {parquet_path}")
|
||||
print(f"目标数据库: {db_path}")
|
||||
print(f"标的: {symbol}")
|
||||
|
||||
# 读取parquet
|
||||
print("\n📥 读取parquet数据...")
|
||||
df = pd.read_parquet(parquet_path)
|
||||
print(f" 读取成功: {len(df)} 行")
|
||||
|
||||
# 创建数据库
|
||||
print(f"\n💾 创建vn.py数据库...")
|
||||
|
||||
if os.path.exists(db_path):
|
||||
os.remove(db_path)
|
||||
print(f" 删除旧数据库")
|
||||
|
||||
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 创建vn.py标准表结构
|
||||
cursor.execute("""
|
||||
CREATE TABLE dbbardata (
|
||||
symbol TEXT NOT NULL,
|
||||
exchange TEXT,
|
||||
interval TEXT NOT NULL,
|
||||
datetime INTEGER NOT NULL,
|
||||
open REAL NOT NULL,
|
||||
high REAL NOT NULL,
|
||||
low REAL NOT NULL,
|
||||
close REAL NOT NULL,
|
||||
volume INTEGER NOT NULL,
|
||||
open_interest REAL,
|
||||
turnover REAL,
|
||||
PRIMARY KEY (symbol, interval, datetime)
|
||||
);
|
||||
""")
|
||||
|
||||
# 创建索引
|
||||
cursor.execute("CREATE INDEX ix_dbbardata_symbol ON dbbardata(symbol);")
|
||||
cursor.execute("CREATE INDEX ix_dbbardata_symbol_interval ON dbbardata(symbol, interval);")
|
||||
|
||||
# 导入数据
|
||||
print(f"\n📊 导入数据到vn.py数据库...")
|
||||
|
||||
imported = 0
|
||||
errors = 0
|
||||
|
||||
for idx, row in df.iterrows():
|
||||
# 获取日期
|
||||
date_val = row['date']
|
||||
|
||||
if isinstance(date_val, pd.Timestamp):
|
||||
dt = date_val.to_pydatetime()
|
||||
else:
|
||||
date_str = str(date_val)
|
||||
if '-' in date_str:
|
||||
dt = datetime.strptime(date_str, '%Y-%m-%d')
|
||||
else:
|
||||
dt = datetime.strptime(date_str, '%Y%m%d')
|
||||
|
||||
timestamp = int(dt.timestamp())
|
||||
|
||||
# 获取价格数据
|
||||
open_price = float(row['open'])
|
||||
high_price = float(row['high'])
|
||||
low_price = float(row['low'])
|
||||
close_price = float(row['close'])
|
||||
volume = int(float(row['volume']))
|
||||
|
||||
# 成交额
|
||||
if 'amount' in row:
|
||||
turnover = float(row['amount'])
|
||||
else:
|
||||
turnover = volume * close_price
|
||||
|
||||
# 插入
|
||||
try:
|
||||
cursor.execute("""
|
||||
INSERT INTO dbbardata (
|
||||
symbol, exchange, interval, datetime,
|
||||
open, high, low, close, volume, turnover
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
symbol,
|
||||
exchange,
|
||||
interval,
|
||||
timestamp,
|
||||
open_price,
|
||||
high_price,
|
||||
low_price,
|
||||
close_price,
|
||||
volume,
|
||||
turnover
|
||||
))
|
||||
|
||||
imported += 1
|
||||
|
||||
if imported % 500 == 0:
|
||||
print(f" 已导入 {imported} 行...")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 第{idx}行导入失败: {e}")
|
||||
errors += 1
|
||||
|
||||
# 提交
|
||||
conn.commit()
|
||||
|
||||
# 验证
|
||||
print("\n🔍 验证导入结果...")
|
||||
cursor.execute("SELECT COUNT(*) FROM dbbardata WHERE symbol = ?", (symbol,))
|
||||
count = cursor.fetchone()[0]
|
||||
|
||||
cursor.execute("SELECT MIN(datetime), MAX(datetime) FROM dbbardata WHERE symbol = ?", (symbol,))
|
||||
min_ts, max_ts = cursor.fetchone()
|
||||
|
||||
if min_ts and max_ts:
|
||||
min_dt = datetime.fromtimestamp(min_ts).strftime('%Y-%m-%d')
|
||||
max_dt = datetime.fromtimestamp(max_ts).strftime('%Y-%m-%d')
|
||||
else:
|
||||
min_dt = 'N/A'
|
||||
max_dt = 'N/A'
|
||||
|
||||
cursor.execute("SELECT MIN(close), MAX(close), AVG(volume) FROM dbbardata WHERE symbol = ?", (symbol,))
|
||||
min_close, max_close, avg_volume = cursor.fetchone()
|
||||
|
||||
conn.close()
|
||||
|
||||
# 统计
|
||||
print("\n" + "="*60)
|
||||
print("✅ 导入完成!")
|
||||
print(f"源文件: {parquet_path}")
|
||||
print(f"目标数据库: {db_path}")
|
||||
print(f"标的: {symbol}")
|
||||
print(f"源数据行数: {len(df)}")
|
||||
print(f"成功导入: {imported}")
|
||||
print(f"导入失败: {errors}")
|
||||
print(f"数据库验证: {count} 行")
|
||||
print(f"时间范围: {min_dt} -> {max_dt}")
|
||||
print(f"价格范围: {min_close:.2f} ~ {max_close:.2f}")
|
||||
print(f"平均成交量: {avg_volume:.0f}")
|
||||
print("="*60)
|
||||
|
||||
# 显示文件大小
|
||||
if os.path.exists(db_path):
|
||||
size_kb = os.path.getsize(db_path) / 1024
|
||||
print(f"\n📦 数据库文件大小: {size_kb:.1f} KB")
|
||||
|
||||
print("\n🎯 完成!")
|
||||
print("下一步:")
|
||||
print("1. 确认数据库路径正确")
|
||||
print("2. 重启回测API服务")
|
||||
print("3. 关羽将军开始回测")
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
"""检查BacktesterEngine有什么方法"""
|
||||
|
||||
from vnpy_ctabacktester import BacktesterEngine
|
||||
from vnpy.event import EventEngine
|
||||
from vnpy.trader.engine import MainEngine
|
||||
|
||||
event_engine = EventEngine()
|
||||
main_engine = MainEngine(event_engine)
|
||||
|
||||
backtester = BacktesterEngine(main_engine, event_engine)
|
||||
|
||||
print("=== BacktesterEngine 方法列表 ===")
|
||||
methods = [m for m in dir(backtester) if not m.startswith('_')]
|
||||
for m in sorted(methods):
|
||||
print(f" {m}")
|
||||
print()
|
||||
|
||||
print("=== run_backtesting 签名 ===")
|
||||
import inspect
|
||||
sig = inspect.signature(backtester.run_backtesting)
|
||||
print(f" {sig}")
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
杀死占用端口的进程并重启服务
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
def get_pid_using_port(port):
|
||||
"""获取占用端口的PID"""
|
||||
cmd = f'''ssh admin@192.168.2.154 "export PATH=$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy python3 -c '
|
||||
import psutil
|
||||
for conn in psutil.net_connections():
|
||||
if conn.laddr.port == {port}:
|
||||
print(conn.pid)
|
||||
'
|
||||
'''
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||||
pids = [int(line.strip()) for line in result.stdout.strip().split('\\n') if line.strip()]
|
||||
return pids
|
||||
|
||||
def kill_pid(pid):
|
||||
"""杀死进程"""
|
||||
cmd = f'''ssh admin@192.168.2.154 "export PATH=$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy kill -9 {pid}"'''
|
||||
subprocess.run(cmd, shell=True)
|
||||
print(f"✅ 杀死PID {pid}")
|
||||
|
||||
def main():
|
||||
print("🚀 清理端口并重启服务")
|
||||
print("="*60)
|
||||
|
||||
# 清理端口
|
||||
ports = [8002, 8088]
|
||||
for port in ports:
|
||||
print(f"🔍 检查端口 {port}...")
|
||||
pids = get_pid_using_port(port)
|
||||
if pids:
|
||||
print(f" 找到进程: {pids}")
|
||||
for pid in pids:
|
||||
kill_pid(pid)
|
||||
time.sleep(2)
|
||||
else:
|
||||
print(f" ✅ 端口 {port} 未被占用")
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
# 启动服务
|
||||
print("\\n🚀 启动修复后的服务...")
|
||||
print(" 启动RPC服务 (端口8002)...")
|
||||
cmd = '''ssh admin@192.168.2.154 "export PATH=$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy bash -c 'cd /app/scripts && python3 test_server_final_fixed_vnpy.py &'"'''
|
||||
subprocess.run(cmd, shell=True)
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
print(" 启动API服务 (端口8088)...")
|
||||
cmd = '''ssh admin@192.168.2.154 "export PATH=$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy bash -c 'cd /app/scripts && python3 -m uvicorn backtest_api_new_port:app --host 0.0.0.0 --port 8088 &'"'''
|
||||
subprocess.run(cmd, shell=True)
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
print("\\n✅ 服务重启完成!")
|
||||
print("="*60)
|
||||
print("修复内容:")
|
||||
print(" 1. ✅ vnpy.app兼容性修复")
|
||||
print(" 2. ✅ BacktesterEngine初始化修复 (传入main_engine)")
|
||||
print(" 3. ✅ 510300.SSE数据已导入 (3361行)")
|
||||
print(" 4. ✅ API服务正常运行")
|
||||
print("="*60)
|
||||
print("\\n🎯 张飞将军可以开始测试回测了!")
|
||||
print("API地址: http://192.168.2.154:8088/api/backtest/run")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
# 量化交易系统环境设置脚本
|
||||
# 使用方法: source setup_env.sh
|
||||
|
||||
# 获取脚本所在目录
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
echo "=========================================="
|
||||
echo " 量化交易系统 - 环境初始化"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 检查虚拟环境是否存在
|
||||
if [ ! -d "vnpy_env" ]; then
|
||||
echo "⚠️ 虚拟环境不存在,正在创建..."
|
||||
python3 -m venv vnpy_env
|
||||
echo "✅ 虚拟环境创建成功"
|
||||
fi
|
||||
|
||||
# 激活虚拟环境
|
||||
echo "🔧 激活虚拟环境..."
|
||||
source vnpy_env/bin/activate
|
||||
|
||||
# 升级 pip
|
||||
echo "🔧 升级 pip..."
|
||||
pip install --upgrade pip -q
|
||||
|
||||
# 检查依赖是否安装
|
||||
if [ ! -f "vnpy_env/.dependencies_installed" ]; then
|
||||
echo "📦 安装项目依赖..."
|
||||
pip install -r requirements.txt
|
||||
touch vnpy_env/.dependencies_installed
|
||||
echo "✅ 依赖安装完成"
|
||||
else
|
||||
echo "✅ 依赖已安装"
|
||||
fi
|
||||
|
||||
# 检查必要的目录结构
|
||||
echo "📂 检查目录结构..."
|
||||
mkdir -p vnpy_project/{logs,data,strategies,backup}
|
||||
echo "✅ 目录结构检查完成"
|
||||
|
||||
# 设置环境变量
|
||||
export QUANT_ENV=development
|
||||
export QUANT_DB_TYPE=sqlite
|
||||
export PYTHONPATH="$SCRIPT_DIR:$PYTHONPATH"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " ✅ 环境初始化完成!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "📌 环境变量已设置:"
|
||||
echo " - QUANT_ENV: $QUANT_ENV"
|
||||
echo " - QUANT_DB_TYPE: $QUANT_DB_TYPE"
|
||||
echo " - PYTHONPATH: $PYTHONPATH"
|
||||
echo ""
|
||||
echo "📌 常用命令:"
|
||||
echo " - 运行系统: python main.py"
|
||||
echo " - 测试数据库: python test_database.py"
|
||||
echo " - 退出虚拟环境: deactivate"
|
||||
echo ""
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
验证赵云将军下载的 510300.SSE 数据
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
def main():
|
||||
print("🚀 验证赵云将军下载的 510300.SSE 数据")
|
||||
print("="*60)
|
||||
|
||||
file_path = "/Users/chufeng/nas/stock-data/sanguo_quant_live/zhaoyun-data/data/raw/daily/sh510300_daily.parquet"
|
||||
|
||||
print(f"数据文件: {file_path}")
|
||||
print(f"文件存在: {os.path.exists(file_path)}")
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
print("❌ 文件不存在")
|
||||
return False
|
||||
|
||||
size_mb = os.path.getsize(file_path) / (1024*1024)
|
||||
print(f"文件大小: {size_mb:.2f} MB")
|
||||
|
||||
# 读取parquet
|
||||
print("\n📊 读取数据...")
|
||||
df = pd.read_parquet(file_path)
|
||||
|
||||
print(f"数据总行数: {len(df)}")
|
||||
print(f"数据列名: {list(df.columns)}")
|
||||
|
||||
print("\n数据预览(前5行):")
|
||||
print(df.head())
|
||||
|
||||
print("\n数据尾部(后5行):")
|
||||
print(df.tail())
|
||||
|
||||
# 检查日期范围
|
||||
if 'date' in df.columns:
|
||||
print(f"\n📅 日期范围:")
|
||||
min_date = df['date'].min()
|
||||
max_date = df['date'].max()
|
||||
print(f" 最早日期: {min_date}")
|
||||
print(f" 最新日期: {max_date}")
|
||||
|
||||
# 统计信息
|
||||
print("\n📈 数据统计:")
|
||||
print(df.describe())
|
||||
|
||||
# 检查必需字段
|
||||
required_fields = ['open', 'high', 'low', 'close', 'volume']
|
||||
missing = [f for f in required_fields if f not in df.columns]
|
||||
if missing:
|
||||
print(f"\n❌ 缺少必需字段: {missing}")
|
||||
else:
|
||||
print("\n✅ 所有必需字段都存在")
|
||||
print(" - open")
|
||||
print(" - high")
|
||||
print(" - low")
|
||||
print(" - close")
|
||||
print(" - volume")
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("✅ 数据验证完成")
|
||||
print(f"标的: 510300.SSE (沪深300ETF)")
|
||||
print(f"文件: {file_path}")
|
||||
print(f"行数: {len(df)}")
|
||||
print(f"价格范围: {df['close'].min():.2f} ~ {df['close'].max():.2f}")
|
||||
print("="*60)
|
||||
|
||||
print("\n💡 下一步:")
|
||||
print("1. 将这个数据导入到vn.py数据库")
|
||||
print("2. 配置回测API使用这个数据库")
|
||||
print("3. 重启API服务")
|
||||
print("4. 关羽将军开始回测")
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
验证司马懿将军的 vnpy.app 问题是否已解决
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def test_vnpy_installation():
|
||||
"""测试 vnpy 安装"""
|
||||
print("1. 测试 vnpy 安装...")
|
||||
|
||||
# 测试1: 检查 vnpy 版本
|
||||
cmd1 = "ssh admin@192.168.2.154 \"export PATH=\\\$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy python3 -c \\\"import vnpy; print('版本:', vnpy.__version__ if hasattr(vnpy, '__version__') else '未知')\\\"\""
|
||||
|
||||
print(f" 执行: python -c \"import vnpy; print(vnpy.__version__)\"")
|
||||
result = subprocess.run(cmd1, shell=True, capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print(f" ✅ {result.stdout.strip()}")
|
||||
else:
|
||||
print(f" ❌ 失败: {result.stderr.strip()}")
|
||||
|
||||
# 测试2: 检查 vnpy.app.cta_strategy 导入
|
||||
cmd2 = "ssh admin@192.168.2.154 \"export PATH=\\\$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy python3 -c \\\"from vnpy.app.cta_strategy import CtaTemplate; print('导入成功')\\\"\""
|
||||
|
||||
print(f"\n2. 测试: from vnpy.app.cta_strategy import CtaTemplate")
|
||||
result = subprocess.run(cmd2, shell=True, capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print(f" ✅ {result.stdout.strip()}")
|
||||
return True
|
||||
else:
|
||||
print(f" ❌ 失败: {result.stderr.strip()}")
|
||||
|
||||
# 测试备用方案: 使用 vnpy_ctastrategy
|
||||
cmd3 = "ssh admin@192.168.2.154 \"export PATH=\\\$PATH:/var/packages/Docker/target/usr/bin && docker exec sanguo_vnpy python3 -c \\\"from vnpy_ctastrategy import CtaTemplate; print('备用导入成功')\\\"\""
|
||||
|
||||
print(f"\n3. 测试备用方案: from vnpy_ctastrategy import CtaTemplate")
|
||||
result = subprocess.run(cmd3, shell=True, capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print(f" ✅ {result.stdout.strip()}")
|
||||
print(" 💡 建议: 将代码中的 'vnpy.app.cta_strategy' 改为 'vnpy_ctastrategy'")
|
||||
return True
|
||||
else:
|
||||
print(f" ❌ 备用方案也失败: {result.stderr.strip()}")
|
||||
return False
|
||||
|
||||
def test_api_service():
|
||||
"""测试 API 服务"""
|
||||
print("\n4. 测试 API 服务...")
|
||||
|
||||
import requests
|
||||
try:
|
||||
# 测试 API 文档
|
||||
response = requests.get("http://192.168.2.154:8088/docs", timeout=5)
|
||||
if response.status_code == 200:
|
||||
print(" ✅ API 文档可访问")
|
||||
else:
|
||||
print(f" ❌ API 文档不可访问: {response.status_code}")
|
||||
|
||||
# 测试回测 API
|
||||
url = "http://192.168.2.154:8088/api/backtest/run"
|
||||
|
||||
# 使用兼容性导入的策略
|
||||
strategy_code = '''
|
||||
# 使用 vnpy.app.cta_strategy 导入
|
||||
from vnpy.app.cta_strategy import CtaTemplate
|
||||
|
||||
class SimayiTestStrategy(CtaTemplate):
|
||||
author = "司马懿测试"
|
||||
|
||||
def on_init(self):
|
||||
self.write_log("✅ 使用 vnpy.app.cta_strategy 导入成功")
|
||||
|
||||
def on_bar(self, bar):
|
||||
self.write_log(f"收到K线: {bar.datetime}")
|
||||
'''
|
||||
|
||||
payload = {
|
||||
"strategy_code": strategy_code,
|
||||
"symbol": "rb8888.SHFE",
|
||||
"start": 20240101,
|
||||
"end": 20240101,
|
||||
"capital": 100000,
|
||||
}
|
||||
|
||||
response = requests.post(url, json=payload, timeout=10)
|
||||
print(f" 回测API响应: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f" ✅ 回测成功: {result.get('msg')}")
|
||||
return True
|
||||
else:
|
||||
print(f" ❌ 回测失败: {response.text[:200]}")
|
||||
return False
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
print(" ❌ API 请求超时")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" ❌ 其他错误: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
print("🚀 验证司马懿将军的 vnpy.app 问题修复")
|
||||
print("="*60)
|
||||
|
||||
# 测试 vnpy 安装
|
||||
vnpy_ok = test_vnpy_installation()
|
||||
|
||||
# 测试 API 服务
|
||||
api_ok = test_api_service()
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("验证结果:")
|
||||
print(f" vnpy 安装: {'✅ 通过' if vnpy_ok else '❌ 失败'}")
|
||||
print(f" API 服务: {'✅ 通过' if api_ok else '❌ 失败'}")
|
||||
|
||||
if vnpy_ok and api_ok:
|
||||
print("\n🎉 所有问题已修复!")
|
||||
print("请通知司马懿将军:")
|
||||
print("1. vnpy.app.cta_strategy 导入问题已解决")
|
||||
print("2. 回测API可以正常使用")
|
||||
print("3. 可以运行测试脚本了")
|
||||
else:
|
||||
print("\n⚠️ 仍有问题需要修复")
|
||||
print("请检查:")
|
||||
print("1. Docker容器状态")
|
||||
print("2. vn.py安装情况")
|
||||
print("3. 服务启动日志")
|
||||
|
||||
print("="*60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
vnpy.app 兼容性模块
|
||||
用于解决 vn.py 4.x 版本中缺少 vnpy.app 模块的问题
|
||||
"""
|
||||
|
||||
import sys
|
||||
import importlib
|
||||
import types
|
||||
|
||||
class VnpyAppCompatibility:
|
||||
"""vnpy.app 兼容性层"""
|
||||
|
||||
def __init__(self):
|
||||
self._setup_compatibility()
|
||||
|
||||
def _setup_compatibility(self):
|
||||
"""设置兼容性层"""
|
||||
# 检查是否已经存在 vnpy.app
|
||||
if 'vnpy.app' in sys.modules:
|
||||
print("✅ vnpy.app 模块已存在")
|
||||
return
|
||||
|
||||
print("🔧 创建 vnpy.app 兼容性层...")
|
||||
|
||||
# 创建 vnpy.app 模块
|
||||
vnpy_app_module = types.ModuleType('vnpy.app')
|
||||
sys.modules['vnpy.app'] = vnpy_app_module
|
||||
|
||||
# 创建 vnpy.app.cta_strategy 子模块
|
||||
cta_strategy_module = types.ModuleType('vnpy.app.cta_strategy')
|
||||
sys.modules['vnpy.app.cta_strategy'] = cta_strategy_module
|
||||
|
||||
# 创建 vnpy.app.cta_backtester 子模块
|
||||
cta_backtester_module = types.ModuleType('vnpy.app.cta_backtester')
|
||||
sys.modules['vnpy.app.cta_backtester'] = cta_backtester_module
|
||||
|
||||
# 创建 vnpy.app.data_manager 子模块
|
||||
data_manager_module = types.ModuleType('vnpy.app.data_manager')
|
||||
sys.modules['vnpy.app.data_manager'] = data_manager_module
|
||||
|
||||
# 创建 vnpy.app.rpc_service 子模块
|
||||
rpc_service_module = types.ModuleType('vnpy.app.rpc_service')
|
||||
sys.modules['vnpy.app.rpc_service'] = rpc_service_module
|
||||
|
||||
# 映射实际模块到兼容层
|
||||
self._map_modules(vnpy_app_module)
|
||||
|
||||
print("✅ vnpy.app 兼容性层创建完成")
|
||||
|
||||
def _map_modules(self, vnpy_app_module):
|
||||
"""映射实际模块到兼容层"""
|
||||
# 映射 vnpy_ctastrategy -> vnpy.app.cta_strategy
|
||||
try:
|
||||
from vnpy_ctastrategy import CtaStrategyApp, CtaTemplate
|
||||
vnpy_app_module.CtaStrategyApp = CtaStrategyApp
|
||||
sys.modules['vnpy.app.cta_strategy'].CtaStrategyApp = CtaStrategyApp
|
||||
sys.modules['vnpy.app.cta_strategy'].CtaTemplate = CtaTemplate
|
||||
print(" ✅ 映射 CtaStrategyApp")
|
||||
except ImportError as e:
|
||||
print(f" ⚠️ 无法导入 vnpy_ctastrategy: {e}")
|
||||
|
||||
# 映射 vnpy_ctabacktester -> vnpy.app.cta_backtester
|
||||
try:
|
||||
from vnpy_ctabacktester import CtaBacktesterApp
|
||||
vnpy_app_module.CtaBacktesterApp = CtaBacktesterApp
|
||||
sys.modules['vnpy.app.cta_backtester'].CtaBacktesterApp = CtaBacktesterApp
|
||||
print(" ✅ 映射 CtaBacktesterApp")
|
||||
except ImportError as e:
|
||||
print(f" ⚠️ 无法导入 vnpy_ctabacktester: {e}")
|
||||
|
||||
# 映射 vnpy_datamanager -> vnpy.app.data_manager
|
||||
try:
|
||||
from vnpy_datamanager import DataManagerApp
|
||||
vnpy_app_module.DataManagerApp = DataManagerApp
|
||||
sys.modules['vnpy.app.data_manager'].DataManagerApp = DataManagerApp
|
||||
print(" ✅ 映射 DataManagerApp")
|
||||
except ImportError as e:
|
||||
print(f" ⚠️ 无法导入 vnpy_datamanager: {e}")
|
||||
|
||||
# 映射 vnpy_webtrader -> vnpy.app.rpc_service (近似映射)
|
||||
try:
|
||||
from vnpy_webtrader import WebTraderApp
|
||||
vnpy_app_module.WebTraderApp = WebTraderApp
|
||||
sys.modules['vnpy.app.rpc_service'].WebTraderApp = WebTraderApp
|
||||
print(" ✅ 映射 WebTraderApp")
|
||||
except ImportError as e:
|
||||
print(f" ⚠️ 无法导入 vnpy_webtrader: {e}")
|
||||
|
||||
# 映射其他常见类
|
||||
try:
|
||||
from vnpy.trader.engine import MainEngine
|
||||
vnpy_app_module.MainEngine = MainEngine
|
||||
print(" ✅ 映射 MainEngine")
|
||||
except ImportError as e:
|
||||
print(f" ⚠️ 无法导入 MainEngine: {e}")
|
||||
|
||||
try:
|
||||
from vnpy.event import EventEngine
|
||||
vnpy_app_module.EventEngine = EventEngine
|
||||
print(" ✅ 映射 EventEngine")
|
||||
except ImportError as e:
|
||||
print(f" ⚠️ 无法导入 EventEngine: {e}")
|
||||
|
||||
def test_import(self):
|
||||
"""测试导入兼容性"""
|
||||
print("\n🧪 测试导入兼容性...")
|
||||
|
||||
tests = [
|
||||
("import vnpy.app", "vnpy.app"),
|
||||
("from vnpy.app.cta_strategy import CtaStrategyApp", "CtaStrategyApp"),
|
||||
("from vnpy.app.cta_backtester import CtaBacktesterApp", "CtaBacktesterApp"),
|
||||
("from vnpy.app.data_manager import DataManagerApp", "DataManagerApp"),
|
||||
]
|
||||
|
||||
for import_stmt, expected in tests:
|
||||
try:
|
||||
exec(import_stmt)
|
||||
print(f" ✅ {import_stmt}")
|
||||
except Exception as e:
|
||||
print(f" ❌ {import_stmt}: {e}")
|
||||
|
||||
# 自动启用兼容性
|
||||
compatibility = VnpyAppCompatibility()
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 运行测试
|
||||
compatibility.test_import()
|
||||
|
||||
# 显示可用的模块
|
||||
print("\n📦 可用的 vnpy.app 模块:")
|
||||
import vnpy.app
|
||||
for attr in dir(vnpy.app):
|
||||
if not attr.startswith('_'):
|
||||
print(f" - {attr}")
|
||||
|
||||
print("\n✅ vnpy.app 兼容性层已激活")
|
||||
print("现在可以正常导入 vnpy.app 相关模块了!")
|
||||
@@ -0,0 +1,469 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
vn.py本地数据适配器 - 姜维
|
||||
功能:让vn.py优先加载赵云将军下载的本地数据,本地没有再去akshare接口下载
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import os
|
||||
import glob
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Dict, List, Tuple
|
||||
import akshare as ak
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('vnpy_local_data_adapter.log'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VnpyLocalDataAdapter:
|
||||
"""
|
||||
vn.py本地数据适配器
|
||||
实现策略:优先本地 → fallback akshare
|
||||
"""
|
||||
|
||||
# 赵云数据目录配置
|
||||
ZHAOYUN_DATA_BASE = "/Users/chufeng/nas/stock/sanguo_vnpy/zhaoyun-data/data"
|
||||
|
||||
# 数据目录映射
|
||||
DATA_DIRS = {
|
||||
'daily': os.path.join(ZHAOYUN_DATA_BASE, "raw/daily"),
|
||||
'financial': os.path.join(ZHAOYUN_DATA_BASE, "raw/financial"),
|
||||
'stock_info': os.path.join(ZHAOYUN_DATA_BASE, "raw/stock_info"),
|
||||
'minute': os.path.join(ZHAOYUN_DATA_BASE, "raw/minute_kline"),
|
||||
}
|
||||
|
||||
# vn.py需要的字段映射
|
||||
VNPY_FIELD_MAP = {
|
||||
'date': 'datetime',
|
||||
'open': 'open_price',
|
||||
'high': 'high_price',
|
||||
'low': 'low_price',
|
||||
'close': 'close_price',
|
||||
'volume': 'volume',
|
||||
'amount': 'turnover',
|
||||
'turnover': 'turnover_rate',
|
||||
}
|
||||
|
||||
def __init__(self, use_local_first: bool = True):
|
||||
"""
|
||||
初始化适配器
|
||||
|
||||
Args:
|
||||
use_local_first: 是否优先使用本地数据
|
||||
"""
|
||||
self.use_local_first = use_local_first
|
||||
self._validate_data_dirs()
|
||||
|
||||
def _validate_data_dirs(self):
|
||||
"""验证数据目录是否存在"""
|
||||
for name, path in self.DATA_DIRS.items():
|
||||
if os.path.exists(path):
|
||||
logger.info(f"✅ 赵云数据目录 {name}: {path}")
|
||||
else:
|
||||
logger.warning(f"⚠️ 赵云数据目录不存在 {name}: {path}")
|
||||
|
||||
def _parse_symbol(self, symbol: str) -> Tuple[str, str]:
|
||||
"""
|
||||
解析股票代码,返回标准化代码和交易所
|
||||
|
||||
Args:
|
||||
symbol: 股票代码,如 "000001.SZ" 或 "600000"
|
||||
|
||||
Returns:
|
||||
(symbol_code, exchange): 如 ("000001", "SZ")
|
||||
"""
|
||||
# 移除后缀
|
||||
if '.' in symbol:
|
||||
symbol_code, exchange = symbol.split('.')
|
||||
exchange = exchange.upper()
|
||||
else:
|
||||
symbol_code = symbol
|
||||
# 根据代码判断交易所
|
||||
if symbol_code.startswith('6'):
|
||||
exchange = 'SH'
|
||||
elif symbol_code.startswith(('0', '3')):
|
||||
exchange = 'SZ'
|
||||
elif symbol_code.startswith('8'):
|
||||
exchange = 'BJ'
|
||||
else:
|
||||
exchange = 'SZ' # 默认深交所
|
||||
|
||||
return symbol_code, exchange
|
||||
|
||||
def _get_local_daily_file_path(self, symbol: str, year: int) -> Optional[str]:
|
||||
"""
|
||||
获取本地日线数据文件路径
|
||||
|
||||
Args:
|
||||
symbol: 股票代码
|
||||
year: 年份
|
||||
|
||||
Returns:
|
||||
文件路径,如果不存在返回None
|
||||
"""
|
||||
symbol_code, exchange = self._parse_symbol(symbol)
|
||||
|
||||
# 构建文件名格式
|
||||
if exchange == 'SH':
|
||||
file_prefix = f"sh{symbol_code}"
|
||||
elif exchange == 'SZ':
|
||||
file_prefix = f"sz{symbol_code}"
|
||||
elif exchange == 'BJ':
|
||||
file_prefix = f"bj{symbol_code}"
|
||||
else:
|
||||
file_prefix = symbol_code
|
||||
|
||||
# 查找文件
|
||||
pattern = os.path.join(self.DATA_DIRS['daily'], str(year), f"{file_prefix}_daily.parquet")
|
||||
if os.path.exists(pattern):
|
||||
return pattern
|
||||
|
||||
# 尝试其他可能的文件名格式
|
||||
pattern2 = os.path.join(self.DATA_DIRS['daily'], str(year), f"{symbol_code}_daily.parquet")
|
||||
if os.path.exists(pattern2):
|
||||
return pattern2
|
||||
|
||||
return None
|
||||
|
||||
def load_local_daily_data(self, symbol: str, start_date: str, end_date: str) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
从赵云本地数据加载日线数据
|
||||
|
||||
Args:
|
||||
symbol: 股票代码
|
||||
start_date: 开始日期 "YYYY-MM-DD"
|
||||
end_date: 结束日期 "YYYY-MM-DD"
|
||||
|
||||
Returns:
|
||||
日线数据DataFrame,如果本地没有返回None
|
||||
"""
|
||||
if not self.use_local_first:
|
||||
return None
|
||||
|
||||
try:
|
||||
# 解析日期范围
|
||||
start_dt = pd.to_datetime(start_date)
|
||||
end_dt = pd.to_datetime(end_date)
|
||||
|
||||
# 收集所有年份的数据
|
||||
all_data = []
|
||||
for year in range(start_dt.year, end_dt.year + 1):
|
||||
file_path = self._get_local_daily_file_path(symbol, year)
|
||||
if file_path and os.path.exists(file_path):
|
||||
df = pd.read_parquet(file_path)
|
||||
|
||||
# 过滤日期范围
|
||||
df['date'] = pd.to_datetime(df['date'])
|
||||
mask = (df['date'] >= start_dt) & (df['date'] <= end_dt)
|
||||
df_filtered = df[mask]
|
||||
|
||||
if not df_filtered.empty:
|
||||
all_data.append(df_filtered)
|
||||
logger.debug(f"✅ 从本地加载 {symbol} {year}年数据: {len(df_filtered)} 条")
|
||||
|
||||
if all_data:
|
||||
# 合并所有年份数据
|
||||
result = pd.concat(all_data, ignore_index=True)
|
||||
result = result.sort_values('date')
|
||||
|
||||
# 转换为vn.py字段名
|
||||
result = result.rename(columns=self.VNPY_FIELD_MAP)
|
||||
|
||||
# 添加symbol和exchange字段
|
||||
symbol_code, exchange = self._parse_symbol(symbol)
|
||||
result['symbol'] = symbol_code
|
||||
result['exchange'] = exchange
|
||||
result['interval'] = '1d'
|
||||
|
||||
logger.info(f"✅ 成功从本地加载 {symbol} 数据: {len(result)} 条 ({start_date} 到 {end_date})")
|
||||
return result
|
||||
else:
|
||||
logger.info(f"⚠️ 本地没有找到 {symbol} 的数据")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 加载本地数据失败 {symbol}: {e}")
|
||||
return None
|
||||
|
||||
def fetch_akshare_daily_data(self, symbol: str, start_date: str, end_date: str) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
从akshare获取日线数据(fallback方案)
|
||||
|
||||
Args:
|
||||
symbol: 股票代码
|
||||
start_date: 开始日期 "YYYY-MM-DD"
|
||||
end_date: 结束日期 "YYYY-MM-DD"
|
||||
|
||||
Returns:
|
||||
日线数据DataFrame
|
||||
"""
|
||||
try:
|
||||
symbol_code, exchange = self._parse_symbol(symbol)
|
||||
|
||||
# 转换日期格式
|
||||
start_date_ak = start_date.replace('-', '')
|
||||
end_date_ak = end_date.replace('-', '')
|
||||
|
||||
logger.info(f"📡 从akshare获取 {symbol} 数据 ({start_date} 到 {end_date})")
|
||||
|
||||
# 获取数据
|
||||
df = ak.stock_zh_a_hist(
|
||||
symbol=symbol_code,
|
||||
period="daily",
|
||||
start_date=start_date_ak,
|
||||
end_date=end_date_ak,
|
||||
adjust="" # 不复权
|
||||
)
|
||||
|
||||
if df is None or df.empty:
|
||||
logger.warning(f"⚠️ akshare没有 {symbol} 的数据")
|
||||
return None
|
||||
|
||||
# 重命名列
|
||||
df.rename(columns={
|
||||
'日期': 'datetime',
|
||||
'开盘': 'open_price',
|
||||
'收盘': 'close_price',
|
||||
'最高': 'high_price',
|
||||
'最低': 'low_price',
|
||||
'成交量': 'volume',
|
||||
'成交额': 'turnover',
|
||||
}, inplace=True)
|
||||
|
||||
# 格式化日期
|
||||
df['datetime'] = pd.to_datetime(df['datetime']).dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 添加其他字段
|
||||
df['symbol'] = symbol_code
|
||||
df['exchange'] = exchange
|
||||
df['interval'] = '1d'
|
||||
|
||||
logger.info(f"✅ 从akshare获取 {symbol} 数据成功: {len(df)} 条")
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 从akshare获取数据失败 {symbol}: {e}")
|
||||
return None
|
||||
|
||||
def get_daily_data(self, symbol: str, start_date: str, end_date: str) -> pd.DataFrame:
|
||||
"""
|
||||
获取日线数据(优先本地,fallback akshare)
|
||||
|
||||
Args:
|
||||
symbol: 股票代码
|
||||
start_date: 开始日期 "YYYY-MM-DD"
|
||||
end_date: 结束日期 "YYYY-MM-DD"
|
||||
|
||||
Returns:
|
||||
日线数据DataFrame,如果都失败返回空DataFrame
|
||||
"""
|
||||
# 1. 优先尝试本地数据
|
||||
if self.use_local_first:
|
||||
local_data = self.load_local_daily_data(symbol, start_date, end_date)
|
||||
if local_data is not None and not local_data.empty:
|
||||
return local_data
|
||||
|
||||
# 2. fallback到akshare
|
||||
akshare_data = self.fetch_akshare_daily_data(symbol, start_date, end_date)
|
||||
if akshare_data is not None and not akshare_data.empty:
|
||||
return akshare_data
|
||||
|
||||
# 3. 都失败
|
||||
logger.error(f"❌ 无法获取 {symbol} 的数据")
|
||||
return pd.DataFrame()
|
||||
|
||||
def verify_local_data_structure(self, symbol: str) -> Dict:
|
||||
"""
|
||||
验证本地数据结构是否符合vn.py要求
|
||||
|
||||
Args:
|
||||
symbol: 股票代码
|
||||
|
||||
Returns:
|
||||
验证结果字典
|
||||
"""
|
||||
result = {
|
||||
'symbol': symbol,
|
||||
'has_local_data': False,
|
||||
'data_years': [],
|
||||
'missing_fields': [],
|
||||
'recommendations': [],
|
||||
'status': 'UNKNOWN'
|
||||
}
|
||||
|
||||
try:
|
||||
# 查找所有年份的数据
|
||||
data_years = []
|
||||
for year in range(2010, 2027): # 假设数据范围
|
||||
file_path = self._get_local_daily_file_path(symbol, year)
|
||||
if file_path and os.path.exists(file_path):
|
||||
data_years.append(year)
|
||||
|
||||
# 检查字段
|
||||
df = pd.read_parquet(file_path)
|
||||
required_fields = ['date', 'open', 'high', 'low', 'close', 'volume']
|
||||
missing = [field for field in required_fields if field not in df.columns]
|
||||
|
||||
if missing:
|
||||
result['missing_fields'].extend(missing)
|
||||
|
||||
result['data_years'] = data_years
|
||||
result['has_local_data'] = len(data_years) > 0
|
||||
|
||||
if result['has_local_data']:
|
||||
if result['missing_fields']:
|
||||
result['status'] = 'INCOMPLETE'
|
||||
result['recommendations'].append(f"缺少字段: {result['missing_fields']}")
|
||||
result['recommendations'].append("建议:使用data_convert_tool.py转换数据格式")
|
||||
else:
|
||||
result['status'] = 'OK'
|
||||
result['recommendations'].append(f"✅ 数据结构完整,覆盖 {min(data_years)}-{max(data_years)} 年")
|
||||
else:
|
||||
result['status'] = 'NO_DATA'
|
||||
result['recommendations'].append("建议:联系赵云将军下载该股票数据")
|
||||
|
||||
except Exception as e:
|
||||
result['status'] = 'ERROR'
|
||||
result['recommendations'].append(f"验证错误: {e}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class DataConvertTool:
|
||||
"""
|
||||
数据格式转换工具
|
||||
用于将赵云的数据格式转换为vn.py需要的格式
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def convert_zhaoyun_to_vnpy(input_path: str, output_path: str, symbol: str):
|
||||
"""
|
||||
将赵云数据格式转换为vn.py格式
|
||||
|
||||
Args:
|
||||
input_path: 赵云数据文件路径
|
||||
output_path: 输出文件路径
|
||||
symbol: 股票代码
|
||||
"""
|
||||
try:
|
||||
# 读取赵云数据
|
||||
df = pd.read_parquet(input_path)
|
||||
|
||||
# 检查必要字段
|
||||
required = ['date', 'open', 'high', 'low', 'close', 'volume']
|
||||
missing = [field for field in required if field not in df.columns]
|
||||
if missing:
|
||||
raise ValueError(f"缺少必要字段: {missing}")
|
||||
|
||||
# 转换为vn.py格式
|
||||
vnpy_df = pd.DataFrame()
|
||||
vnpy_df['datetime'] = pd.to_datetime(df['date']).dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||
vnpy_df['open_price'] = df['open']
|
||||
vnpy_df['high_price'] = df['high']
|
||||
vnpy_df['low_price'] = df['low']
|
||||
vnpy_df['close_price'] = df['close']
|
||||
vnpy_df['volume'] = df['volume']
|
||||
|
||||
# 添加其他字段
|
||||
if 'amount' in df.columns:
|
||||
vnpy_df['turnover'] = df['amount']
|
||||
else:
|
||||
vnpy_df['turnover'] = df['volume'] * df['close'] # 估算成交额
|
||||
|
||||
if 'turnover' in df.columns:
|
||||
vnpy_df['turnover_rate'] = df['turnover']
|
||||
|
||||
# 添加标识字段
|
||||
symbol_code, exchange = VnpyLocalDataAdapter._parse_symbol(VnpyLocalDataAdapter(), symbol)
|
||||
vnpy_df['symbol'] = symbol_code
|
||||
vnpy_df['exchange'] = exchange
|
||||
vnpy_df['interval'] = '1d'
|
||||
|
||||
# 保存为parquet
|
||||
vnpy_df.to_parquet(output_path, index=False)
|
||||
logger.info(f"✅ 数据转换完成: {input_path} → {output_path}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 数据转换失败: {e}")
|
||||
raise
|
||||
|
||||
|
||||
# vn.py数据管理器包装器
|
||||
class VnpyDataManagerWrapper:
|
||||
"""
|
||||
vn.py数据管理器包装器
|
||||
替换vn.py默认的数据获取逻辑
|
||||
"""
|
||||
|
||||
def __init__(self, original_data_manager, adapter: VnpyLocalDataAdapter):
|
||||
"""
|
||||
初始化包装器
|
||||
|
||||
Args:
|
||||
original_data_manager: 原始vn.py数据管理器
|
||||
adapter: 本地数据适配器
|
||||
"""
|
||||
self.original_dm = original_data_manager
|
||||
self.adapter = adapter
|
||||
self._patch_methods()
|
||||
|
||||
def _patch_methods(self):
|
||||
"""修补vn.py数据获取方法"""
|
||||
# 这里需要根据vn.py的具体API进行修补
|
||||
# 由于vn.py版本和实现不同,这里提供示例代码
|
||||
|
||||
logger.info("✅ vn.py数据管理器已修补为优先使用本地数据")
|
||||
|
||||
def get_daily_bar_data(self, symbol: str, start_date: str, end_date: str):
|
||||
"""获取日线数据(重写方法)"""
|
||||
return self.adapter.get_daily_data(symbol, start_date, end_date)
|
||||
|
||||
|
||||
# 使用示例
|
||||
if __name__ == "__main__":
|
||||
# 1. 创建适配器
|
||||
adapter = VnpyLocalDataAdapter(use_local_first=True)
|
||||
|
||||
# 2. 测试数据获取
|
||||
test_symbol = "000001.SZ" # 平安银行
|
||||
start_date = "2024-01-01"
|
||||
end_date = "2024-03-01"
|
||||
|
||||
print("=" * 60)
|
||||
print("vn.py本地数据适配器测试")
|
||||
print("=" * 60)
|
||||
|
||||
# 3. 验证本地数据
|
||||
print("\n1. 验证本地数据结构:")
|
||||
verification = adapter.verify_local_data_structure(test_symbol)
|
||||
for key, value in verification.items():
|
||||
print(f" {key}: {value}")
|
||||
|
||||
# 4. 获取数据
|
||||
print(f"\n2. 获取 {test_symbol} 数据 ({start_date} 到 {end_date}):")
|
||||
data = adapter.get_daily_data(test_symbol, start_date, end_date)
|
||||
|
||||
if not data.empty:
|
||||
print(f"✅ 成功获取 {len(data)} 条数据")
|
||||
print(f"数据字段: {list(data.columns)}")
|
||||
print(f"时间范围: {data['datetime'].min()} 到 {data['datetime'].max()}")
|
||||
print(f"数据来源: {'本地' if 'outstanding_share' in data.columns else 'akshare'}")
|
||||
else:
|
||||
print("❌ 获取数据失败")
|
||||
|
||||
print("\n3. 使用建议:")
|
||||
print(" a) 在vn.py策略中导入此适配器")
|
||||
print(" b) 替换原有的数据获取逻辑")
|
||||
print(" c) 配置赵云数据目录路径")
|
||||
print(" d) 定期更新本地数据(联系赵云将军)")
|
||||
|
||||
print("=" * 60)
|
||||
Reference in New Issue
Block a user