skills/portfolio-analyzer/references/data-loading-guide.md
wangyitong a65adcc2e5 Initial commit: merged, deduplicated, and vetted skill collection
Sources: extracted from two upstream archives (skill-repo, skills-main),
merged with the following policy:

- 15 broken symlinks (pointing to /Users/jameslee/.cc-switch/skills or
  ../../.agents/skills on a foreign machine) discarded
- 3 real name collisions with identical content (ai-pair, ifind-http-api,
  zhipu-websearch) kept as one copy
- Functional overlaps deduped keeping the strongest variant:
  - docx family: kept docx (official, full toolchain) + docx-cn
    (GB/T 9704 Chinese official-document constants),
    dropped docx_writer (no scripts, name collided with docx)
  - humanizer family: kept humanizer-zh (6 zh reference docs),
    dropped humanizer (en, redundant for CN workflow)
- Skills that only ran in a foreign environment removed:
  ablemind-ops, app-publish, hlb-design-system, openclaw-adj-skill,
  claude-driver
- alphapai excluded from this public repo because its SKILL.md hard-coded
  live credentials

Result: 25 skills, 572 files, ~7.5 MB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 14:47:12 +08:00

8.1 KiB
Raw Blame History

数据加载指南

概述

投资组合 Excel 文件包含 5 张表sheet每张表结构不同。load_portfolio_data() 通过特征列匹配自动识别表类型,无需依赖 sheet 名称。


表 1时序分析NAV 时序数据)

识别特征

  • 含有"日期"列 + "单位净值"或"累计净值"列
  • 或含有"日期" + "组合代码"/"组合名称" + "净值"列

列映射

原始列名(可能变体) 标准化列名 dtype 必需
日期 / 交易日期 / Date date datetime64
组合代码 / 组合编码 / PortCode portfolio_code str
组合名称 / 组合简称 / PortName portfolio_name str
单位净值 / 净值 / NAV nav float64
累计净值 / AccNav acc_nav float64
日收益率 / DailyReturn daily_return float64
累计收益率 / AccReturn acc_return float64

解析规则

# 列名模糊匹配规则
COLUMN_PATTERNS_NAV = {
    "date": ["日期", "交易日期", "Date", "TradeDate"],
    "portfolio_code": ["组合代码", "组合编码", "PortCode", "产品代码"],
    "portfolio_name": ["组合名称", "组合简称", "PortName", "产品名称"],
    "nav": ["单位净值", "净值", "NAV", "UnitNAV"],
    "acc_nav": ["累计净值", "AccNAV", "累计单位净值"],
    "daily_return": ["日收益率", "DailyReturn", "日回报"],
    "acc_return": ["累计收益率", "AccReturn", "累计回报"],
}

Pivot 逻辑

长表(多组合在同一列)→ pivot 为宽表,每列为一个组合的净值序列:

# 输入:长表
#   date | portfolio_code | nav
#   2026-01-02 | P001 | 1.0000
#   2026-01-02 | P002 | 1.0000
#   2026-01-03 | P001 | 1.0050

# 输出:宽表
#            | P001   | P002
# 2026-01-02 | 1.0000 | 1.0000
# 2026-01-03 | 1.0050 | NaN

nav_wide = df.pivot(index="date", columns="portfolio_code", values="nav")
nav_wide = nav_wide.sort_index().ffill()  # 前向填充缺失值

表 2交易明细

识别特征

  • 含有"证券代码"/"股票代码" + "成交金额"/"交易金额"列
  • 或含有"买卖方向"/"交易方向"列

列映射

原始列名 标准化列名 dtype 必需
日期 / 交易日期 date datetime64
组合代码 portfolio_code str
证券代码 / 股票代码 sec_code str
证券名称 / 股票名称 sec_name str
买卖方向 / 交易方向 direction str
成交数量 / 交易数量 volume float64
成交价格 / 交易价格 price float64
成交金额 / 交易金额 amount float64
手续费 / 佣金 commission float64

处理规则

  • 直接透传,不做 pivot
  • 统一买卖方向标签:买入/B → "买入",卖出/S → "卖出"
  • 确保 amount 为正数(卖出交易不取负)

表 3收益贡献

识别特征

  • 含有"收益贡献"/"收益率贡献"/"绝对收益率贡献"列
  • 或含有"持仓市值" + "收益"列

列映射

