Files
sanguo_vnpy/research/vnpy/赵云数据转换指南.md
T
2026-04-11 21:18:55 +08:00

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()

📋 转换工作清单

第一阶段:测试转换(立即开始)

  1. 创建转换脚本 convert_to_vnpy.py
  2. 测试转换2024年的前10个股票文件
  3. 验证转换后的数据格式
  4. 调整转换逻辑(如有问题)

第二阶段:批量转换(测试通过后)

  1. 🔄 转换所有年份的日线数据
  2. 🔄 转换股票基础信息
  3. 🔄 转换财务数据(可选)
  4. 🔄 转换分钟线数据(可选)

第三阶段:定期更新

  1. 🔄 设置定时任务,自动转换新数据
  2. 🔄 数据质量监控
  3. 🔄 版本管理

🔧 技术要点

1. 性能优化

  • 使用pandas批量处理
  • 按年分区存储
  • Parquet格式压缩存储

2. 数据质量

  • 字段完整性检查
  • 数据有效性验证
  • 异常值处理

3. 可维护性

  • 详细的日志记录
  • 错误重试机制
  • 配置化参数

📞 协作流程

数据更新流程:

赵云:下载新数据 → 运行转换脚本 → 更新vnpy_format目录 → 通知姜维
姜维:测试新数据 → 更新适配器配置 → 部署到生产环境

问题处理:

  • 数据问题:联系赵云将军
  • 格式问题:参考本指南调整转换逻辑
  • 集成问题:联系姜维将军

赵云将军,请按此指南开始数据转换工作。转换完成后,末将(姜维)立即开始vn.py集成工作!

分工明确,协同作战,粮道必通! 🚛

姜维 - 后勤总督 2026-03-29