15 KiB
15 KiB
赵云数据转换指南 - vn.py标准格式
🎯 目标
将赵云将军下载的A股数据转换为vn.py标准格式,便于vn.py策略直接使用
📋 转换要求
1. 目录结构
/nas/zhaoyun-data/
├── raw/ # 原始下载数据(保持原样)
│ ├── daily/ # 原始日线数据
│ ├── financial/ # 财务数据
│ └── stock_info/ # 股票信息
├── processed/ # 处理后的数据
│ └── vnpy_format/ # ✅ vn.py标准格式(新增)
│ ├── daily/ # 日线数据(已转换)
│ │ ├── 2010/ # 按年分区
│ │ ├── 2011/
│ │ └── ...
│ ├── minute/ # 分钟线数据(已转换)
│ └── stock_info/ # 股票信息(已转换)
└── scripts/
└── convert_to_vnpy.py # 数据转换脚本(新增)
2. 数据格式规范
原始数据(赵云格式)
# 假设您的原始数据字段:
{
'code': '000001', # 股票代码(可能带交易所前缀)
'date': '2024-01-01', # 日期
'open': 10.5, # 开盘价
'high': 11.2, # 最高价
'low': 10.3, # 最低价
'close': 11.0, # 收盘价
'volume': 1000000, # 成交量(股)
'amount': 11000000, # 成交额(元)
'turnover': 0.5, # 换手率
}
目标格式(vn.py标准)
# vn.py需要的字段:
{
'symbol': '000001', # 股票代码(6位,不含交易所)
'exchange': 'SZ', # 交易所代码:SH/SZ/BJ
'datetime': '2024-01-01 15:00:00', # 时间戳(日线数据用15:00:00)
'open_price': 10.5, # 开盘价
'high_price': 11.2, # 最高价
'low_price': 10.3, # 最低价
'close_price': 11.0, # 收盘价
'volume': 1000000, # 成交量(股)
'turnover': 11000000, # 成交额(元)
'turnover_rate': 0.5, # 换手率(可选)
'interval': '1d', # 周期:1d=日线,1m=分钟线
}
3. 转换逻辑
股票代码处理
def parse_symbol(code):
"""
解析股票代码,返回(symbol, exchange)
示例:
'000001' -> ('000001', 'SZ')
'600000' -> ('600000', 'SH')
'830000' -> ('830000', 'BJ')
'sh600000' -> ('600000', 'SH')
'sz000001' -> ('000001', 'SZ')
"""
# 移除交易所前缀
if code.startswith('sh'):
return code[2:], 'SH'
elif code.startswith('sz'):
return code[2:], 'SZ'
elif code.startswith('bj'):
return code[2:], 'BJ'
# 根据数字判断
if code.startswith('6'):
return code, 'SH'
elif code.startswith(('0', '3')):
return code, 'SZ'
elif code.startswith('8'):
return code, 'BJ'
return code, 'SZ' # 默认
日期时间处理
def format_datetime(date_str, interval='1d'):
"""
格式化日期时间
日线数据:添加 15:00:00(A股收盘时间)
分钟线数据:保持原样
"""
if interval == '1d':
# 日线数据:YYYY-MM-DD 15:00:00
return f"{date_str} 15:00:00"
else:
# 分钟线数据:保持原样或添加 :00
if len(date_str) == 10: # YYYY-MM-DD
return f"{date_str} 09:30:00" # 开盘时间
else:
return date_str
🚀 转换脚本示例
完整转换脚本:convert_to_vnpy.py
#!/usr/bin/env python3
"""
赵云数据 → vn.py格式转换脚本
作者:赵云(数据工程将军)
"""
import pandas as pd
import os
import glob
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('convert_to_vnpy.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class ZhaoyunToVnpyConverter:
"""赵云数据转vn.py格式转换器"""
def __init__(self, raw_data_dir, output_dir):
"""
初始化转换器
Args:
raw_data_dir: 赵云原始数据目录
output_dir: vn.py格式输出目录
"""
self.raw_dir = raw_data_dir
self.output_dir = output_dir
# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
def parse_symbol(self, code):
"""解析股票代码"""
code_str = str(code)
# 移除交易所前缀
if code_str.startswith('sh'):
return code_str[2:8], 'SH' # 取6位数字
elif code_str.startswith('sz'):
return code_str[2:8], 'SZ'
elif code_str.startswith('bj'):
return code_str[2:8], 'BJ'
# 根据数字判断
if code_str.startswith('6'):
return code_str[:6], 'SH' # 取前6位
elif code_str.startswith(('0', '3')):
return code_str[:6], 'SZ'
elif code_str.startswith('8'):
return code_str[:6], 'BJ'
return code_str[:6], 'SZ' # 默认
def convert_daily_data(self, year=None, limit=None):
"""
转换日线数据
Args:
year: 指定年份,None表示所有年份
limit: 限制转换数量(测试用)
"""
# 原始数据目录
raw_daily_dir = os.path.join(self.raw_dir, 'raw/daily')
if not os.path.exists(raw_daily_dir):
logger.error(f"原始日线数据目录不存在: {raw_daily_dir}")
return
# 确定年份
if year:
years = [str(year)]
else:
years = [d for d in os.listdir(raw_daily_dir)
if os.path.isdir(os.path.join(raw_daily_dir, d))]
years.sort()
logger.info(f"开始转换日线数据,年份: {years}")
total_converted = 0
for year_dir in years:
year_path = os.path.join(raw_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 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:
# 读取原始数据
df = pd.read_parquet(file_path)
# 检查必要字段
required = ['date', 'open', 'high', 'low', 'close', 'volume']
missing = [f for f in required if f not in df.columns]
if missing:
logger.warning(f"文件缺少字段: {os.path.basename(file_path)} - {missing}")
continue
# 从文件名获取股票代码
file_name = os.path.basename(file_path)
if file_name.startswith('sh'):
symbol = file_name[2:8]
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:
# 尝试从数据中获取
if 'code' in df.columns:
symbol, exchange = self.parse_symbol(df['code'].iloc[0])
else:
logger.warning(f"无法确定股票代码: {file_name}")
continue
# 创建vn.py格式DataFrame
vnpy_df = pd.DataFrame()
# 基本字段
vnpy_df['datetime'] = pd.to_datetime(df['date']).dt.strftime('%Y-%m-%d 15:00:00')
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'] # 换手率
# 标识字段
vnpy_df['symbol'] = symbol
vnpy_df['exchange'] = exchange
vnpy_df['interval'] = '1d'
# 保存文件
output_file = os.path.join(output_year_path, f"{exchange}{symbol}_daily_vnpy.parquet")
vnpy_df.to_parquet(output_file, index=False)
total_converted += 1
if file_idx % 100 == 0:
logger.info(f"进度: {year_dir}年 {file_idx}/{len(parquet_files)}")
except Exception as e:
logger.error(f"转换失败 {file_path}: {e}")
logger.info(f"日线数据转换完成: 共转换 {total_converted} 个文件")
def convert_stock_info(self):
"""转换股票基础信息"""
raw_stock_dir = os.path.join(self.raw_dir, 'raw/stock_info')
if not os.path.exists(raw_stock_dir):
logger.warning(f"股票信息目录不存在: {raw_stock_dir}")
return
# 查找股票信息文件
stock_files = glob.glob(os.path.join(raw_stock_dir, '*.parquet')) + \
glob.glob(os.path.join(raw_stock_dir, '*.csv'))
if not stock_files:
logger.warning("未找到股票信息文件")
return
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: self.parse_symbol(x)[1])
all_stock_info.append(df)
logger.info(f"转换股票信息: {os.path.basename(file_path)}")
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_dir = os.path.join(self.output_dir, 'stock_info')
os.makedirs(output_dir, exist_ok=True)
output_file = os.path.join(output_dir, 'stock_basic_info_vnpy.parquet')
combined_df.to_parquet(output_file, index=False)
logger.info(f"股票信息转换完成: {output_file} ({len(combined_df)} 只股票)")
def main():
"""主函数"""
print("=" * 60)
print("赵云数据 → vn.py格式转换工具")
print("=" * 60)
# 配置路径
RAW_DATA_DIR = "/Users/chufeng/nas/stock/sanguo_vnpy/zhaoyun-data/data"
OUTPUT_DIR = "/Users/chufeng/nas/stock/sanguo_vnpy/zhaoyun-data/processed/vnpy_format"
# 创建转换器
converter = ZhaoyunToVnpyConverter(RAW_DATA_DIR, OUTPUT_DIR)
# 1. 转换日线数据(测试模式:只转换2024年前10个文件)
print("\n1. 转换日线数据(测试模式)...")
converter.convert_daily_data(year=2024, limit=10)
# 2. 转换股票信息
print("\n2. 转换股票信息...")
converter.convert_stock_info()
print("\n" + "=" * 60)
print("转换完成!")
print(f"输出目录: {OUTPUT_DIR}")
print("=" * 60)
print("\n下一步:")
print("1. 测试转换后的数据")
print("2. 联系姜维将军集成到vn.py")
print("3. 批量转换所有年份数据")
print("=" * 60)
if __name__ == "__main__":
main()
📋 转换工作清单
第一阶段:测试转换(立即开始)
- ✅ 创建转换脚本
convert_to_vnpy.py - ✅ 测试转换2024年的前10个股票文件
- ✅ 验证转换后的数据格式
- ✅ 调整转换逻辑(如有问题)
第二阶段:批量转换(测试通过后)
- 🔄 转换所有年份的日线数据
- 🔄 转换股票基础信息
- 🔄 转换财务数据(可选)
- 🔄 转换分钟线数据(可选)
第三阶段:定期更新
- 🔄 设置定时任务,自动转换新数据
- 🔄 数据质量监控
- 🔄 版本管理
🔧 技术要点
1. 性能优化
- 使用pandas批量处理
- 按年分区存储
- Parquet格式压缩存储
2. 数据质量
- 字段完整性检查
- 数据有效性验证
- 异常值处理
3. 可维护性
- 详细的日志记录
- 错误重试机制
- 配置化参数
📞 协作流程
数据更新流程:
赵云:下载新数据 → 运行转换脚本 → 更新vnpy_format目录 → 通知姜维
姜维:测试新数据 → 更新适配器配置 → 部署到生产环境
问题处理:
- 数据问题:联系赵云将军
- 格式问题:参考本指南调整转换逻辑
- 集成问题:联系姜维将军
赵云将军,请按此指南开始数据转换工作。转换完成后,末将(姜维)立即开始vn.py集成工作!
分工明确,协同作战,粮道必通! 🚛
姜维 - 后勤总督 2026-03-29