# coding:utf-8

"""
股票技术因子数据采集工具（专业版-按年月分表）
功能：从Tushare获取股票技术因子专业版数据，存储到gp_stock_factor_pro_YYYYMM月度分表
API文档: https://tushare.pro/document/2?doc_id=328

注意事项：
  1. 该接口需要至少5000积分权限
  2. 单次最大返回10000条数据
  3. 由于全市场单日数据超过10000条限制，采用按股票逐一采集策略
  4. 每天17~18点左右更新
  5. 字段众多（261个），数据量巨大，采用按年月分表策略

分表策略：
  - 表名格式：gp_stock_factor_pro_202510（2025年10月）
  - 自动按数据的交易日期分配到对应月份的表
  - 每个月度表包含该月所有股票的所有技术因子数据

采集策略：
  - 每日增量：一次性获取指定日期全市场数据（约5000只股票），保存到当月分表（1次API调用，约1秒）
  - 全量历史：逐只股票采集日期范围数据（默认4000天），自动按月份分配到对应分表（每秒10次请求，约8分钟）
  
建表说明：
  - 首次使用建议手动创建模板表 gp_stock_factor_pro_template，包含所有261个字段
  - 或者手动创建首个月份的表（如gp_stock_factor_pro_202510），程序会自动复制表结构
  - 程序会尝试自动创建分表，但由于字段众多，建议提前手动创建
"""

import tushare as ts
import pandas as pd
from sqlalchemy import create_engine, text
from datetime import datetime, timedelta
from tqdm import tqdm
import time
import sys
import os

# 添加项目根目录到路径
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(project_root)

from src.scripts.config import TUSHARE_TOKEN


