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

257 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 数据加载指南
## 概述
投资组合 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 | 否 |
### 解析规则
```python
# 列名模糊匹配规则
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 为宽表,每列为一个组合的净值序列:
```python
# 输入:长表
# 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 中的行业名称映射到标准申万一级行业代码:
```python
# 模糊匹配逻辑
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
```
---
## 表识别算法
```python
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"
```
---
## 通用解析配置
### 日期解析
```python
# 支持多种日期格式
DATE_FORMATS = [
"%Y-%m-%d", # 2026-01-02
"%Y/%m/%d", # 2026/01/02
"%Y%m%d", # 20260102
"%Y-%m-%d %H:%M:%S", # 带时间
]
```
### 数值清洗
```python
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")
```
### 百分比检测
```python
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
```