# =============================================================================
# Real Data Acquisition Demo — Tushare Pro
# 真实数据获取演示 — Tushare Pro 版
# =============================================================================
#
# Tushare Pro 是国内最成熟的金融数据 API 之一，数据质量和稳定性
# 优于 AKShare，适合策略验证通过后切换到生产级数据。
#
# 与 AKShare 的核心差异:
#   • 需要注册获取 Token (免费注册，基础接口免费)
#   • 积分系统: 注册送 120 分，部分接口需要更高积分
#   • 股票代码格式: "000001.SZ" (而非纯数字)
#   • 数据质量更高、接口更稳定
#   • 支持基本面数据 (PE/PB/ROE) 和因子数据
#
# Prerequisites / 前置准备:
#   1. 注册 Tushare: https://tushare.pro/register
#   2. 获取 Token: 登录后 → 个人主页 → 接口 Token
#   3. 安装: pip install tushare pandas numpy
#
# Topics covered / 涵盖主题:
#   §1  Tushare 注册与 Token 配置
#   §2  获取 A 股个股日线数据
#   §3  获取指数日线数据
#   §4  获取股票基础信息 (上市日期/行业/市值)
#   §5  获取财务数据 (PE/PB/ROE — 价值因子的数据源)
#   §6  获取指数成分股权重 (组合优化的输入)
#   §7  获取行业分类 (申万行业)
#   §8  数据清洗与对齐
#   §9  构建本地数据库
#   §10 AKShare vs Tushare 对比总结
# =============================================================================

from __future__ import annotations

import warnings
warnings.filterwarnings("ignore")

import numpy as np
import pandas as pd
import os
import time
from datetime import datetime

# ── 检查 tushare 是否安装 ──
try:
    import tushare as ts
    print(f"✓ Tushare 版本: {ts.__version__}")
except ImportError:
    print("❌ Tushare 未安装。请在 trading conda 环境中执行:")
    print("   conda activate trading")
    print("   pip install tushare")
    raise

print("=" * 68)
print("  真实数据获取演示 — Tushare Pro 版")
print("  Real Data Acquisition Demo with Tushare Pro")
print("=" * 68)


# ══════════════════════════════════════════════════════════════════════
# §1  Tushare 注册与 Token 配置
# ══════════════════════════════════════════════════════════════════════
#
# 使用步骤:
#   1. 访问 https://tushare.pro/register 注册 (用手机号即可)
#   2. 登录后 → 个人主页 → 接口 Token → 复制那一长串字符
#   3. 把 Token 粘贴到下面 (或设置环境变量 TUSHARE_TOKEN)
#
# 积分说明:
#   注册: 120 分 (基础接口可用)
#   完善个人信息: +20 分
#   推荐他人注册: 各 +50 分
#   捐赠: 200 RMB = 3000 分 (解锁全部接口)
#
# 日常使用建议:
#   - 将 Token 存为环境变量而非硬编码在代码中
#   - 每个接口都标注所需最低积分，避免超限调用
# =============================================================================

print("\n[§1] Tushare Token 配置")

TUSHARE_TOKEN = os.environ.get("TUSHARE_TOKEN", "")

if not TUSHARE_TOKEN:
    print("  ⚠ 未检测到 TUSHARE_TOKEN 环境变量")
    print("  ── 获取 Token 的步骤 ──")
    print("  1. 访问 https://tushare.pro/register 注册")
    print("  2. 登录后进入个人主页 → 接口 Token")
    print("  3. 复制 Token 后，设置环境变量:")
    print("     export TUSHARE_TOKEN='你的token'")
    print()
    print("  或者直接在下方代码中填入 (仅本地测试用):")
    print('  TUSHARE_TOKEN = "你的token"')
    print()
    print("  ⚠ 本 Demo 将继续运行，但数据获取部分会跳过或使用模拟数据")
    print("    您仍然可以看到完整的代码结构和数据清洗逻辑。")
    print()

# 尝试初始化 Pro API
PRO = None
TOKEN_VALID = False
if TUSHARE_TOKEN:
    try:
        PRO = ts.pro_api(TUSHARE_TOKEN)
        # 用最简单接口测试 Token 是否有效
        test = PRO.trade_cal(exchange='SSE', start_date='20250601', end_date='20250605')
        TOKEN_VALID = len(test) > 0
        print(f"  ✓ Token 验证成功")
        print(f"  ✓ Pro API 就绪")
    except Exception as e:
        print(f"  ✗ Token 验证失败: {e}")