class StockFactorProCollector:
    """股票技术因子专业版数据采集器（按年月分表）"""

    def __init__(self, db_url, tushare_token, base_table_name='gp_stock_factor_pro'):
        """
        初始化采集器
        
        Args:
            db_url: 数据库连接URL
            tushare_token: Tushare API Token
            base_table_name: 基础表名，默认为gp_stock_factor_pro（实际表名会加上年月后缀，如gp_stock_factor_pro_202510）
        """
        self.engine = create_engine(
            db_url,
            pool_size=5,
            max_overflow=10,
            pool_recycle=3600
        )
        self.base_table_name = base_table_name
        
        # 初始化Tushare
        ts.set_token(tushare_token)
        self.pro = ts.pro_api()
        
        print("=" * 60)
        print("股票技术因子数据采集工具（专业版-按年月分表）")
        print(f"基础表名: {self.base_table_name}")
        print("=" * 60)
    
    def get_table_name_by_date(self, date_str):
        """
        根据日期获取对应的分表名称
        
        Args:
            date_str: 日期字符串，格式YYYY-MM-DD
            
        Returns:
            分表名称，如gp_stock_factor_pro_202510
        """
        date_obj = datetime.strptime(date_str, '%Y-%m-%d')
        year_month = date_obj.strftime('%Y%m')
        return f"{self.base_table_name}_{year_month}"
    
    def create_monthly_table(self, table_name):
        """
        创建月度分表（如果不存在）
        
        Args:
            table_name: 表名，如gp_stock_factor_pro_202510
        """
        try:
            # 由于字段太多（261个），这里使用简化的建表语句
            # 实际使用时，建议手动创建表或从模板表复制结构
            create_table_sql = f"""
            CREATE TABLE IF NOT EXISTS `{table_name}` LIKE `{self.base_table_name}_template`
            """
            
            # 如果没有模板表，则创建完整的表结构
            # 注意：由于字段太多，这里只列出关键字段，其他字段会在实际使用时动态处理
            fallback_sql = f"""
            CREATE TABLE IF NOT EXISTS `{table_name}` (
            `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
            `symbol` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '股票代码（数据库格式，如SH600000）',
            `ts_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'Tushare股票代码（如600000.SH）',
            `trade_date` date NOT NULL COMMENT '交易日期',
            `open` decimal(10, 2) NULL DEFAULT NULL COMMENT '开盘价',
            `open_hfq` decimal(10, 2) NULL DEFAULT NULL COMMENT '开盘价（后复权）',
            `open_qfq` decimal(10, 2) NULL DEFAULT NULL COMMENT '开盘价（前复权）',
            `high` decimal(10, 2) NULL DEFAULT NULL COMMENT '最高价',
            `high_hfq` decimal(10, 2) NULL DEFAULT NULL COMMENT '最高价（后复权）',
            `high_qfq` decimal(10, 2) NULL DEFAULT NULL COMMENT '最高价（前复权）',
            `low` decimal(10, 2) NULL DEFAULT NULL COMMENT '最低价',
            `low_hfq` decimal(10, 2) NULL DEFAULT NULL COMMENT '最低价（后复权）',
            `low_qfq` decimal(10, 2) NULL DEFAULT NULL COMMENT '最低价（前复权）',
            `close` decimal(10, 2) NULL DEFAULT NULL COMMENT '收盘价',
            `close_hfq` decimal(10, 2) NULL DEFAULT NULL COMMENT '收盘价（后复权）',
            `close_qfq` decimal(10, 2) NULL DEFAULT NULL COMMENT '收盘价（前复权）',
            `pre_close` decimal(10, 2) NULL DEFAULT NULL COMMENT '昨收价',
            `change` decimal(10, 2) NULL DEFAULT NULL COMMENT '涨跌额',
            `pct_chg` decimal(10, 4) NULL DEFAULT NULL COMMENT '涨跌幅（%）',
            `vol` decimal(20, 2) NULL DEFAULT NULL COMMENT '成交量（手）',
            `amount` decimal(20, 2) NULL DEFAULT NULL COMMENT '成交额（千元）',
            `turnover_rate` decimal(10, 4) NULL DEFAULT NULL COMMENT '换手率（%）',
            `turnover_rate_f` decimal(10, 4) NULL DEFAULT NULL COMMENT '换手率（自由流通股）',
            `volume_ratio` decimal(10, 4) NULL DEFAULT NULL COMMENT '量比',
            `pe` decimal(10, 4) NULL DEFAULT NULL COMMENT '市盈率',
            `pe_ttm` decimal(10, 4) NULL DEFAULT NULL COMMENT '市盈率（TTM）',
            `pb` decimal(10, 4) NULL DEFAULT NULL COMMENT '市净率',
            `ps` decimal(10, 4) NULL DEFAULT NULL COMMENT '市销率',
            `ps_ttm` decimal(10, 4) NULL DEFAULT NULL COMMENT '市销率（TTM）',
            `dv_ratio` decimal(10, 4) NULL DEFAULT NULL COMMENT '股息率（%）',
            `dv_ttm` decimal(10, 4) NULL DEFAULT NULL COMMENT '股息率（TTM）（%）',
            `total_share` decimal(20, 2) NULL DEFAULT NULL COMMENT '总股本（万股）',
            `float_share` decimal(20, 2) NULL DEFAULT NULL COMMENT '流通股本（万股）',
            `free_share` decimal(20, 2) NULL DEFAULT NULL COMMENT '自由流通股本（万）',
            `total_mv` decimal(20, 2) NULL DEFAULT NULL COMMENT '总市值（万元）',
            `circ_mv` decimal(20, 2) NULL DEFAULT NULL COMMENT '流通市值（万元）',
            `adj_factor` decimal(15, 6) NULL DEFAULT NULL COMMENT '复权因子',
            `asi_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'ASI振动升降指标（不复权）',
            `asi_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'ASI振动升降指标（后复权）',
            `asi_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'ASI振动升降指标（前复权）',
            `asit_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'ASIT（不复权）',
            `asit_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'ASIT（后复权）',
            `asit_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'ASIT（前复权）',
            `atr_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'ATR真实波幅（不复权）',
            `atr_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'ATR真实波幅（后复权）',
            `atr_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'ATR真实波幅（前复权）',
            `bbi_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BBI多空指标（不复权）',
            `bbi_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BBI多空指标（后复权）',
            `bbi_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BBI多空指标（前复权）',
            `bias1_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BIAS1乖离率（不复权）',
            `bias1_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BIAS1乖离率（后复权）',
            `bias1_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BIAS1乖离率（前复权）',
            `bias2_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BIAS2乖离率（不复权）',
            `bias2_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BIAS2乖离率（后复权）',
            `bias2_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BIAS2乖离率（前复权）',
            `bias3_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BIAS3乖离率（不复权）',
            `bias3_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BIAS3乖离率（后复权）',
            `bias3_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BIAS3乖离率（前复权）',
            `boll_lower_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BOLL下轨（不复权）',
            `boll_lower_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BOLL下轨（后复权）',
            `boll_lower_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BOLL下轨（前复权）',
            `boll_mid_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BOLL中轨（不复权）',
            `boll_mid_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BOLL中轨（后复权）',
            `boll_mid_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BOLL中轨（前复权）',
            `boll_upper_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BOLL上轨（不复权）',
            `boll_upper_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BOLL上轨（后复权）',
            `boll_upper_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BOLL上轨（前复权）',
            `brar_ar_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BRAR-AR（不复权）',
            `brar_ar_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BRAR-AR（后复权）',
            `brar_ar_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BRAR-AR（前复权）',
            `brar_br_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BRAR-BR（不复权）',
            `brar_br_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BRAR-BR（后复权）',
            `brar_br_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'BRAR-BR（前复权）',
            `cci_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'CCI顺势指标（不复权）',
            `cci_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'CCI顺势指标（后复权）',
            `cci_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'CCI顺势指标（前复权）',
            `cr_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'CR价格动量指标（不复权）',
            `cr_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'CR价格动量指标（后复权）',
            `cr_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'CR价格动量指标（前复权）',
            `dfma_dif_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DFMA-DIF（不复权）',
            `dfma_dif_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DFMA-DIF（后复权）',
            `dfma_dif_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DFMA-DIF（前复权）',
            `dfma_difma_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DFMA-DIFMA（不复权）',
            `dfma_difma_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DFMA-DIFMA（后复权）',
            `dfma_difma_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DFMA-DIFMA（前复权）',
            `dmi_adx_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DMI-ADX（不复权）',
            `dmi_adx_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DMI-ADX（后复权）',
            `dmi_adx_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DMI-ADX（前复权）',
            `dmi_adxr_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DMI-ADXR（不复权）',
            `dmi_adxr_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DMI-ADXR（后复权）',
            `dmi_adxr_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DMI-ADXR（前复权）',
            `dmi_mdi_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DMI-MDI（不复权）',
            `dmi_mdi_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DMI-MDI（后复权）',
            `dmi_mdi_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DMI-MDI（前复权）',
            `dmi_pdi_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DMI-PDI（不复权）',
            `dmi_pdi_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DMI-PDI（后复权）',
            `dmi_pdi_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DMI-PDI（前复权）',
            `downdays` decimal(10, 0) NULL DEFAULT NULL COMMENT '连跌天数',
            `updays` decimal(10, 0) NULL DEFAULT NULL COMMENT '连涨天数',
            `dpo_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DPO区间震荡线（不复权）',
            `dpo_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DPO区间震荡线（后复权）',
            `dpo_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'DPO区间震荡线（前复权）',
            `madpo_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MADPO（不复权）',
            `madpo_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MADPO（后复权）',
            `madpo_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MADPO（前复权）',
            `ema_bfq_5` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA5（不复权）',
            `ema_bfq_10` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA10（不复权）',
            `ema_bfq_20` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA20（不复权）',
            `ema_bfq_30` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA30（不复权）',
            `ema_bfq_60` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA60（不复权）',
            `ema_bfq_90` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA90（不复权）',
            `ema_bfq_250` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA250（不复权）',
            `ema_hfq_5` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA5（后复权）',
            `ema_hfq_10` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA10（后复权）',
            `ema_hfq_20` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA20（后复权）',
            `ema_hfq_30` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA30（后复权）',
            `ema_hfq_60` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA60（后复权）',
            `ema_hfq_90` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA90（后复权）',
            `ema_hfq_250` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA250（后复权）',
            `ema_qfq_5` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA5（前复权）',
            `ema_qfq_10` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA10（前复权）',
            `ema_qfq_20` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA20（前复权）',
            `ema_qfq_30` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA30（前复权）',
            `ema_qfq_60` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA60（前复权）',
            `ema_qfq_90` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA90（前复权）',
            `ema_qfq_250` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMA250（前复权）',
            `emv_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMV简易波动指标（不复权）',
            `emv_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMV简易波动指标（后复权）',
            `emv_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EMV简易波动指标（前复权）',
            `maemv_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MAEMV（不复权）',
            `maemv_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MAEMV（后复权）',
            `maemv_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MAEMV（前复权）',
            `expma_12_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EXPMA12（不复权）',
            `expma_12_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EXPMA12（后复权）',
            `expma_12_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EXPMA12（前复权）',
            `expma_50_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EXPMA50（不复权）',
            `expma_50_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EXPMA50（后复权）',
            `expma_50_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'EXPMA50（前复权）',
            `kdj_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KDJ-J（不复权）',
            `kdj_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KDJ-J（后复权）',
            `kdj_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KDJ-J（前复权）',
            `kdj_d_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KDJ-D（不复权）',
            `kdj_d_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KDJ-D（后复权）',
            `kdj_d_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KDJ-D（前复权）',
            `kdj_k_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KDJ-K（不复权）',
            `kdj_k_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KDJ-K（后复权）',
            `kdj_k_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KDJ-K（前复权）',
            `ktn_down_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KTN下轨（不复权）',
            `ktn_down_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KTN下轨（后复权）',
            `ktn_down_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KTN下轨（前复权）',
            `ktn_mid_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KTN中轨（不复权）',
            `ktn_mid_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KTN中轨（后复权）',
            `ktn_mid_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KTN中轨（前复权）',
            `ktn_upper_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KTN上轨（不复权）',
            `ktn_upper_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KTN上轨（后复权）',
            `ktn_upper_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'KTN上轨（前复权）',
            `lowdays` decimal(10, 0) NULL DEFAULT NULL COMMENT '最低价区间天数',
            `topdays` decimal(10, 0) NULL DEFAULT NULL COMMENT '最高价区间天数',
            `ma_bfq_5` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA5（不复权）',
            `ma_bfq_10` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA10（不复权）',
            `ma_bfq_20` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA20（不复权）',
            `ma_bfq_30` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA30（不复权）',
            `ma_bfq_60` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA60（不复权）',
            `ma_bfq_90` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA90（不复权）',
            `ma_bfq_250` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA250（不复权）',
            `ma_hfq_5` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA5（后复权）',
            `ma_hfq_10` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA10（后复权）',
            `ma_hfq_20` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA20（后复权）',
            `ma_hfq_30` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA30（后复权）',
            `ma_hfq_60` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA60（后复权）',
            `ma_hfq_90` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA90（后复权）',
            `ma_hfq_250` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA250（后复权）',
            `ma_qfq_5` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA5（前复权）',
            `ma_qfq_10` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA10（前复权）',
            `ma_qfq_20` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA20（前复权）',
            `ma_qfq_30` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA30（前复权）',
            `ma_qfq_60` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA60（前复权）',
            `ma_qfq_90` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA90（前复权）',
            `ma_qfq_250` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA250（前复权）',
            `macd_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MACD（不复权）',
            `macd_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MACD（后复权）',
            `macd_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MACD（前复权）',
            `macd_dea_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MACD-DEA（不复权）',
            `macd_dea_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MACD-DEA（后复权）',
            `macd_dea_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MACD-DEA（前复权）',
            `macd_dif_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MACD-DIF（不复权）',
            `macd_dif_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MACD-DIF（后复权）',
            `macd_dif_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MACD-DIF（前复权）',
            `mass_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MASS梅斯线（不复权）',
            `mass_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MASS梅斯线（后复权）',
            `mass_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MASS梅斯线（前复权）',
            `ma_mass_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA-MASS（不复权）',
            `ma_mass_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA-MASS（后复权）',
            `ma_mass_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MA-MASS（前复权）',
            `mfi_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MFI资金流量指标（不复权）',
            `mfi_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MFI资金流量指标（后复权）',
            `mfi_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MFI资金流量指标（前复权）',
            `mtm_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MTM动量指标（不复权）',
            `mtm_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MTM动量指标（后复权）',
            `mtm_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MTM动量指标（前复权）',
            `mtmma_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MTMMA（不复权）',
            `mtmma_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MTMMA（后复权）',
            `mtmma_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MTMMA（前复权）',
            `obv_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'OBV能量潮（不复权）',
            `obv_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'OBV能量潮（后复权）',
            `obv_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'OBV能量潮（前复权）',
            `psy_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'PSY心理线指标（不复权）',
            `psy_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'PSY心理线指标（后复权）',
            `psy_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'PSY心理线指标（前复权）',
            `psyma_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'PSYMA（不复权）',
            `psyma_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'PSYMA（后复权）',
            `psyma_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'PSYMA（前复权）',
            `roc_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'ROC变动率指标（不复权）',
            `roc_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'ROC变动率指标（后复权）',
            `roc_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'ROC变动率指标（前复权）',
            `maroc_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MAROC（不复权）',
            `maroc_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MAROC（后复权）',
            `maroc_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'MAROC（前复权）',
            `rsi_bfq_6` decimal(15, 6) NULL DEFAULT NULL COMMENT 'RSI6（不复权）',
            `rsi_bfq_12` decimal(15, 6) NULL DEFAULT NULL COMMENT 'RSI12（不复权）',
            `rsi_bfq_24` decimal(15, 6) NULL DEFAULT NULL COMMENT 'RSI24（不复权）',
            `rsi_hfq_6` decimal(15, 6) NULL DEFAULT NULL COMMENT 'RSI6（后复权）',
            `rsi_hfq_12` decimal(15, 6) NULL DEFAULT NULL COMMENT 'RSI12（后复权）',
            `rsi_hfq_24` decimal(15, 6) NULL DEFAULT NULL COMMENT 'RSI24（后复权）',
            `rsi_qfq_6` decimal(15, 6) NULL DEFAULT NULL COMMENT 'RSI6（前复权）',
            `rsi_qfq_12` decimal(15, 6) NULL DEFAULT NULL COMMENT 'RSI12（前复权）',
            `rsi_qfq_24` decimal(15, 6) NULL DEFAULT NULL COMMENT 'RSI24（前复权）',
            `taq_down_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TAQ下轨（不复权）',
            `taq_down_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TAQ下轨（后复权）',
            `taq_down_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TAQ下轨（前复权）',
            `taq_mid_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TAQ中轨（不复权）',
            `taq_mid_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TAQ中轨（后复权）',
            `taq_mid_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TAQ中轨（前复权）',
            `taq_up_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TAQ上轨（不复权）',
            `taq_up_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TAQ上轨（后复权）',
            `taq_up_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TAQ上轨（前复权）',
            `trix_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TRIX三重指数平滑平均线（不复权）',
            `trix_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TRIX三重指数平滑平均线（后复权）',
            `trix_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TRIX三重指数平滑平均线（前复权）',
            `trma_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TRMA（不复权）',
            `trma_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TRMA（后复权）',
            `trma_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'TRMA（前复权）',
            `vr_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'VR容量比率（不复权）',
            `vr_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'VR容量比率（后复权）',
            `vr_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'VR容量比率（前复权）',
            `wr_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'WR威廉指标（不复权）',
            `wr_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'WR威廉指标（后复权）',
            `wr_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'WR威廉指标（前复权）',
            `wr1_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'WR1威廉指标（不复权）',
            `wr1_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'WR1威廉指标（后复权）',
            `wr1_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'WR1威廉指标（前复权）',
            `xsii_td1_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'XSII-TD1（不复权）',
            `xsii_td1_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'XSII-TD1（后复权）',
            `xsii_td1_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'XSII-TD1（前复权）',
            `xsii_td2_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'XSII-TD2（不复权）',
            `xsii_td2_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'XSII-TD2（后复权）',
            `xsii_td2_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'XSII-TD2（前复权）',
            `xsii_td3_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'XSII-TD3（不复权）',
            `xsii_td3_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'XSII-TD3（后复权）',
            `xsii_td3_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'XSII-TD3（前复权）',
            `xsii_td4_bfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'XSII-TD4（不复权）',
            `xsii_td4_hfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'XSII-TD4（后复权）',
            `xsii_td4_qfq` decimal(15, 6) NULL DEFAULT NULL COMMENT 'XSII-TD4（前复权）',
            `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
              PRIMARY KEY (`id`) USING BTREE,
              UNIQUE INDEX `idx_symbol_trade_date`(`symbol` ASC, `trade_date` ASC) USING BTREE,
              INDEX `idx_symbol`(`symbol` ASC) USING BTREE,
              INDEX `idx_trade_date`(`trade_date` ASC) USING BTREE,
              INDEX `idx_ts_code`(`ts_code` ASC) USING BTREE
            ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin 
            COMMENT = '股票技术因子数据表-专业版（按月分表）' ROW_FORMAT = DYNAMIC
            """
            
            with self.engine.begin() as conn:
                try:
                    # 尝试从模板表复制结构
                    conn.execute(text(create_table_sql))
                    print(f"✓ 表 {table_name} 已从模板表创建")
                except:
                    # 如果模板表不存在，则使用基础建表语句
                    # 注意：这只会创建基础字段，实际使用时Pandas会自动添加其他列
                    conn.execute(text(fallback_sql))
                    print(f"✓ 表 {table_name} 已创建（使用基础结构）")
            
        except Exception as e:
            print(f"创建表 {table_name} 失败: {e}")
            # 不抛出异常，让程序继续运行，Pandas的to_sql会尝试创建表

    def convert_tushare_code_to_db(self, ts_code):
        """
        将Tushare格式的股票代码转换为数据库格式
        Tushare格式：600000.SH、000001.SZ
        数据库格式：SH600000、SZ000001
        
        Args:
            ts_code: Tushare格式的股票代码
            
        Returns:
            数据库格式的股票代码
        """
        if '.SH' in ts_code:
            return f"SH{ts_code.replace('.SH', '')}"
        elif '.SZ' in ts_code:
            return f"SZ{ts_code.replace('.SZ', '')}"
        elif '.BJ' in ts_code:
            return f"BJ{ts_code.replace('.BJ', '')}"
        else:
            return ts_code

    def convert_db_code_to_tushare(self, symbol):
        """
        将数据库格式的股票代码转换为Tushare格式
        数据库格式：SH600000、SZ000001
        Tushare格式：600000.SH、000001.SZ
        
        Args:
            symbol: 数据库格式的股票代码
            
        Returns:
            Tushare格式的股票代码
        """
        if symbol.startswith('SH'):
            return f"{symbol[2:]}.SH"
        elif symbol.startswith('SZ'):
            return f"{symbol[2:]}.SZ"
        elif symbol.startswith('BJ'):
            return f"{symbol[2:]}.BJ"
        else:
            return symbol

    def fetch_all_stock_codes(self):
        """
        从数据库获取所有A股股票代码
        
        Returns:
            股票代码列表
        """
        try:
            query = "SELECT gp_code FROM gp_code_all_copy"
            df = pd.read_sql(query, self.engine)
            codes = df['gp_code'].tolist()
            print(f"获取到A股股票代码: {len(codes)}个")
            return codes
        except Exception as e:
            print(f"获取股票代码失败: {e}")
            return []

    def fetch_stock_factor_pro(self, ts_code=None, trade_date=None, start_date=None, end_date=None):
        """
        从Tushare获取股票技术因子专业版数据
        API文档: https://tushare.pro/document/2?doc_id=328
        单次最大返回10000条数据
        
        Args:
            ts_code: Tushare格式的股票代码，为None时获取全市场数据
            trade_date: 交易日期，格式YYYYMMDD（单日查询）
            start_date: 开始日期，格式YYYYMMDD（范围查询）
            end_date: 结束日期，格式YYYYMMDD（范围查询）
            
        Returns:
            技术因子DataFrame
        """
        try:
            df = self.pro.stk_factor_pro(
                ts_code=ts_code,
                trade_date=trade_date,
                start_date=start_date,
                end_date=end_date
            )
            return df
        except Exception as e:
            print(f"获取技术因子专业版数据失败: {e}")
            return pd.DataFrame()

    def transform_data(self, df):
        """
        转换Tushare数据为数据库格式
        动态映射所有字段，支持261个字段的完整处理
        
        Args:
            df: Tushare返回的DataFrame
            
        Returns:
            转换后的DataFrame
        """
        if df.empty:
            return pd.DataFrame()
        
        result_df = pd.DataFrame()
        
        # 处理symbol和ts_code字段
        result_df['symbol'] = df['ts_code'].apply(self.convert_tushare_code_to_db)
        result_df['ts_code'] = df['ts_code']
        result_df['trade_date'] = pd.to_datetime(df['trade_date'], format='%Y%m%d')
        
        # 动态映射所有其他字段（除了ts_code和trade_date已处理）
        # 这样可以自动处理所有技术指标字段，无需手动枚举
        exclude_fields = {'ts_code', 'trade_date'}
        for col in df.columns:
            if col not in exclude_fields and col not in result_df.columns:
                result_df[col] = df[col]
        
        return result_df

    def run_full_collection(self, start_date=None, end_date=None):
        """
        执行全量覆盖采集（按股票+日期范围采集，按年月分表）
        每只股票采集指定日期范围的数据，自动根据数据日期分配到对应的月度分表
        
        Args:
            start_date: 开始日期，格式YYYY-MM-DD，默认为4000天前
            end_date: 结束日期，格式YYYY-MM-DD，默认为今天
        """
        print("=" * 60)
        print("开始执行全量覆盖采集（按年月分表，按股票逐一采集日期范围）")
        print("=" * 60)
        
        try:
            # 确定日期范围
            if end_date is None:
                end_date_obj = datetime.now()
            else:
                end_date_obj = datetime.strptime(end_date, '%Y-%m-%d')
            
            if start_date is None:
                # 默认采集近4000天历史数据
                start_date_obj = end_date_obj - timedelta(days=4000)
            else:
                start_date_obj = datetime.strptime(start_date, '%Y-%m-%d')
            
            start_date_str = start_date_obj.strftime('%Y%m%d')
            end_date_str = end_date_obj.strftime('%Y%m%d')
            
            print(f"日期范围: {start_date_obj.strftime('%Y-%m-%d')} 至 {end_date_obj.strftime('%Y-%m-%d')}")
            print("=" * 60)
            
            # 预先创建所有涉及月份的分表
            current_month = start_date_obj.replace(day=1)
            end_month = end_date_obj.replace(day=1)
            while current_month <= end_month:
                month_str = current_month.strftime('%Y-%m-%d')
                table_name = self.get_table_name_by_date(month_str)
                self.create_monthly_table(table_name)
                # 移动到下个月
                if current_month.month == 12:
                    current_month = current_month.replace(year=current_month.year + 1, month=1)
                else:
                    current_month = current_month.replace(month=current_month.month + 1)
            
            # 获取所有A股股票代码
            stock_codes = self.fetch_all_stock_codes()
            if not stock_codes:
                print("未获取到股票代码")
                return
            
            print(f"共需采集{len(stock_codes)}只股票")
            
            # 逐只股票采集日期范围数据
            success_count = 0
            fail_count = 0
            total_records = 0
            
            for i, symbol in enumerate(stock_codes, 1):
                try:
                    # 转换为Tushare格式
                    ts_code = self.convert_db_code_to_tushare(symbol)
                    
                    # 获取该股票指定日期范围的技术因子数据
                    df = self.fetch_stock_factor_pro(
                        ts_code=ts_code,
                        start_date=start_date_str,
                        end_date=end_date_str
                    )
                    
                    if not df.empty:
                        # 转换数据格式
                        result_df = self.transform_data(df)
                        
                        if not result_df.empty:
                            # 按月份分组保存到对应的分表
                            # 给每行数据添加一个临时的年月列用于分组
                            result_df['year_month'] = result_df['trade_date'].dt.strftime('%Y%m')
                            
                            # 按年月分组
                            for year_month, group_df in result_df.groupby('year_month'):
                                # 删除临时列
                                group_df = group_df.drop(columns=['year_month'])
                                
                                # 获取该月的表名
                                table_name = f"{self.base_table_name}_{year_month}"
                                
                                # 保存到对应的月度分表
                                group_df.to_sql(table_name, self.engine, if_exists='append', index=False)
                                total_records += len(group_df)
                            
                            success_count += 1
                    
                    # 每50只股票显示一次进度
                    if i % 50 == 0:
                        print(f"进度: {i}/{len(stock_codes)} ({success_count}成功, {fail_count}失败, 共{total_records}条记录)")
                    
                    # API限频控制
                    time.sleep(0.1)  # 每秒最多10次请求（历史数据较多，适当放慢）
                    
                except Exception as e:
                    fail_count += 1
                    if i % 50 == 0:
                        print(f"采集 {symbol} 失败: {e}")
            
            # 输出统计信息
            print("\n" + "=" * 60)
            print("全量覆盖采集完成")
            print("=" * 60)
            print(f"日期范围: {start_date_obj.strftime('%Y-%m-%d')} 至 {end_date_obj.strftime('%Y-%m-%d')}")
            print(f"目标股票数: {len(stock_codes)}只")
            print(f"成功采集: {success_count}只")
            print(f"失败: {fail_count}只")
            print(f"总记录数: {total_records}条")
            print(f"数据表: {self.base_table_name}_YYYYMM（按月分表）")
            print("=" * 60)
            
        except Exception as e:
            print(f"全量采集失败: {e}")
            import traceback
            traceback.print_exc()
        finally:
            self.engine.dispose()

    def run_daily_collection(self, date=None):
        """
        执行每日增量采集（按年月分表）
        一次性获取指定日期全市场的技术因子专业版数据
        
        Args:
            date: 指定日期，格式YYYY-MM-DD，默认为今天
        """
        print("=" * 60)
        print("开始执行每日增量采集（按年月分表，全市场一次性采集）")
        print("=" * 60)
        
        try:
            # 确定采集日期
            if date is None:
                date_str = datetime.now().strftime('%Y-%m-%d')
            else:
                date_str = date
            
            # 转换为YYYYMMDD格式
            date_obj = datetime.strptime(date_str, '%Y-%m-%d')
            trade_date = date_obj.strftime('%Y%m%d')
            
            # 获取当月分表名称
            table_name = self.get_table_name_by_date(date_str)
            
            print(f"采集日期: {date_str} ({trade_date})")
            print(f"目标表名: {table_name}")
            
            # 创建当月分表（如果不存在）
            self.create_monthly_table(table_name)
            
            # 删除指定日期的旧数据
            with self.engine.begin() as conn:
                delete_query = text(f"DELETE FROM {table_name} WHERE DATE(trade_date) = :date_str")
                result = conn.execute(delete_query, {"date_str": date_str})
                print(f"已删除{table_name}表中{date_str}的旧数据，共{result.rowcount}条")
            
            # 一次性获取指定日期全市场的技术因子数据
            print(f"正在获取{date_str}全市场技术因子数据...")
            df = self.fetch_stock_factor_pro(ts_code=None, trade_date=trade_date)
            
            if df.empty:
                print(f"{date_str}没有采集到任何数据（可能是非交易日）")
                return
            
            print(f"成功获取{len(df)}条原始数据")
            
            # 转换数据格式
            result_df = self.transform_data(df)
            
            if result_df.empty:
                print(f"数据转换后为空")
                return
            
            # 一次性保存到数据库
            print(f"正在保存数据到{table_name}表...")
            result_df.to_sql(table_name, self.engine, if_exists='append', index=False)
            
            # 输出统计信息
            print("\n" + "=" * 60)
            print("每日增量采集完成")
            print("=" * 60)
            print(f"采集日期: {date_str}")
            print(f"采集股票数: {result_df['symbol'].nunique()}只")
            print(f"保存记录数: {len(result_df)}条")
            print(f"数据表: {table_name}")
            print("=" * 60)
            
        except Exception as e:
            print(f"每日增量采集失败: {e}")
            import traceback
            traceback.print_exc()
        finally:
            self.engine.dispose()


