这个步骤是把csv数据转成qlib的bin数据,坑巨多。
之前说到放弃,就是因为它对分时小周期数据的支持非常不友好,懒得说了,直接上源码。
1 #!/usr/bin/env python3 2 # -*- coding: utf-8 -*- 3 """ 4 简单的CSV转qlib工具 - 循环处理不同周期 5 """ 6 7 import os 8 import pandas as pd 9 from pathlib import Path 10 import subprocess 11 import sys 12 import tempfile 13 import shutil 14 from datetime import datetime, timedelta 15 16 def load_trading_calendar(): 17 """加载交易日历""" 18 calendar_file = Path(__file__).parent / "day.txt" 19 20 if not calendar_file.exists(): 21 print(f"⚠ 交易日历文件不存在: {calendar_file}") 22 return None 23 24 try: 25 # 读取交易日历,假设格式为每行一个日期 YYYY-MM-DD 26 with open(calendar_file, 'r', encoding='utf-8') as f: 27 dates = [] 28 for line in f: 29 line = line.strip() 30 if line and not line.startswith('#'): # 跳过空行和注释 31 try: 32 date_obj = datetime.strptime(line, '%Y-%m-%d').date() 33 dates.append(date_obj) 34 except ValueError: 35 continue 36 37 dates.sort() # 确保日期排序 38 print(f"✓ 加载交易日历: {len(dates)} 个交易日") 39 return dates 40 41 except Exception as e: 42 print(f"✗ 读取交易日历失败: {e}") 43 return None 44 45 def get_previous_trading_date(current_date, trading_calendar): 46 """获取指定日期的前一个交易日""" 47 if not trading_calendar: 48 return current_date - timedelta(days=1) # 如果没有日历,简单减一天 49 50 try: 51 # 找到当前日期在交易日历中的位置 52 if current_date in trading_calendar: 53 current_index = trading_calendar.index(current_date) 54 if current_index > 0: 55 return trading_calendar[current_index - 1] 56 57 # 如果当前日期不在交易日历中,找到最近的前一个交易日 58 for i in range(len(trading_calendar) - 1, -1, -1): 59 if trading_calendar[i] < current_date: 60 return trading_calendar[i] 61 62 # 如果找不到,返回简单减一天的结果 63 return current_date - timedelta(days=1) 64 65 except Exception as e: 66 print(f"⚠ 获取前一交易日失败: {e}") 67 return current_date - timedelta(days=1) 68 69 def adjust_night_session_time(df, trading_calendar): 70 """调整夜盘时间:将21:00-23:59的数据日期改为前一个交易日""" 71 if 'datetime' not in df.columns: 72 return df 73 74 print(" 检查夜盘时间...") 75 76 # 转换datetime列为pandas datetime类型 77 df['datetime'] = pd.to_datetime(df['datetime']) 78 79 adjusted_count = 0 80 81 for idx, row in df.iterrows(): 82 dt = row['datetime'] 83 hour = dt.hour 84 85 # 检查是否为夜盘时间 (21:00-23:59) 86 if 21 <= hour <= 23: 87 current_date = dt.date() 88 previous_trading_date = get_previous_trading_date(current_date, trading_calendar) 89 90 # 创建新的datetime,保持时间不变,只改日期 91 new_datetime = datetime.combine(previous_trading_date, dt.time()) 92 df.at[idx, 'datetime'] = new_datetime 93 adjusted_count += 1 94 95 if adjusted_count > 0: 96 print(f" ✓ 调整了 {adjusted_count} 条夜盘数据的日期") 97 # 重新按时间排序 98 df = df.sort_values('datetime').reset_index(drop=True) 99 else: 100 print(" - 没有发现需要调整的夜盘数据") 101 102 return df 103 104 def run_dump_bin_ordered(temp_csv_dir, target_path, freq, ordered_symbols): 105 """按指定顺序运行qlib的dump_bin.py脚本,第一个用dump_all,后续用dump_update""" 106 dump_script_path = Path(r'C:\ctp\q_qlib\qlib_src\scripts\dump_bin.py') 107 108 if not dump_script_path.exists(): 109 print(f"✗ dump_bin.py脚本不存在: {dump_script_path}") 110 return False 111 112 print(f"使用dump_bin.py按顺序转换{freq}数据...") 113 print(f"处理顺序: {ordered_symbols}") 114 115 # 按顺序处理每个品种 116 for i, symbol in enumerate(ordered_symbols): 117 print(f" 处理 {i+1}/{len(ordered_symbols)}: {symbol}") 118 119 # 查找该品种的CSV文件 120 csv_files = list(Path(temp_csv_dir).glob(f"{symbol}.*.csv")) 121 122 if not csv_files: 123 print(f" ⚠ 未找到 {symbol} 的文件") 124 continue 125 126 # 为该品种创建单独的临时目录 127 symbol_temp_dir = Path(temp_csv_dir) / f"temp_{symbol}" 128 symbol_temp_dir.mkdir(exist_ok=True) 129 130 # 复制该品种的文件到单独目录 131 for csv_file in csv_files: 132 shutil.copy2(csv_file, symbol_temp_dir / csv_file.name) 133 134 # 决定使用dump_all还是dump_update 135 dump_mode = "dump_all" if i==0 else "dump_update" 136 137 cmd = [ 138 sys.executable, str(dump_script_path), 139 dump_mode, 140 "--csv_path", str(symbol_temp_dir), # 传递该品种的目录 141 "--qlib_dir", str(target_path), 142 "--freq", freq, 143 "--max_workers", "1", # 单线程确保顺序 144 "--include_fields", "open,close,high,low,volume", 145 "--date_field_name", "datetime" 146 ] 147 148 try: 149 result = subprocess.run(cmd, capture_output=True, text=True) 150 151 if result.returncode == 0: 152 print(f" ✓ {symbol} 转换成功 ({dump_mode})") 153 else: 154 print(f" ✗ {symbol} 转换失败 ({dump_mode})") 155 print(f" 错误: {result.stderr}") 156 # 清理临时目录 157 shutil.rmtree(symbol_temp_dir, ignore_errors=True) 158 return False 159 160 except Exception as e: 161 print(f" ✗ 处理 {symbol} 时出错: {e}") 162 # 清理临时目录 163 shutil.rmtree(symbol_temp_dir, ignore_errors=True) 164 return False 165 166 # 清理该品种的临时目录 167 shutil.rmtree(symbol_temp_dir, ignore_errors=True) 168 169 print(f"✓ 所有品种按顺序转换完成") 170 return True 171 172 def run_dump_bin_ordered_update(temp_csv_dir, target_path, freq, ordered_symbols): 173 """按指定顺序运行qlib的dump_bin.py脚本,第一个用dump_all,后续用dump_update(用于分钟周期)""" 174 dump_script_path = Path(r'C:\ctp\q_qlib\qlib_src\scripts\dump_bin.py') 175 176 if not dump_script_path.exists(): 177 print(f"✗ dump_bin.py脚本不存在: {dump_script_path}") 178 return False 179 180 print(f"使用dump_bin.py按顺序转换{freq}数据(第一个dump_all,后续dump_update)...") 181 print(f"处理顺序: {ordered_symbols}") 182 183 # 按顺序处理每个品种 184 for i, symbol in enumerate(ordered_symbols): 185 print(f" 处理 {i+1}/{len(ordered_symbols)}: {symbol}") 186 187 # 查找该品种的CSV文件 188 csv_files = list(Path(temp_csv_dir).glob(f"{symbol}.*.csv")) 189 190 if not csv_files: 191 print(f" ⚠ 未找到 {symbol} 的文件") 192 continue 193 194 # 为该品种创建单独的临时目录 195 symbol_temp_dir = Path(temp_csv_dir) / f"temp_{symbol}" 196 symbol_temp_dir.mkdir(exist_ok=True) 197 198 # 复制该品种的文件到单独目录 199 for csv_file in csv_files: 200 shutil.copy2(csv_file, symbol_temp_dir / csv_file.name) 201 202 # 第一个品种使用dump_all,后续使用dump_update 203 dump_mode = "dump_all" if i == 0 else "dump_update" 204 205 cmd = [ 206 sys.executable, str(dump_script_path), 207 dump_mode, 208 "--csv_path", str(symbol_temp_dir), # 传递该品种的目录 209 "--qlib_dir", str(target_path), 210 "--freq", freq, 211 "--max_workers", "1", # 单线程确保顺序 212 "--include_fields", "open,close,high,low,volume", 213 "--date_field_name", "datetime" 214 ] 215 216 try: 217 result = subprocess.run(cmd, capture_output=True, text=True) 218 219 if result.returncode == 0: 220 print(f" ✓ {symbol} 转换成功 ({dump_mode})") 221 else: 222 print(f" ✗ {symbol} 转换失败 ({dump_mode})") 223 print(f" 错误: {result.stderr}") 224 # 清理临时目录 225 shutil.rmtree(symbol_temp_dir, ignore_errors=True) 226 return False 227 228 except Exception as e: 229 print(f" ✗ 处理 {symbol} 时出错: {e}") 230 # 清理临时目录 231 shutil.rmtree(symbol_temp_dir, ignore_errors=True) 232 return False 233 234 # 清理该品种的临时目录 235 shutil.rmtree(symbol_temp_dir, ignore_errors=True) 236 237 print(f"✓ 所有品种按顺序转换完成") 238 return True 239 240 def run_dump_bin_group(temp_csv_dir, target_path, freq, dump_mode, symbols): 241 """运行qlib的dump_bin.py脚本处理一组相同交易时间的合约""" 242 dump_script_path = Path(r'C:\ctp\q_qlib\qlib_src\scripts\dump_bin.py') 243 244 if not dump_script_path.exists(): 245 print(f"✗ dump_bin.py脚本不存在: {dump_script_path}") 246 return False 247 248 print(f" 使用{dump_mode}模式处理: {symbols}") 249 250 # 检查临时目录中的文件 251 temp_files = list(Path(temp_csv_dir).glob("*.csv")) 252 print(f" 目录中有 {len(temp_files)} 个CSV文件") 253 254 cmd = [ 255 sys.executable, str(dump_script_path), 256 dump_mode, 257 "--csv_path", str(temp_csv_dir), 258 "--qlib_dir", str(target_path), 259 "--freq", freq, 260 "--max_workers", "1", 261 "--include_fields", "open,close,high,low,volume", 262 "--date_field_name", "datetime" 263 ] 264 265 try: 266 result = subprocess.run(cmd, capture_output=True, text=True) 267 268 if result.returncode == 0: 269 print(f" ✓ 转换成功") 270 return True 271 else: 272 print(f" ✗ 转换失败") 273 print(f" 错误: {result.stderr}") 274 return False 275 276 except Exception as e: 277 print(f" ✗ 运行dump_bin.py时出错: {e}") 278 return False 279 280 def run_dump_bin(temp_csv_dir, target_path, freq): 281 """运行qlib的dump_bin.py脚本""" 282 dump_script_path = Path(r'C:\ctp\q_qlib\qlib_src\scripts\dump_bin.py') 283 284 if not dump_script_path.exists(): 285 print(f"✗ dump_bin.py脚本不存在: {dump_script_path}") 286 return False 287 288 print(f"使用dump_bin.py转换{freq}数据...") 289 290 # 检查临时目录中的文件 291 temp_files = list(Path(temp_csv_dir).glob("*.csv")) 292 print(f" 临时目录中有 {len(temp_files)} 个CSV文件") 293 for f in temp_files[:3]: # 只显示前3个 294 print(f" - {f.name}") 295 if len(temp_files) > 3: 296 print(f" ... 还有 {len(temp_files) - 3} 个文件") 297 298 cmd = [ 299 sys.executable, str(dump_script_path), 300 "dump_all", 301 "--csv_path", str(temp_csv_dir), 302 "--qlib_dir", str(target_path), 303 "--freq", freq, # 使用传入的频率参数 304 "--max_workers", "1", # 改为单线程,便于调试 305 "--include_fields", "open,close,high,low,volume", 306 "--date_field_name", "datetime" 307 ] 308 309 print(f" 执行命令: {' '.join(cmd)}") 310 311 try: 312 result = subprocess.run(cmd, capture_output=True, text=True) 313 314 print(f" 返回码: {result.returncode}") 315 316 if result.stdout: 317 print(f" 标准输出:") 318 for line in result.stdout.split('\n')[:10]: # 只显示前10行 319 if line.strip(): 320 print(f" {line}") 321 322 if result.stderr: 323 print(f" 错误输出:") 324 for line in result.stderr.split('\n')[:10]: # 只显示前10行 325 if line.strip(): 326 print(f" {line}") 327 328 if result.returncode == 0: 329 print(f" ✓ 数据转换成功") 330 return True 331 else: 332 print(f" ✗ 数据转换失败 (返回码: {result.returncode})") 333 return False 334 335 except Exception as e: 336 print(f"✗ 运行dump_bin.py时出错: {e}") 337 return False 338 339 def copy_symbol_period_files(source_dir, temp_dir, symbol, period, trading_calendar=None): 340 """复制指定品种和周期的文件到临时目录""" 341 source_path = Path(source_dir) 342 temp_path = Path(temp_dir) 343 temp_path.mkdir(parents=True, exist_ok=True) 344 345 copied_count = 0 346 347 # 查找指定品种和周期的文件 348 pattern = f"{symbol}.*.{period}.csv" 349 for csv_file in source_path.glob(pattern): 350 try: 351 print(f" 处理文件: {csv_file.name}") 352 353 # 读取CSV文件 354 df = pd.read_csv(csv_file, encoding='utf-8') 355 356 # 解析文件名:RBL8.SHFE.day.csv 357 filename = csv_file.stem # 去掉.csv 358 parts = filename.split('.') 359 360 if len(parts) >= 3: 361 file_symbol = parts[0].upper() 362 exchange = parts[1] 363 364 # 添加instrument列 365 df['instrument'] = file_symbol 366 367 # 调整夜盘时间(只对分钟级数据进行调整) 368 if period in ['1m', '5m', '15m', '30m', '60m'] and trading_calendar: 369 df = adjust_night_session_time(df, trading_calendar) 370 371 # 重新排列列顺序 372 cols = ['instrument', 'datetime'] + [col for col in df.columns if col not in ['instrument', 'datetime']] 373 df = df[cols] 374 375 # 保存为标准格式:RBL8.SHFE.csv (去掉周期) 376 new_filename = f"{file_symbol}.{exchange}.csv" 377 output_file = temp_path / new_filename 378 df.to_csv(output_file, index=False) 379 380 print(f" ✓ 完成: {csv_file.name} -> {new_filename}") 381 copied_count += 1 382 383 except Exception as e: 384 print(f" ✗ 处理文件 {csv_file} 时出错: {e}") 385 386 return copied_count 387 388 def copy_period_files(source_dir, temp_dir, period, trading_calendar=None): 389 """复制指定周期的文件到临时目录,并修正夜盘时间""" 390 source_path = Path(source_dir) 391 temp_path = Path(temp_dir) 392 temp_path.mkdir(parents=True, exist_ok=True) 393 394 copied_count = 0 395 396 # 查找指定周期的文件 397 pattern = f"*.{period}.csv" 398 for csv_file in source_path.glob(pattern): 399 try: 400 print(f" 处理文件: {csv_file.name}") 401 402 # 读取CSV文件 403 df = pd.read_csv(csv_file, encoding='utf-8') 404 405 # 解析文件名:RBL8.SHFE.day.csv 406 filename = csv_file.stem # 去掉.csv 407 parts = filename.split('.') 408 409 if len(parts) >= 3: 410 symbol = parts[0].upper() 411 exchange = parts[1] 412 413 # 添加instrument列 414 df['instrument'] = symbol 415 416 # 调整夜盘时间(只对分钟级数据进行调整) 417 if period in ['1m', '5m', '15m', '30m', '60m'] and trading_calendar: 418 df = adjust_night_session_time(df, trading_calendar) 419 420 # 重新排列列顺序 421 cols = ['instrument', 'datetime'] + [col for col in df.columns if col not in ['instrument', 'datetime']] 422 df = df[cols] 423 424 # 保存为标准格式:RBL8.SHFE.csv (去掉周期) 425 new_filename = f"{symbol}.{exchange}.csv" 426 output_file = temp_path / new_filename 427 df.to_csv(output_file, index=False) 428 429 print(f" ✓ 完成: {csv_file.name} -> {new_filename}") 430 copied_count += 1 431 432 except Exception as e: 433 print(f" ✗ 处理文件 {csv_file} 时出错: {e}") 434 435 return copied_count 436 437 def run_dump_bin_contract(temp_csv_dir, target_path, freq, contract, period): 438 """为单个合约运行qlib的dump_bin.py脚本""" 439 dump_script_path = Path(r'C:\ctp\q_qlib\qlib_src\scripts\dump_bin.py') 440 441 if not dump_script_path.exists(): 442 print(f" ✗ dump_bin.py脚本不存在: {dump_script_path}") 443 return False 444 445 print(f" 使用dump_bin.py转换{freq}数据...") 446 447 cmd = [ 448 sys.executable, str(dump_script_path), 449 "dump_all", # 每个合约都用dump_all,独立处理 450 "--csv_path", str(temp_csv_dir), 451 "--qlib_dir", str(target_path), 452 "--freq", freq, 453 "--max_workers", "1", 454 "--include_fields", "open,close,high,low,volume", 455 "--date_field_name", "datetime" 456 ] 457 458 try: 459 result = subprocess.run(cmd, capture_output=True, text=True) 460 461 if result.returncode == 0: 462 print(f" ✓ 转换成功") 463 return True 464 else: 465 print(f" ✗ 转换失败") 466 if result.stderr: 467 print(f" 错误: {result.stderr}") 468 return False 469 470 except Exception as e: 471 print(f" ✗ 运行dump_bin.py时出错: {e}") 472 return False 473 474 def main(): 475 """主函数 - 按合约为单位处理,每个合约独立的数据目录""" 476 # 配置 477 source_dir = r"C:\ctp\q_qlib\cleaned_data" 478 base_target_dir = r"C:\ctp\q_qlib\qlib_data\cn_data" 479 480 # 定义要处理的周期和对应的qlib频率 481 period_freq_mapping = { 482 'day': 'day', 483 '1m': '1min', 484 '5m': '5min', 485 '15m': '15min', 486 '30m': '30min', 487 '60m': '60min' 488 } 489 490 # 调整处理顺序:day放到最后,让all.txt时间周期更长 491 periods = ['1m', '5m', '15m', '30m', '60m', 'day'] 492 493 print("=== 按合约为单位处理数据 ===") 494 print(f"源目录: {source_dir}") 495 print(f"目标目录: {base_target_dir}") 496 print(f"处理周期: {periods} (day放在最后,让all.txt时间周期更长)") 497 498 # 检查源目录 499 if not os.path.exists(source_dir): 500 print(f"✗ 源目录不存在: {source_dir}") 501 return 502 503 # 先检查源目录中实际存在的文件 504 print(f"\n检查源目录中的文件...") 505 source_path = Path(source_dir) 506 all_files = list(source_path.glob("*.csv")) 507 print(f"找到 {len(all_files)} 个CSV文件") 508 509 # 加载交易日历 510 print("\n加载交易日历...") 511 trading_calendar = load_trading_calendar() 512 if trading_calendar: 513 print(f"交易日历范围: {trading_calendar[0]} 到 {trading_calendar[-1]}") 514 else: 515 print("⚠ 未加载交易日历,夜盘时间将使用简单日期减1的方式处理") 516 517 # 创建目标目录 518 Path(base_target_dir).mkdir(parents=True, exist_ok=True) 519 520 # 获取所有合约(从实际存在的文件中) 521 contract_files = {} # {合约名: {周期: [文件列表]}} 522 523 for csv_file in all_files: 524 filename = csv_file.stem 525 parts = filename.split('.') 526 if len(parts) >= 3: 527 contract = f"{parts[0].upper()}.{parts[1]}" # 如 RBL8.SHFE 528 period = parts[2] # 如 day, 1m 529 530 if contract not in contract_files: 531 contract_files[contract] = {} 532 if period not in contract_files[contract]: 533 contract_files[contract][period] = [] 534 contract_files[contract][period].append(csv_file) 535 536 print(f"\n发现 {len(contract_files)} 个合约:") 537 for contract in sorted(contract_files.keys()): 538 available_periods = list(contract_files[contract].keys()) 539 print(f" {contract}: {available_periods}") 540 541 # 按合约处理 542 processed_contracts = 0 543 544 for contract in sorted(contract_files.keys()): 545 print(f"\n{'='*80}") 546 print(f"处理合约: {contract}") 547 print(f"{'='*80}") 548 549 # 为该合约创建独立的目标目录 550 contract_target_dir = Path(base_target_dir) / contract 551 contract_target_dir.mkdir(parents=True, exist_ok=True) 552 553 print(f"合约数据目录: {contract_target_dir}") 554 555 # 处理该合约的所有周期 556 contract_success = True 557 558 for period in periods: 559 if period not in contract_files[contract]: 560 print(f" ⚠ 跳过 {period} 周期:没有找到相关文件") 561 continue 562 563 qlib_freq = period_freq_mapping[period] 564 print(f"\n 处理周期: {period} (qlib频率: {qlib_freq})") 565 566 # 创建临时目录 567 with tempfile.TemporaryDirectory() as temp_dir: 568 print(f" 使用临时目录: {temp_dir}") 569 570 # 复制该合约该周期的文件 571 copied_count = 0 572 for csv_file in contract_files[contract][period]: 573 try: 574 print(f" 处理文件: {csv_file.name}") 575 576 # 读取CSV文件 577 df = pd.read_csv(csv_file, encoding='utf-8') 578 579 # 解析文件名:RBL8.SHFE.day.csv 580 filename = csv_file.stem 581 parts = filename.split('.') 582 symbol = parts[0].upper() 583 exchange = parts[1] 584 585 # 添加instrument列 586 df['instrument'] = symbol 587 588 # 调整夜盘时间(只对分钟级数据进行调整) 589 if period in ['1m', '5m', '15m', '30m', '60m'] and trading_calendar: 590 df = adjust_night_session_time(df, trading_calendar) 591 592 # 重新排列列顺序 593 cols = ['instrument', 'datetime'] + [col for col in df.columns if col not in ['instrument', 'datetime']] 594 df = df[cols] 595 596 # 保存为标准格式:RBL8.SHFE.csv (去掉周期) 597 new_filename = f"{symbol}.{exchange}.csv" 598 output_file = Path(temp_dir) / new_filename 599 df.to_csv(output_file, index=False) 600 601 print(f" ✓ 完成: {csv_file.name} -> {new_filename}") 602 copied_count += 1 603 604 except Exception as e: 605 print(f" ✗ 处理文件 {csv_file} 时出错: {e}") 606 contract_success = False 607 break 608 609 if copied_count == 0: 610 print(f" ✗ 没有成功复制任何文件") 611 contract_success = False 612 continue 613 614 print(f" 复制了 {copied_count} 个文件") 615 616 # 使用dump_all处理该合约该周期的数据 617 if run_dump_bin_contract(temp_dir, contract_target_dir, qlib_freq, contract, period): 618 print(f" ✓ {period} 周期处理完成") 619 else: 620 print(f" ✗ {period} 周期处理失败") 621 contract_success = False 622 623 if contract_success: 624 print(f"✓ 合约 {contract} 处理完成") 625 processed_contracts += 1 626 else: 627 print(f"✗ 合约 {contract} 处理失败") 628 629 print(f"\n{'='*50}") 630 print(f"处理完成!成功处理了 {processed_contracts}/{len(contract_files)} 个合约") 631 print(f"数据路径: {base_target_dir}") 632 print(f"每个合约都有独立的数据目录,避免了时间对齐问题") 633 print(f"处理顺序: 分钟周期 → day周期,让all.txt时间周期更长") 634 print(f"夜盘时间修正功能已启用 (21:00-23:59 → 前一交易日)") 635 print(f"{'='*50}") 636 637 638 639 if __name__ == "__main__": 640 main() 641 #python qlib_convert_to_qlib.py
浙公网安备 33010602011771号