else:
    print(f"  - Token 未设置，跳过远程数据获取")
    print(f"  - 代码结构完整，可阅读学习流程逻辑")


# ══════════════════════════════════════════════════════════════════════
# §2  获取 A 股个股日线数据
# ══════════════════════════════════════════════════════════════════════
#
# 接口: pro.daily()
#   所需积分: 120 分 (注册即送)
#   每日调用上限: 200 次
#   单次最多返回: 约 5000 条记录
#
# 参数:
#   ts_code   — 股票代码 "000001.SZ" / "600000.SH"
#   trade_date— 交易日期 "YYYYMMDD"
#   start_date/end_date — 日期范围
#
# 注意: Tushare 的日期格式是 "YYYYMMDD" (无连字符)
# =============================================================================

print("\n[§2] 获取个股日线数据")

# ── 构建 A 股代表性标的池 ──
STOCK_POOL = {
    "000001.SZ": "平安银行",
    "000002.SZ": "万科A",
    "000858.SZ": "五粮液",
    "600519.SH": "贵州茅台",
    "600036.SH": "招商银行",
    "600276.SH": "恒瑞医药",
    "300750.SZ": "宁德时代",
    "601318.SH": "中国平安",
}

# ── 日期范围转换 ──
# Demo 系列用 "YYYY-MM-DD"，Tushare 用 "YYYYMMDD"
START_DATE = "20190101"
END_DATE   = "20250601"


def fetch_daily_safe(pro, ts_code, start, end, max_retries=3):
    """
    安全获取个股日线数据。

    Tushare 每次调用返回约 5000 条，单只股票 6 年约 1500 个交易日，
    一次调用即可覆盖。如果有更多股票/更长区间，需要分多次调用并拼接。
    """
    for attempt in range(max_retries):
        try:
            df = pro.daily(
                ts_code=ts_code,
                start_date=start,
                end_date=end,
                fields='ts_code,trade_date,open,high,low,close,pre_close,'
                       'change,pct_chg,vol,amount'
            )
            time.sleep(0.3)  # 频率控制: Tushare 每分钟上限约 200 次
            return df
        except Exception as e:
            print(f"    ⚠ {ts_code} 第 {attempt+1} 次失败: {e}")
            if attempt < max_retries - 1:
                time.sleep(2.0)
    return None


if TOKEN_VALID and PRO:
    stock_daily_data = {}
    for code, name in STOCK_POOL.items():
        print(f"  获取 {code} ({name})...", end=" ")
        df = fetch_daily_safe(PRO, code, START_DATE, END_DATE)
        if df is not None and len(df) > 0:
            stock_daily_data[code] = df
            print(f"{len(df)} 行, {df['trade_date'].iloc[-1]} ~ {df['trade_date'].iloc[0]}")
        else:
            print("无数据")

    if stock_daily_data:
        # ── 构建价格面板 ──
        stock_price_panel = pd.DataFrame()
        for code, df in stock_daily_data.items():
            # Tushare 返回的 trade_date 是 YYYYMMDD 格式
            df = df.copy()
            df['trade_date'] = pd.to_datetime(df['trade_date'], format='%Y%m%d')
            df = df.sort_values('trade_date')
            s = df.set_index('trade_date')['close'].copy()
            s.name = code
            stock_price_panel = pd.concat([stock_price_panel, s], axis=1)

        print(f"\n  个股价格面板: {stock_price_panel.shape}")
        # 展示最新 3 行
        print(f"  最新 3 个交易日:")
        print(stock_price_panel.tail(3).to_string())
else:
    print("  (跳过 — Token 未配置)")
    print(f"  演示: 如果 Token 有效，会拉取以下 {len(STOCK_POOL)} 只股票的数据")
    for code, name in STOCK_POOL.items():
        print(f"    {code} ({name})")


# ══════════════════════════════════════════════════════════════════════
# §3  获取指数日线数据
# ══════════════════════════════════════════════════════════════════════
#
# 接口: pro.index_daily()
#   所需积分: 120 分
#
# 指数代码:
#   000300.SH — 沪深300
#   000905.SH — 中证500
#   000016.SH — 上证50
#   399006.SZ — 创业板指
# =============================================================================

print("\n[§3] 获取指数日线数据")