def collect_stock_factor_pro(db_url, tushare_token, mode='daily', date=None, start_date=None, end_date=None):
    """
    股票技术因子专业版数据采集入口函数（按年月分表）
    
    Args:
        db_url: 数据库连接URL
        tushare_token: Tushare API Token
        mode: 采集模式，'daily'=增量采集当日数据，'full'=全量覆盖采集
        date: 指定日期（仅mode='daily'时有效），格式YYYY-MM-DD
        start_date: 开始日期（仅mode='full'时有效），格式YYYY-MM-DD，默认为4000天前
        end_date: 结束日期（仅mode='full'时有效），格式YYYY-MM-DD，默认为今天
        
    分表说明：
        数据会自动保存到对应月份的分表中，表名格式为 gp_stock_factor_pro_YYYYMM
        例如：2025年10月的数据保存到 gp_stock_factor_pro_202510 表
    
    采集策略：
        - 每日模式：一次性获取指定日期全市场数据（约5000只股票），保存到当月分表
        - 全量模式：逐只股票采集历史数据，自动按月份分配到对应分表
    """
    collector = StockFactorProCollector(db_url, tushare_token)
    
    if mode == 'daily':
        collector.run_daily_collection(date)
    elif mode == 'full':
        collector.run_full_collection(start_date, end_date)
    else:
        print(f"未知的采集模式: {mode}")