原始列名 标准化列名 dtype 必需
组合代码 portfolio_code str
证券代码 / 股票代码 sec_code str
证券名称 / 股票名称 sec_name str
行业 / 所属行业 / 申万行业 industry str
持仓市值 / 市值 market_value float64
权重 / 持仓权重 weight float64
收益率 / 个股收益率 sec_return float64
收益贡献 / 收益率贡献 / 绝对收益率贡献 return_contrib float64

处理规则

  • return_contrib 绝对值降序排序
  • 便于输出 Top/Bottom 贡献个股

表 4业绩归因资产大类

识别特征

  • 含有"资产大类"/"资产类别"列
  • 或含有"配置效应"/"选股效应"列(排除含"行业"相关列的表)

列映射

原始列名 标准化列名 dtype 必需
组合代码 portfolio_code str
资产大类 / 大类资产 asset_class str
资产类别 / 子类 asset_subclass str
组合权重 / 实际权重 port_weight float64
基准权重 bench_weight float64
组合收益 / 实际收益 port_return float64
基准收益 bench_return float64
配置效应 allocation float64
选股效应 selection float64
交互效应 interaction float64
总归因 / 超额收益 total_attribution float64

层级结构

资产大类 → 资产类别为两级层级Excel 输出时应体现父子关系:

股票
  ├── 主板
  ├── 创业板
  └── 科创板
债券
  ├── 利率债
  └── 信用债
现金

表 5行业归因

识别特征

  • 含有"行业"列 + "配置效应"/"选股效应"列
  • 且不含"资产大类"/"资产类别"列(与表 4 区分)

列映射

原始列名 标准化列名 dtype 必需
组合代码 portfolio_code str
行业 / 行业名称 / 申万行业 industry str
组合权重 / 行业权重 port_weight float64
基准权重 bench_weight float64
组合收益 / 行业收益 port_return float64
基准收益 bench_return float64
配置效应 allocation float64
选股效应 selection float64
交互效应 interaction float64
总归因 / 超额收益 total_attribution float64

行业代码匹配

使用 shenwan-industry-codes.md 中的模糊匹配表,将用户 Excel 中的行业名称映射到标准申万一级行业代码:

# 模糊匹配逻辑
def match_industry(name: str) -> str | None:
    """将行业名称匹配到 Wind 指数代码"""
    for code, standard_name in SHENWAN_L1_NAMES.items():
        if standard_name in name or name in standard_name:
            return code
    # 简称匹配
    ALIASES = {"家电": "家用电器", "非银": "非银金融", "电新": "电力设备", ...}
    if name in ALIASES:
        return match_industry(ALIASES[name])
    return None

表识别算法

def identify_sheet(df: pd.DataFrame) -> str:
    """根据特征列识别表类型"""
    cols = set(c.strip() for c in df.columns)

    # 关键词集合
    nav_keywords = {"单位净值", "累计净值", "NAV", "净值"}
    trade_keywords = {"成交金额", "交易金额", "买卖方向", "交易方向"}
    contrib_keywords = {"收益贡献", "收益率贡献", "绝对收益率贡献"}
    asset_keywords = {"资产大类", "大类资产", "资产类别"}
    industry_keywords = {"行业", "行业名称", "申万行业"}
    brinson_keywords = {"配置效应", "选股效应"}

    if cols & nav_keywords:
        return "nav_timeseries"
    if cols & trade_keywords:
        return "trade_detail"
    if cols & contrib_keywords:
        return "return_contribution"
    if cols & asset_keywords:
        return "asset_attribution"
    if (cols & industry_keywords) and (cols & brinson_keywords):
        return "industry_attribution"

    return "unknown"

通用解析配置

日期解析

# 支持多种日期格式
DATE_FORMATS = [
    "%Y-%m-%d",      # 2026-01-02
    "%Y/%m/%d",      # 2026/01/02
    "%Y%m%d",        # 20260102
    "%Y-%m-%d %H:%M:%S",  # 带时间
]

数值清洗

def clean_numeric(s: pd.Series) -> pd.Series:
    """清洗数值列:去除百分号、逗号、空格"""
    if s.dtype == object:
        s = s.str.replace("%", "", regex=False)
        s = s.str.replace(",", "", regex=False)
        s = s.str.strip()
    return pd.to_numeric(s, errors="coerce")

百分比检测

def detect_percentage(s: pd.Series) -> bool:
    """检测列值是否为百分比形式(值域 -100 ~ 100 而非 -1 ~ 1"""
    valid = s.dropna()
    if len(valid) == 0:
        return False
    return valid.abs().max() > 1.0 and valid.abs().max() <= 100.0