投资分析数据模型(Security / Equity / Fund / Allocations)
retail-api 把每一笔持仓——无论是个股还是基金/ETF——都建模成一行 securities 记录,再挂上对应类型的分类信息(equities 或 funds)。基金还带有自己在多个维度上的百分比拆分(资产类型、行业、市场、类别、股票风格、债券信用等级),数据来自 Morningstar。这样,顾问端的”投资分析”页面就能**穿透(look-through)**基金持仓,并与直接持有的个股合并,算出家庭层面的组合分析指标。
erDiagram
securities ||--o| equities : "type=equity"
securities ||--o| funds : "type=fund"
securities ||--o{ prices : "security_id"
securities ||--o{ positions : "security_id(实际持仓)"
securities ||--o{ target_category_mix_allocations : "security_id(可选,针对单个证券的目标配置)"
equities }o--|| categories : "category_id(14 个类别之一)"
equities }o--|| sectors : "sector_id"
funds ||--o| allocations_by_asset_type : "fund_id — 股/债/现金/其他 拆分"
funds ||--o| allocations_by_market : "fund_id — 发达/新兴市场拆分"
funds ||--o{ allocations_by_category : "fund_id — 14 个类别各自占比"
funds ||--o{ allocations_by_sector : "fund_id — 各行业占比"
funds ||--o| fund_bond_portfolios : "fund_id"
funds ||--o| fund_equity_portfolios : "fund_id"
fund_bond_portfolios ||--o| allocations_by_bond_quality : "fund_bond_portfolio_id — AAA...below/unrated 占比"
fund_equity_portfolios ||--o{ allocations_by_equity_style : "fund_equity_portfolio_id — 市值x估值 占比"
| Security 类型 | 分类数据来源 | 权重 |
|---|---|---|
equity(个股) | equities.category_id / equities.sector_id —— 单一一个类别、单一一个行业 | 100% |
fund(基金) | allocations_by_category、allocations_by_sector、allocations_by_asset_type、allocations_by_equity_style、allocations_by_bond_quality | 百分比拆分,合计 100% |
fixed_income(固定收益) | 非 Morningstar 来源(手动录入的债券/CD) | 0% 权益类 |
关系定义在生成的基类里:vendor/rightcapital/core-models/src/Schemas/{Security,Fund,Equity,FundBondPortfolio,FundEquityPortfolio}.php;retail-api 的 App\Models\{Security,Fund,Equity} 继承这些基类,并在此之上补充下面的穿透(look-through)访问器。
穿透(Look-Through)机制
Section titled “穿透(Look-Through)机制”App\Models\Security 对外暴露一套统一的访问器接口,不管底层是个股还是基金,调用方都不需要按类型分支处理:
| 访问器 | 个股(100% 落在一个桶里) | 基金(百分比拆分) | 文件位置 |
|---|---|---|---|
getEquityPercentageAttribute() | 100 | $this->fund->equity_percentage(由 allocations_by_asset_type 推导) | app/Models/Security.php:60-79 |
getPercentagesByEquityStyleAttribute() | 来自 equities 行的 {capitalization: {valuation: 100}} | $this->fund->percentages_by_equity_style(来自 fund_equity_portfolios.allocations_by_equity_style) | app/Models/Security.php:92-117 |
getPercentagesByEquitySectorIdAttribute() | {sector_id: 100} | $this->fund->percentages_by_equity_sector_id(来自 allocations_by_sector,过滤出权益类行业) | app/Models/Security.php:180-201 |
getPercentagesByBondSectorIdAttribute() | [] | $this->fund->percentages_by_bond_sector_id | app/Models/Security.php:130-147 |
getPercentagesByCategoryIdAttribute() | {category_id: 100} | $this->fund->percentages_by_category_id(来自 allocations_by_category) | app/Models/Security.php:152-175 |
getPrice() | Price::getAdjustedClose($this->id, ...) | 同上 | app/Models/Security.php:208-211 |
Position(app/Models/Position.php:99-139)把上述所有访问器都转发给 getUnderlyingSecurity()——一笔持仓在任意维度拆分中的贡献 = 市值 × equity_percentage × 该维度百分比。InvestmentAccount(app/Models/InvestmentAccount.php:335-446)按账户汇总,App\Models\Support\Investment(app/Models/Support/Investment.php:120-178)再跨账户汇总到整个家庭。计算结果按 security 缓存在 Redis 里(ONE_DAY_IN_SECONDS / ONE_WEEK_IN_SECONDS 标签),因为同一只基金的拆分数据会被家庭里持有它的每一笔持仓重复复用。
由此支撑的业务功能
Section titled “由此支撑的业务功能”以下四个页面都在 app/Http/Controllers/Calculate/Advisors/Households/Investment/ 目录下:
| 页面 | Controller | 计算内容 |
|---|---|---|
| Asset Allocation(资产配置) | AllocationController.php:46-189 | 把家庭全部持仓按 category_id(14 个类别)穿透聚合 → 当前配置饼图;与顾问设置的 investment_target_category_mix(目标模型)对比 → 目标配置。输出偏离明细表/图、按金额的再平衡建议,以及双方的预期收益/标准差(用到 资产收益与相关性假设体系 里的假设参数)。 |
| Equity Sector & Style(股票行业与风格) | EquitySectorAndStyleController.php:34-90 | 把权益类持仓按 sector_id 和风格箱(市值 × 估值)穿透聚合,再与一个基准证券(默认 VTI,本身也是一只 Morningstar 基金)对比——“你的组合 vs 指数”图。 |
| Concentration(集中度) | ConcentrationController.php:33-168 | 按 security_id 跨账户汇总市值(含公开股票计划持仓),标记单一证券占比过高的集中度风险。 |
| Tax Allocation(税务配置) | TaxAllocationController.php | 按账户的税务属性(应税 / 递延 / 免税)拆分配置,同样叠加上面的类别穿透逻辑。 |
TargetCategoryMixAllocation(vendor/rightcapital/core-models/src/Schemas/TargetCategoryMixAllocation.php)允许顾问的目标模型既可以指定一个 category_id(粗粒度目标,比如”20% Large Growth”),也可以直接指定一个 security_id(比如给一只集中持有的个股定”10% AAPL”的目标)——两者都会进入同一个 Allocation 页面的对比逻辑。
- Morningstar 证券主数据 ——
securities/equities/funds/allocations_by_*/prices的数据来源 - 资产收益与相关性假设体系 —— 同样的 14 个类别如何被赋予预期收益/波动率/相关性假设,供规划引擎使用
- Engine Input Construction —— 计算引擎输入里的
AssetAllocationReturnAndVolatilityAssumptionsSection
- retail-api(
web-service/api)仓库 ——app/Models/{Security,Fund,Equity,Position,InvestmentAccount,Category}.php、app/Models/Support/Investment.php、app/Http/Controllers/Calculate/Advisors/Households/Investment/*.php,读取于develop分支,2026-07。 vendor/rightcapital/core-modelscomposer 包 ——src/Schemas/{Security,Fund,Equity,FundBondPortfolio,FundEquityPortfolio,TargetCategoryMixAllocation}.php。