if __name__ == "__main__":
    # 数据库配置
    db_url = 'mysql+pymysql://fac_pattern:Chlry$%.8pattern@192.168.16.150:3306/factordb_mysql'
    
    # Tushare Token（从config.py读取）
    tushare_token = TUSHARE_TOKEN
    
    # 使用方式示例（按年月分表）：
    
    # 1. 每日增量采集（推荐每日使用，一次性获取全市场数据，保存到当月分表）
    # collect_stock_factor_pro(db_url, tushare_token, mode='daily')
    
    # 2. 全量覆盖采集（首次使用或需要重建数据时，默认采集近4000天数据，自动按月分配到对应分表）
    # collect_stock_factor_pro(db_url, tushare_token, mode='full')
    
    # 3. 采集指定日期的数据（一次性获取全市场数据，保存到该日期对应的月份分表）
    collect_stock_factor_pro(db_url, tushare_token, mode='daily', date='2025-11-06')
    
    # 4. 采集指定日期范围的数据（按股票逐一采集，数据会自动按月分配到对应分表）
    # collect_stock_factor_pro(db_url, tushare_token, mode='full', 
    #                          start_date='2024-01-01', end_date='2024-12-31')
    
    # 注意：
    # - 首次使用前，建议手动创建模板表 gp_stock_factor_pro_template 或首个月份表
    # - 表名格式：gp_stock_factor_pro_202510（2025年10月）
    # - 每个月度表包含该月所有股票的所有技术因子数据（261个字段）
    # - 每日模式：一次性请求全市场数据，速度快（约1秒）
    # - 全量模式：按股票逐一采集，适合采集历史数据（约5000只股票 × 0.1秒 ≈ 8分钟）