INDEX_CODES = {
    "000300.SH": "沪深300",
    "000905.SH": "中证500",
    "000016.SH": "上证50",
    "399006.SZ": "创业板指",
}

if TOKEN_VALID and PRO:
    index_data = {}
    for code, name in INDEX_CODES.items():
        print(f"  获取 {code} ({name})...", end=" ")
        try:
            df = PRO.index_daily(
                ts_code=code,
                start_date=START_DATE,
                end_date=END_DATE,
                fields='ts_code,trade_date,close,open,high,low,vol,amount,pct_chg'
            )
            time.sleep(0.3)
            if df is not None and len(df) > 0:
                index_data[code] = df
                print(f"{len(df)} 行")
            else:
                print("无数据")
        except Exception as e:
            print(f"失败: {e}")

    if index_data:
        index_panel = pd.DataFrame()
        for code, df in index_data.items():
            df = df.copy()
            df['trade_date'] = pd.to_datetime(df['trade_date'], format='%Y%m%d')
            df = df.sort_values('trade_date')
            s = df.set_index('trade_date')['close']
            s.name = code
            index_panel = pd.concat([index_panel, s], axis=1)

        print(f"\n  指数面板: {index_panel.shape}")
        # 年化收益对比
        ann_rets = (index_panel.iloc[-1] / index_panel.iloc[0]) ** (252.0 / len(index_panel)) - 1
        print("  年化收益率 (CAGR):")
        for code, val in ann_rets.items():
            name = INDEX_CODES.get(code, code)
            print(f"    {name}: {val:+.2%}")
else:
    print("  (跳过 — Token 未配置)")


# ══════════════════════════════════════════════════════════════════════
# §4  获取股票基础信息
# ══════════════════════════════════════════════════════════════════════
#
# 接口: pro.stock_basic()
#   所需积分: 120 分
#   功能: 获取股票列表，包含股票代码、名称、上市日期、退市日期、行业、地区等
# =============================================================================

print("\n[§4] 获取股票基础信息")

if TOKEN_VALID and PRO:
    try:
        # 获取沪深两市全部 A 股列表
        stock_basic = PRO.stock_basic(
            exchange='',
            list_status='L',       # L=上市, D=退市, P=暂停上市
            fields='ts_code,symbol,name,area,industry,market,list_date,'
                   'delist_date,curr_type,list_status,enname'
        )
        if stock_basic is not None and len(stock_basic) > 0:
            print(f"  当前上市股票: {len(stock_basic)} 只")

            # 退市股票 (用于幸存者偏差检查!)
            # 如果只拉当前上市股票，回测会漏掉已退市的垃圾公司
            # → 幸存者偏差 (Survivorship Bias) ← 这是实盘最大的坑之一
            delisted = PRO.stock_basic(
                exchange='',
                list_status='D',   # 退市
                fields='ts_code,symbol,name,list_date,delist_date'
            )
            if delisted is not None and len(delisted) > 0:
                print(f"  历史退市股票: {len(delisted)} 只 ← 回测必须包含!")
                print(f"  退市样本 (前 5 只):")
                for _, row in delisted.head(5).iterrows():
                    print(f"    {row['ts_code']} {row['name']} "
                          f"{row['list_date']} ~ {row['delist_date']}")

            # 行业分布
            if 'industry' in stock_basic.columns:
                industry_counts = stock_basic['industry'].value_counts().head(10)
                print(f"\n  行业分布 Top 10:")
                for ind, cnt in industry_counts.items():
                    print(f"    {ind}: {cnt} 只")

    except Exception as e:
        print(f"  获取失败: {e}")
else:
    print("  (跳过 — Token 未配置)")
    print("  提示: stock_basic() 可获取全市场股票列表和退市记录")
    print("        退市数据对消除幸存者偏差至关重要")


# ══════════════════════════════════════════════════════════════════════
# §5  获取财务数据 (价值因子的数据源)
# ══════════════════════════════════════════════════════════════════════
#
# 这是 Tushare 相比 AKShare 最大的优势领域 ——
# AKShare 也有部分财务接口，但覆盖度和稳定性不如 Tushare。
#
# 接口: pro.daily_basic() — 每日指标
#   所需积分: 300 分 (需升级)
#   字段: pe, pe_ttm, pb, ps, ps_ttm, dv_ratio, total_share, float_share,
#         total_mv, circ_mv
#
# 接口: pro.fina_indicator() — 财务指标
#   所需积分: 600 分 (需进一步升级)
#   字段: roe, roa, grossprofit_margin, netprofit_margin, debt_to_assets 等
# =============================================================================

print("\n[§5] 获取财务数据")

if TOKEN_VALID and PRO:
    try:
        # daily_basic: 日频估值指标 (PE/PB/市值)
        daily_basic = PRO.daily_basic(
            ts_code='000001.SZ',
            start_date='20240101',
            end_date='20250601',
            fields='ts_code,trade_date,close,pe,pe_ttm,pb,ps,total_mv,circ_mv'
        )
        if daily_basic is not None and len(daily_basic) > 0:
            print(f"  平安银行 (000001.SZ) 日频估值: {len(daily_basic)} 行")
            print(f"  字段: {list(daily_basic.columns)}")
            print(f"  最新 PE_TTM / PB / 市值:")
            latest = daily_basic.iloc[0]
            print(f"    PE(TTM): {latest.get('pe_ttm', 'N/A')}")
            print(f"    PB:      {latest.get('pb', 'N/A')}")
            print(f"    总市值:  {latest.get('total_mv', 'N/A')} 万元")
            print(f"\n  ⚠ 注意: 如果返回的 pe_ttm 都是 NaN, 说明积分不足 (需 300 分)")
        else:
            print("  未获取到数据 (积分可能不足)")

    except Exception as e:
        print(f"  获取失败: {e}")
        print(f"  提示: daily_basic 需要 300 积分，注册仅送 120 分")
else:
    print("  (跳过 — Token 未配置)")
    print("  提示: Tushare 的 daily_basic() 和 fina_indicator() 可获取")
    print("        PE/PB/ROE/ROA/毛利率/资产负债率等基本面数据")
    print("        这些都是价值因子 (Value Factor) 的核心输入")


# ══════════════════════════════════════════════════════════════════════
# §6  获取指数成分股权重
# ══════════════════════════════════════════════════════════════════════
#
# 接口: pro.index_weight() — 指数成分股权重
#   所需积分: 120 分
#   用途: 组合优化 (Black-Litterman 的市场均衡权重)
# =============================================================================

print("\n[§6] 获取指数成分股权重")

if TOKEN_VALID and PRO:
    try:
        # 沪深300 成分股权重 (每月更新)
        index_weights = PRO.index_weight(
            index_code='000300.SH',
            start_date='20240101',
            end_date='20250601',
        )
        if index_weights is not None and len(index_weights) > 0:
            print(f"  沪深300 成分股权重: {len(index_weights)} 条记录")
            print(f"  字段: {list(index_weights.columns)}")
            # 最新一期 Top 10
            latest_date = index_weights['trade_date'].max() if 'trade_date' in index_weights.columns else None
            if latest_date is not None:
                latest_weights = index_weights[
                    index_weights['trade_date'] == latest_date
                ].nlargest(10, 'weight') if 'weight' in index_weights.columns else None
                if latest_weights is not None and len(latest_weights) > 0:
                    print(f"\n  {latest_date} 前 10 大权重股:")
                    for _, row in latest_weights.iterrows():
                        print(f"    {row['con_code']} {row.get('con_name', '')}: "
                              f"{row['weight']:.2%}")
        else:
            print("  未获取到数据")
    except Exception as e:
        print(f"  获取失败: {e}")
else:
    print("  (跳过 — Token 未配置)")
    print("  提示: index_weight() 可获取沪深300/中证500等指数的成分股权重")
    print("        这是 Black-Litterman 模型市场均衡组合的必需输入")


# ══════════════════════════════════════════════════════════════════════
# §7  获取行业分类 (申万行业)
# ══════════════════════════════════════════════════════════════════════
#
# 申万行业分类是中国 A 股投研最常用的行业分类标准。
# 在组合优化中 (demo_05), 行业约束需要知道每只股票属于哪个行业。
#
# 接口: pro.index_classify() — 申万行业分类
#   所需积分: 120 分
# =============================================================================

print("\n[§7] 获取行业分类")

if TOKEN_VALID and PRO:
    try:
        # 获取申万一级行业分类 (level='L1')
        sw_classify = PRO.index_classify(
            level='L1',
            src='SW2021'  # 申万2021版行业分类
        )
        if sw_classify is not None and len(sw_classify) > 0:
            print(f"  申万一级行业: {sw_classify['industry_name'].nunique()} 个")
            print(f"  各行业成分股数量 Top 10:")
            industry_counts = sw_classify.groupby('industry_name').size().sort_values(ascending=False)
            for name, cnt in industry_counts.head(10).items():
                print(f"    {name}: {cnt} 只")
        else:
            print("  未获取到数据 (接口可能已变更)")
    except Exception as e:
        print(f"  获取失败: {e}")
        print(f"  提示: 申万行业分类接口可能已更新，建议查阅最新文档")
else:
    print("  (跳过 — Token 未配置)")


# ══════════════════════════════════════════════════════════════════════
# §8  数据清洗与对齐 (Tushare 特有)
# ══════════════════════════════════════════════════════════════════════
# =============================================================================

print("\n[§8] 数据清洗与对齐")

if TOKEN_VALID and PRO and 'stock_daily_data' in dir() and stock_daily_data:
    # ── 缺失值分析 ──
    missing = stock_price_panel.isna().sum()
    print(f"  各标的缺失天数:")
    for code, cnt in missing.items():
        name = STOCK_POOL.get(code, code)
        print(f"    {code} ({name}): {cnt} 天 ({cnt/len(stock_price_panel):.1%})")

    # ── ffill + 丢弃行首 ──
    clean_prices = stock_price_panel.ffill().dropna(how='any')
    print(f"\n  清洗前: {stock_price_panel.shape[0]} 天")
    print(f"  清洗后: {clean_prices.shape[0]} 天 (丢弃 {stock_price_panel.shape[0] - clean_prices.shape[0]} 天)")

    # ── 收益率面板 ──
    returns = clean_prices.pct_change().dropna()
    print(f"  收益率面板: {returns.shape}")

    # ── 相关性矩阵 ──
    corr = returns.corr()
    print(f"\n  收益率相关性 (仅展示对角外最高相关的 3 对):")
    corr_unstack = corr.where(
        ~np.eye(len(corr), dtype=bool)
    ).unstack().dropna()
    top_pairs = corr_unstack.abs().nlargest(6)
    for (s1, s2), val in top_pairs.items():
        print(f"    {s1} ↔ {s2}: {val:+.3f}")
else:
    print("  (跳过 — 无数据可清洗)")
    print("  提示: 清洗流程与 demo_akshare_data.py 相同，核心原则:")
    print("    1. 只用 ffill(), 禁止 bfill()")
    print("    2. 填充后 dropna(how='any') 丢弃仍有 NaN 的行")
    print("    3. pct_change() 后再 dropna() 得到收益率面板")


# ══════════════════════════════════════════════════════════════════════
# §9  构建本地数据库
# ══════════════════════════════════════════════════════════════════════
# =============================================================================

print("\n[§9] 构建本地数据库")

DATA_DIR = os.path.join(os.path.dirname(__file__), "data", "tushare")
os.makedirs(DATA_DIR, exist_ok=True)

if TOKEN_VALID and PRO:
    saved_files = []

    if 'stock_daily_data' in dir() and stock_daily_data:
        # 保存清洗后价格面板
        clean_path = os.path.join(DATA_DIR, "stock_price_clean.csv")
        clean_prices.to_csv(clean_path)
        saved_files.append(clean_path)

        # 保存收益率
        ret_path = os.path.join(DATA_DIR, "stock_returns.csv")
        returns.to_csv(ret_path)
        saved_files.append(ret_path)

    if 'index_panel' in dir() and len(index_panel) > 0:
        idx_path = os.path.join(DATA_DIR, "index_prices.csv")
        index_panel.to_csv(idx_path)
        saved_files.append(idx_path)

    # 保存拉取元信息
    meta_path = os.path.join(DATA_DIR, "fetch_metadata.txt")
    with open(meta_path, "w", encoding="utf-8") as f:
        f.write(f"数据源: Tushare Pro\n")
        f.write(f"拉取时间: {datetime.now().isoformat()}\n")
        f.write(f"数据范围: {START_DATE} ~ {END_DATE}\n")
        f.write(f"标的数量: {len(STOCK_POOL)} 只个股, {len(INDEX_CODES)} 个指数\n")
    saved_files.append(meta_path)

    for f in saved_files:
        print(f"  ✓ {f}")

    print(f"\n  数据目录: {DATA_DIR}")
    print(f"  文件列表: {os.listdir(DATA_DIR) if os.path.exists(DATA_DIR) else '无'}")
else:
    print("  (跳过 — 无数据可保存)")


# ══════════════════════════════════════════════════════════════════════
# §10  AKShare vs Tushare 对比总结
# ══════════════════════════════════════════════════════════════════════
# =============================================================================

print("\n" + "=" * 68)
print("  §10  AKShare vs Tushare 对比总结")
print("=" * 68)

print("""
  ┌───────────────────┬─────────────────────┬─────────────────────┐
  │      维度          │      AKShare        │    Tushare Pro      │
  ├───────────────────┼─────────────────────┼─────────────────────┤
  │ 费用              │ 完全免费            │ 基础免费(120分)     │
  │                   │                     │ 高级需积分/捐赠     │
  ├───────────────────┼─────────────────────┼─────────────────────┤
  │ 注册              │ 不需要              │ 需要手机号注册      │
  ├───────────────────┼─────────────────────┼─────────────────────┤
  │ 数据覆盖          │ 极广 (股/基/期/外/  │ 聚焦 A 股/指数/    │
  │                   │  宏/另类/舆情等)    │  基金/财务/因子    │
  ├───────────────────┼─────────────────────┼─────────────────────┤
  │ 数据稳定性        │ 中等 (依赖爬虫,     │ 高 (独立数据源,     │
  │                   │  接口偶发变更)      │  专业维护)          │
  ├───────────────────┼─────────────────────┼─────────────────────┤
  │ 基本面数据        │ 有限 (PE/PB 基本)   │ 丰富 (PE/PB/ROE/   │
  │                   │                     │  财务报表等)        │
  ├───────────────────┼─────────────────────┼─────────────────────┤
  │ 退市股票数据      │ 获取较难            │ stock_basic() 直接  │
  │                   │                     │ 包含退市记录        │
  ├───────────────────┼─────────────────────┼─────────────────────┤
  │ 因子数据          │ 无                  │ 有 (Barra 风格因子) │
  ├───────────────────┼─────────────────────┼─────────────────────┤
  │ 股票代码格式      │ "000001" (纯数字)   │ "000001.SZ" (含市场)│
  ├───────────────────┼─────────────────────┼─────────────────────┤
  │ 调用频率限制      │ 无硬限制 (建议礼貌) │ 有 (按积分等级)     │
  ├───────────────────┼─────────────────────┼─────────────────────┤
  │ 最佳使用场景      │ 研究探索、快速验证  │ 策略验证通过后的     │
  │                   │ 数据覆盖第一选择    │ 生产级数据源         │
  └───────────────────┴─────────────────────┴─────────────────────┘

  推荐路径:
    ① 研究阶段 → AKShare (零成本、快速验证想法)
    ② 策略定型 → Tushare (数据质量更高、接口更稳定)
    ③ 实盘运行 → Tushare + 本地缓存数据库 (减少 API 依赖)

  关键提醒:
    • AKShare 接口偶尔变更，升级版本前记得检查 changelog
    • Tushare 积分用完会拒绝请求，注意剩余调用次数
    • 两个数据源的收盘价可能存在微小差异 (取整/复权方式)
      回测结果因此会有细微不同，这是正常的
    • 长期存储请用 Parquet 格式 (比 CSV 更快更省空间):
      df.to_parquet("data.parquet")
      df = pd.read_parquet("data.parquet")
""")

print("=" * 68)
print("  ✓ Tushare Pro 数据获取 Demo 完成")
print("=" * 68)
print(f"""
  关键收获:
  1. Tushare 需要 Token (免费注册)，积分系统决定可用接口范围
  2. 股票代码格式为 "000001.SZ" / "600000.SH" (含交易所后缀)
  3. Tushare 最大优势: 财务数据 (PE/PB/ROE) + 退市数据 + 稳定性
  4. daily_basic() 和 fina_indicator() 是价值因子的核心数据源
  5. index_weight() 提供 Black-Litterman 所需的市场权重
  6. stock_basic(list_status='D') 是消除幸存者偏差的关键
  7. 研究阶段用 AKShare，定型后用 Tushare 生产化

  下一步:
    - 将 Tushare 拉取的数据写入 demo_04 (Alpha 因子)，
      用真实 PE/PB/ROE 替代合成因子
    - 用真实退市记录修正回测的幸存者偏差
""")
