alphacouncil-agent 0.9.4 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +4 -4
- package/AGENTS.md +25 -11
- package/CHANGELOG.md +146 -0
- package/CLAUDE.md +24 -7
- package/README.ja.md +15 -10
- package/README.md +21 -13
- package/README.zh-CN.md +17 -12
- package/data/authored/core-seats.mjs +808 -0
- package/data/authored/growth-seats.mjs +581 -0
- package/data/authored/index.mjs +114 -0
- package/data/authored/quant-seats.mjs +564 -0
- package/data/authored/value-seats.mjs +663 -0
- package/data/build-profile.v1.json +2 -1
- package/data/council-evaluation-protocol.v1.json +7 -4
- package/data/master-selector-method-locales.v1.mjs +10 -0
- package/data/persona-v3-ai-assisted-solo-profile.v1.json +2 -2
- package/data/persona-v3-build-specs.v1.mjs +60 -3
- package/docs/INSTALL.md +6 -10
- package/docs/personas.md +2 -1
- package/docs/plans/v1.0-roadmap.md +487 -0
- package/docs/plans/v1.1-basket-native-seats.md +267 -0
- package/docs/releases/v0.9.5.md +96 -0
- package/docs/releases/v1.0.0.md +164 -0
- package/docs/report-contract.md +30 -6
- package/knowledge/ai-assisted-solo/experiments/runs/a.json +13 -13
- package/knowledge/ai-assisted-solo/experiments/runs/b.json +2 -2
- package/knowledge/ai-assisted-solo/experiments/runs/c.json +2 -2
- package/knowledge/ai-assisted-solo/experiments/runs/d13.json +126 -126
- package/knowledge/ai-assisted-solo/experiments/runs/d26.json +274 -254
- package/knowledge/ai-assisted-solo/experiments/runs/e-d13.json +129 -129
- package/knowledge/ai-assisted-solo/experiments/runs/e-d26.json +277 -257
- package/knowledge/ai-assisted-solo/experiments/runs/h_ai_reference.json +278 -258
- package/knowledge/ai-assisted-solo/experiments/simulation-input.json +7278 -556
- package/knowledge/ai-assisted-solo/experiments/simulation-manifest.json +22 -22
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_ackman/reviews/{change_value_bridge.ai-review.json → market_capitalisation.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_ackman/reviews/{power_map.ai-review.json → owner_earnings_yield.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_aschenbrenner/reviews/{compute_power_bridge.ai-review.json → market_capitalisation.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_aschenbrenner/reviews/{timeline_reverse_valuation.ai-review.json → price_implied_cash_yield.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_asness/reviews/{crowding_cost_stress.ai-review.json → nominal_earnings_yield_gap.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_asness/reviews/{factor_decomposer.ai-review.json → real_earnings_yield_gap.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_bogle/reviews/expected_return_over_long_bond.ai-review.json +277 -0
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_bogle/reviews/fundamental_expected_return.ai-review.json +277 -0
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_buffett/reviews/{incremental_returns.ai-review.json → market_capitalisation.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_buffett/reviews/{owner_earnings_rebuilder.ai-review.json → owner_earnings_yield.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_burry/reviews/{mispricing_carry.ai-review.json → net_current_asset_surplus_per_share.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_burry/reviews/{capital_structure_reader.ai-review.json → net_current_asset_value_per_share.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_cathie_wood/reviews/{cost_adoption_curve.ai-review.json → current_cash_yield.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_cathie_wood/reviews/{capture_valuation_bridge.ai-review.json → market_capitalisation.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_dalio/reviews/{regime_classifier.ai-review.json → policy_real_rate.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/{master_munger/reviews/incentive_map.ai-review.json → master_dalio/reviews/real_curve_slope.ai-review.json} +51 -51
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_damodaran/reviews/{reverse_valuation.ai-review.json → implied_equity_risk_premium.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_damodaran/reviews/{story_dcf.ai-review.json → premium_versus_long_run_average.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_druckenmiller/reviews/{liquidity_revision_map.ai-review.json → liquidity_curve_impulse.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_druckenmiller/reviews/{inflection_payoff.ai-review.json → policy_real_rate.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_duan_yongping/reviews/{user_value_evidence.ai-review.json → five_year_opportunity_cost.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_duan_yongping/reviews/{opportunity_cost.ai-review.json → margin_retention.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_fisher/reviews/{research_productivity.ai-review.json → margin_retention.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_fisher/reviews/{scuttlebutt_graph.ai-review.json → reinvestment_spread.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/{master_dalio/reviews/debt_driver_stress.ai-review.json → master_forensic_short/reviews/cash_conversion_gap.ai-review.json} +51 -51
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_forensic_short/reviews/{accounting_rebuilder.ai-review.json → cash_cover_of_leverage.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_graham/reviews/{asset_floor.ai-review.json → defensive_hurdle_base.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_graham/reviews/{normalized_earnings.ai-review.json → half_normalised_earnings_yield.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_jhunjhunwala/reviews/{penetration_liquidity.ai-review.json → cash_quality_gap.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_jhunjhunwala/reviews/{promoter_governance.ai-review.json → real_structural_growth.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_klarman/reviews/{recovery_waterfall.ai-review.json → downside_asset_coverage.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_klarman/reviews/{cash_catalyst_compare.ai-review.json → market_capitalisation.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_li_lu/reviews/{promise_integrity_ledger.ai-review.json → cash_backed_decade_return.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_li_lu/reviews/{ten_year_return_bridge.ai-review.json → decade_return_over_long_bond.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_lynch/reviews/{story_numbers_check.ai-review.json → growth_justified_capitalisation.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_lynch/reviews/{category_classifier.ai-review.json → market_capitalisation.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_marks/reviews/{cycle_temperature.ai-review.json → credit_cycle_position.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_marks/reviews/{implied_expectations.ai-review.json → equity_over_corporate_debt.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_munger/reviews/{failure_path_graph.ai-review.json → debt_service_cushion.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_munger/reviews/reported_earnings_cash_gap.ai-review.json +277 -0
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_natenberg/reviews/{surface_builder.ai-review.json → normalised_skew.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_natenberg/reviews/{greeks_payoff.ai-review.json → round_trip_cost.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_pabrai/reviews/{downside_floor.ai-review.json → downside_floor_coverage.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_pabrai/reviews/{discrete_payoff.ai-review.json → market_capitalisation.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_simons/reviews/{leakage_audit.ai-review.json → absolute_session_move.ai-review.json} +42 -42
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_simons/reviews/{oos_cost_test.ai-review.json → session_move_over_implied.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_sinclair/reviews/{realized_vol_forecast.ai-review.json → round_trip_cost.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_sinclair/reviews/{net_edge_sizer.ai-review.json → volatility_per_unit_of_width.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_soros/reviews/{reflexivity_graph.ai-review.json → credit_cycle_position.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_soros/reviews/{reversal_monitor.ai-review.json → financing_burden.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_taleb/reviews/{tail_friction.ai-review.json → debt_service_cushion.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_taleb/reviews/{payoff_ruin.ai-review.json → normalised_skew.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_thorp/reviews/{fractional_kelly.ai-review.json → edge_per_unit_of_volatility.ai-review.json} +46 -46
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_thorp/reviews/{edge_recalculator.ai-review.json → net_edge.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/review-manifest.json +288 -270
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-semantic-adjudications/index.json +2 -2
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-semantic-extractions/index.json +2 -2
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-semantic-skeptic-reviews/index.json +2 -2
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-source-prereviews/index.json +6 -2
- package/knowledge/solo-test/masters/master_ackman/decision_policy.json +122 -11
- package/knowledge/solo-test/masters/master_ackman/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_ackman/manifest.json +13 -6
- package/knowledge/solo-test/masters/master_ackman/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_ackman/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_ackman/tools.json +56 -30
- package/knowledge/solo-test/masters/master_aschenbrenner/decision_policy.json +101 -10
- package/knowledge/solo-test/masters/master_aschenbrenner/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_aschenbrenner/manifest.json +12 -4
- package/knowledge/solo-test/masters/master_aschenbrenner/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_aschenbrenner/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_aschenbrenner/tools.json +56 -30
- package/knowledge/solo-test/masters/master_asness/decision_policy.json +89 -10
- package/knowledge/solo-test/masters/master_asness/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_asness/manifest.json +11 -4
- package/knowledge/solo-test/masters/master_asness/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_asness/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_asness/tools.json +56 -30
- package/knowledge/solo-test/masters/master_bogle/counterfactuals.jsonl +0 -0
- package/knowledge/solo-test/masters/master_bogle/decision_cases.jsonl +0 -0
- package/knowledge/solo-test/masters/master_bogle/decision_policy.json +170 -0
- package/knowledge/solo-test/masters/master_bogle/doctrine.jsonl +14 -0
- package/knowledge/solo-test/masters/master_bogle/evaluation/calibration_cases.jsonl +0 -0
- package/knowledge/solo-test/masters/master_bogle/evaluation/experiments.json +5 -0
- package/knowledge/solo-test/masters/master_bogle/evaluation/golden_cases.jsonl +0 -0
- package/knowledge/solo-test/masters/master_bogle/evaluation/pairwise_cases.jsonl +0 -0
- package/knowledge/solo-test/masters/master_bogle/failures.jsonl +0 -0
- package/knowledge/solo-test/masters/master_bogle/manifest.json +141 -0
- package/knowledge/solo-test/masters/master_bogle/memory_policy.json +5 -0
- package/knowledge/solo-test/masters/master_bogle/provisional-index.json +41 -0
- package/knowledge/solo-test/masters/master_bogle/research_policy.json +11 -0
- package/knowledge/solo-test/masters/master_bogle/sources.jsonl +1 -0
- package/knowledge/solo-test/masters/master_bogle/tools.json +120 -0
- package/knowledge/solo-test/masters/master_bogle/voice.en.md +7 -0
- package/knowledge/solo-test/masters/master_bogle/voice.zh.md +7 -0
- package/knowledge/solo-test/masters/master_buffett/decision_policy.json +102 -11
- package/knowledge/solo-test/masters/master_buffett/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_buffett/manifest.json +10 -4
- package/knowledge/solo-test/masters/master_buffett/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_buffett/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_buffett/tools.json +55 -29
- package/knowledge/solo-test/masters/master_burry/decision_policy.json +102 -12
- package/knowledge/solo-test/masters/master_burry/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_burry/manifest.json +12 -6
- package/knowledge/solo-test/masters/master_burry/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_burry/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_burry/tools.json +56 -30
- package/knowledge/solo-test/masters/master_cathie_wood/decision_policy.json +74 -12
- package/knowledge/solo-test/masters/master_cathie_wood/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_cathie_wood/manifest.json +12 -6
- package/knowledge/solo-test/masters/master_cathie_wood/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_cathie_wood/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_cathie_wood/tools.json +56 -30
- package/knowledge/solo-test/masters/master_dalio/decision_policy.json +123 -16
- package/knowledge/solo-test/masters/master_dalio/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_dalio/manifest.json +10 -4
- package/knowledge/solo-test/masters/master_dalio/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_dalio/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_dalio/tools.json +56 -30
- package/knowledge/solo-test/masters/master_damodaran/decision_policy.json +58 -10
- package/knowledge/solo-test/masters/master_damodaran/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_damodaran/manifest.json +9 -4
- package/knowledge/solo-test/masters/master_damodaran/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_damodaran/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_damodaran/tools.json +54 -28
- package/knowledge/solo-test/masters/master_druckenmiller/decision_policy.json +89 -10
- package/knowledge/solo-test/masters/master_druckenmiller/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_druckenmiller/manifest.json +11 -4
- package/knowledge/solo-test/masters/master_druckenmiller/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_druckenmiller/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_druckenmiller/tools.json +55 -29
- package/knowledge/solo-test/masters/master_duan_yongping/decision_policy.json +138 -13
- package/knowledge/solo-test/masters/master_duan_yongping/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_duan_yongping/manifest.json +17 -7
- package/knowledge/solo-test/masters/master_duan_yongping/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_duan_yongping/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_duan_yongping/tools.json +82 -30
- package/knowledge/solo-test/masters/master_fisher/decision_policy.json +102 -11
- package/knowledge/solo-test/masters/master_fisher/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_fisher/manifest.json +13 -6
- package/knowledge/solo-test/masters/master_fisher/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_fisher/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_fisher/tools.json +58 -32
- package/knowledge/solo-test/masters/master_forensic_short/decision_policy.json +93 -14
- package/knowledge/solo-test/masters/master_forensic_short/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_forensic_short/manifest.json +6 -4
- package/knowledge/solo-test/masters/master_forensic_short/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_forensic_short/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_forensic_short/tools.json +53 -27
- package/knowledge/solo-test/masters/master_graham/decision_policy.json +103 -12
- package/knowledge/solo-test/masters/master_graham/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_graham/manifest.json +12 -4
- package/knowledge/solo-test/masters/master_graham/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_graham/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_graham/tools.json +67 -28
- package/knowledge/solo-test/masters/master_jhunjhunwala/decision_policy.json +77 -12
- package/knowledge/solo-test/masters/master_jhunjhunwala/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_jhunjhunwala/manifest.json +11 -6
- package/knowledge/solo-test/masters/master_jhunjhunwala/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_jhunjhunwala/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_jhunjhunwala/tools.json +54 -28
- package/knowledge/solo-test/masters/master_klarman/decision_policy.json +105 -14
- package/knowledge/solo-test/masters/master_klarman/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_klarman/manifest.json +8 -2
- package/knowledge/solo-test/masters/master_klarman/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_klarman/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_klarman/tools.json +55 -29
- package/knowledge/solo-test/masters/master_li_lu/decision_policy.json +101 -10
- package/knowledge/solo-test/masters/master_li_lu/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_li_lu/manifest.json +13 -6
- package/knowledge/solo-test/masters/master_li_lu/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_li_lu/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_li_lu/tools.json +58 -32
- package/knowledge/solo-test/masters/master_lynch/decision_policy.json +127 -16
- package/knowledge/solo-test/masters/master_lynch/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_lynch/manifest.json +14 -6
- package/knowledge/solo-test/masters/master_lynch/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_lynch/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_lynch/tools.json +67 -28
- package/knowledge/solo-test/masters/master_marks/decision_policy.json +71 -9
- package/knowledge/solo-test/masters/master_marks/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_marks/manifest.json +11 -6
- package/knowledge/solo-test/masters/master_marks/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_marks/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_marks/tools.json +54 -28
- package/knowledge/solo-test/masters/master_munger/decision_policy.json +141 -16
- package/knowledge/solo-test/masters/master_munger/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_munger/manifest.json +10 -6
- package/knowledge/solo-test/masters/master_munger/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_munger/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_munger/tools.json +52 -26
- package/knowledge/solo-test/masters/master_natenberg/decision_policy.json +59 -11
- package/knowledge/solo-test/masters/master_natenberg/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_natenberg/manifest.json +9 -6
- package/knowledge/solo-test/masters/master_natenberg/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_natenberg/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_natenberg/tools.json +50 -24
- package/knowledge/solo-test/masters/master_pabrai/decision_policy.json +125 -14
- package/knowledge/solo-test/masters/master_pabrai/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_pabrai/manifest.json +8 -2
- package/knowledge/solo-test/masters/master_pabrai/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_pabrai/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_pabrai/tools.json +55 -29
- package/knowledge/solo-test/masters/master_simons/decision_policy.json +59 -11
- package/knowledge/solo-test/masters/master_simons/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_simons/manifest.json +8 -4
- package/knowledge/solo-test/masters/master_simons/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_simons/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_simons/tools.json +43 -30
- package/knowledge/solo-test/masters/master_sinclair/decision_policy.json +59 -11
- package/knowledge/solo-test/masters/master_sinclair/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_sinclair/manifest.json +8 -6
- package/knowledge/solo-test/masters/master_sinclair/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_sinclair/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_sinclair/tools.json +54 -28
- package/knowledge/solo-test/masters/master_soros/decision_policy.json +98 -10
- package/knowledge/solo-test/masters/master_soros/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_soros/manifest.json +10 -7
- package/knowledge/solo-test/masters/master_soros/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_soros/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_soros/tools.json +54 -28
- package/knowledge/solo-test/masters/master_taleb/decision_policy.json +124 -13
- package/knowledge/solo-test/masters/master_taleb/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_taleb/manifest.json +9 -5
- package/knowledge/solo-test/masters/master_taleb/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_taleb/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_taleb/tools.json +50 -24
- package/knowledge/solo-test/masters/master_thorp/decision_policy.json +61 -13
- package/knowledge/solo-test/masters/master_thorp/doctrine.jsonl +2 -2
- package/knowledge/solo-test/masters/master_thorp/manifest.json +11 -6
- package/knowledge/solo-test/masters/master_thorp/provisional-index.json +3 -3
- package/knowledge/solo-test/masters/master_thorp/sources.jsonl +1 -1
- package/knowledge/solo-test/masters/master_thorp/tools.json +56 -30
- package/knowledge/solo-test/persona-v3-solo-test-formulas/compilation-manifest.json +290 -264
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_ackman/components/tools.json +58 -32
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_ackman/provisional-derivations/market_capitalisation.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_ackman/provisional-derivations/owner_earnings_yield.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_aschenbrenner/components/tools.json +58 -32
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_aschenbrenner/provisional-derivations/market_capitalisation.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_aschenbrenner/provisional-derivations/price_implied_cash_yield.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_asness/components/tools.json +58 -32
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_asness/provisional-derivations/nominal_earnings_yield_gap.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_asness/provisional-derivations/real_earnings_yield_gap.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_bogle/components/tools.json +120 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_bogle/provisional-derivations/expected_return_over_long_bond.derived-proxy-evidence.json +77 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_bogle/provisional-derivations/fundamental_expected_return.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_buffett/components/tools.json +57 -31
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_buffett/provisional-derivations/market_capitalisation.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_buffett/provisional-derivations/owner_earnings_yield.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_burry/components/tools.json +58 -32
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_burry/provisional-derivations/net_current_asset_surplus_per_share.derived-proxy-evidence.json +77 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_burry/provisional-derivations/net_current_asset_value_per_share.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_cathie_wood/components/tools.json +58 -32
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_cathie_wood/provisional-derivations/current_cash_yield.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_cathie_wood/provisional-derivations/market_capitalisation.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_dalio/components/tools.json +58 -32
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_dalio/provisional-derivations/policy_real_rate.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_dalio/provisional-derivations/real_curve_slope.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_damodaran/components/tools.json +56 -30
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_damodaran/provisional-derivations/implied_equity_risk_premium.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_damodaran/provisional-derivations/{reverse_valuation.derived-proxy-evidence.json → premium_versus_long_run_average.derived-proxy-evidence.json} +29 -16
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_druckenmiller/components/tools.json +57 -31
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_druckenmiller/provisional-derivations/liquidity_curve_impulse.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_druckenmiller/provisional-derivations/policy_real_rate.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_duan_yongping/components/tools.json +84 -32
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_duan_yongping/provisional-derivations/five_year_opportunity_cost.derived-proxy-evidence.json +102 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_duan_yongping/provisional-derivations/margin_retention.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_fisher/components/tools.json +60 -34
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_fisher/provisional-derivations/margin_retention.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_fisher/provisional-derivations/reinvestment_spread.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_forensic_short/components/tools.json +55 -29
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_forensic_short/provisional-derivations/{accounting_rebuilder.derived-proxy-evidence.json → cash_conversion_gap.derived-proxy-evidence.json} +28 -15
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_forensic_short/provisional-derivations/cash_cover_of_leverage.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_graham/components/tools.json +69 -30
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_graham/provisional-derivations/defensive_hurdle_base.derived-proxy-evidence.json +89 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_graham/provisional-derivations/half_normalised_earnings_yield.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_jhunjhunwala/components/tools.json +56 -30
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_jhunjhunwala/provisional-derivations/cash_quality_gap.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_jhunjhunwala/provisional-derivations/real_structural_growth.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_klarman/components/tools.json +57 -31
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_klarman/provisional-derivations/downside_asset_coverage.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_klarman/provisional-derivations/market_capitalisation.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_li_lu/components/tools.json +60 -34
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_li_lu/provisional-derivations/cash_backed_decade_return.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_li_lu/provisional-derivations/decade_return_over_long_bond.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_lynch/components/tools.json +69 -30
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_lynch/provisional-derivations/growth_justified_capitalisation.derived-proxy-evidence.json +89 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_lynch/provisional-derivations/market_capitalisation.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_marks/components/tools.json +56 -30
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_marks/provisional-derivations/credit_cycle_position.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_marks/provisional-derivations/equity_over_corporate_debt.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_munger/components/tools.json +54 -28
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_munger/provisional-derivations/debt_service_cushion.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_munger/provisional-derivations/reported_earnings_cash_gap.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_natenberg/components/tools.json +52 -26
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_natenberg/provisional-derivations/{surface_builder.derived-proxy-evidence.json → normalised_skew.derived-proxy-evidence.json} +27 -14
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_natenberg/provisional-derivations/round_trip_cost.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_pabrai/components/tools.json +57 -31
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_pabrai/provisional-derivations/downside_floor_coverage.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_pabrai/provisional-derivations/market_capitalisation.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_simons/components/tools.json +45 -32
- package/knowledge/solo-test/persona-v3-solo-test-formulas/{master_natenberg/provisional-derivations/greeks_payoff.derived-proxy-evidence.json → master_simons/provisional-derivations/absolute_session_move.derived-proxy-evidence.json} +17 -17
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_simons/provisional-derivations/session_move_over_implied.derived-proxy-evidence.json +77 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_sinclair/components/tools.json +56 -30
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_sinclair/provisional-derivations/round_trip_cost.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_sinclair/provisional-derivations/volatility_per_unit_of_width.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_soros/components/tools.json +56 -30
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_soros/provisional-derivations/credit_cycle_position.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_soros/provisional-derivations/financing_burden.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_taleb/components/tools.json +52 -26
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_taleb/provisional-derivations/debt_service_cushion.derived-proxy-evidence.json +76 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_taleb/provisional-derivations/{tail_friction.derived-proxy-evidence.json → normalised_skew.derived-proxy-evidence.json} +27 -14
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_thorp/components/tools.json +58 -32
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_thorp/provisional-derivations/edge_per_unit_of_volatility.derived-proxy-evidence.json +77 -0
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_thorp/provisional-derivations/net_edge.derived-proxy-evidence.json +76 -0
- package/mcp/lib/breadth.mjs +126 -0
- package/mcp/lib/constants.mjs +15 -0
- package/mcp/lib/damodaran.mjs +119 -0
- package/mcp/lib/fred.mjs +246 -0
- package/mcp/lib/fund-holdings-parsers.mjs +297 -0
- package/mcp/lib/fund-observations.mjs +152 -0
- package/mcp/lib/fundamentals.mjs +806 -0
- package/mcp/lib/funds.mjs +785 -0
- package/mcp/lib/gates.mjs +71 -4
- package/mcp/lib/grounding.mjs +209 -36
- package/mcp/lib/index-aggregate.mjs +733 -0
- package/mcp/lib/insider-ownership.mjs +149 -0
- package/mcp/lib/instrument-facts.mjs +855 -0
- package/mcp/lib/instruments.mjs +216 -0
- package/mcp/lib/markdown.mjs +216 -43
- package/mcp/lib/master-catalog.mjs +4 -0
- package/mcp/lib/orchestrator.mjs +173 -29
- package/mcp/lib/packets.mjs +59 -6
- package/mcp/lib/personas/engine.mjs +23 -10
- package/mcp/lib/personas-v3/deterministic-executor.mjs +13 -2
- package/mcp/lib/personas-v3/ga-external-evidence.mjs +3 -2
- package/mcp/lib/personas-v3/ga-gate.mjs +2 -1
- package/mcp/lib/personas-v3/ga-package-evidence.mjs +5 -4
- package/mcp/lib/personas-v3/grounding-adapter.mjs +309 -2
- package/mcp/lib/personas-v3/production-root.mjs +5 -4
- package/mcp/lib/personas-v3/release-formula-evidence.mjs +14 -7
- package/mcp/lib/personas-v3/release-source-evidence.mjs +2 -1
- package/mcp/lib/personas-v3/releases.mjs +5 -4
- package/mcp/lib/personas-v3/runtime-release-integrity.mjs +6 -5
- package/mcp/lib/personas-v3/semantic-source-adjudication.mjs +2 -1
- package/mcp/lib/personas-v3/semantic-source-extraction.mjs +2 -1
- package/mcp/lib/personas-v3/semantic-source-skeptic-review.mjs +2 -1
- package/mcp/lib/personas-v3/source-review-operations.mjs +3 -2
- package/mcp/lib/personas-v3/staging.mjs +22 -3
- package/mcp/lib/prompts.mjs +87 -3
- package/mcp/lib/quotes.mjs +5 -0
- package/mcp/lib/rpc.mjs +27 -10
- package/mcp/lib/run-store.mjs +5 -3
- package/mcp/lib/sec.mjs +32 -0
- package/mcp/lib/tables.mjs +11 -0
- package/mcp/lib/voice.mjs +122 -0
- package/mcp/lib/xbrl-aggregates.mjs +153 -0
- package/mcp/lib/xbrl-series.mjs +341 -0
- package/mcp/lib/xlsx.mjs +155 -0
- package/package.json +5 -5
- package/personas/masters/masters-value/bogle.md +145 -0
- package/schemas/council-evaluation-protocol-v1.schema.json +2 -2
- package/schemas/persona-v3-ai-assisted-solo-profile-v1.schema.json +2 -2
- package/schemas/persona-v3-ai-machine-simulation-v1.schema.json +1 -1
- package/schemas/persona-v3-build-spec-v1.schema.json +3 -3
- package/schemas/persona-v3-release-formula-review-evidence-v1.schema.json +3 -3
- package/schemas/persona-v3-release-manifest-v1.schema.json +6 -6
- package/schemas/persona-v3-release-source-review-evidence-v1.schema.json +3 -3
- package/schemas/persona-v3-tool-graph-v1.schema.json +1 -1
- package/schemas/source-review-batch-v1.schema.json +9 -9
- package/scripts/assemble-persona-v3-release.mjs +3 -2
- package/scripts/build-persona-v3-solo-test-packs.mjs +6 -8
- package/scripts/check-persona-v3-ga.mjs +1 -1
- package/scripts/check-persona-v3-production-candidates.mjs +1 -1
- package/scripts/compile-persona-v3-formulas.mjs +19 -18
- package/scripts/doctor.mjs +11 -9
- package/scripts/export-persona-source-review-batch.mjs +1 -1
- package/scripts/generate-persona-v3-drafts.mjs +2 -2
- package/scripts/lib/build-profile.mjs +24 -0
- package/scripts/lib/council-evaluation-protocol.mjs +7 -6
- package/scripts/lib/host-capabilities.mjs +2 -1
- package/scripts/lib/packaged-host-parity.mjs +9 -3
- package/scripts/lib/persona-v3-ai-assisted-solo-status.mjs +6 -4
- package/scripts/lib/persona-v3-ai-formula-review.mjs +6 -5
- package/scripts/lib/persona-v3-ai-machine-simulations.mjs +32 -4
- package/scripts/lib/persona-v3-build-specs.mjs +3 -2
- package/scripts/lib/persona-v3-draft-packs.mjs +2 -2
- package/scripts/lib/persona-v3-formula-pipeline.mjs +11 -10
- package/scripts/lib/persona-v3-production-candidates.mjs +10 -9
- package/scripts/lib/persona-v3-solo-formula-pipeline.mjs +188 -16
- package/scripts/lib/persona-v3-solo-test-packs.mjs +112 -11
- package/scripts/package-smoke.mjs +2 -1
- package/scripts/report-persona-corpus-gaps.mjs +4 -1
- package/scripts/review-persona-v3-solo-formulas.mjs +12 -8
- package/scripts/scaffold-persona-v3-staging.mjs +1 -1
- package/skills/agent-skills-governance/SKILL.md +8 -2
- package/skills/alphacouncil-agent/SKILL.md +12 -4
- package/skills/public-equity-investing/SKILL.md +17 -0
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_forensic_short/reviews/borrow_catalyst.ai-review.json +0 -277
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_ackman/provisional-derivations/change_value_bridge.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_ackman/provisional-derivations/power_map.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_aschenbrenner/provisional-derivations/compute_power_bridge.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_aschenbrenner/provisional-derivations/timeline_reverse_valuation.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_asness/provisional-derivations/crowding_cost_stress.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_asness/provisional-derivations/factor_decomposer.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_buffett/provisional-derivations/incremental_returns.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_buffett/provisional-derivations/owner_earnings_rebuilder.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_burry/provisional-derivations/capital_structure_reader.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_burry/provisional-derivations/mispricing_carry.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_cathie_wood/provisional-derivations/capture_valuation_bridge.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_cathie_wood/provisional-derivations/cost_adoption_curve.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_dalio/provisional-derivations/debt_driver_stress.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_dalio/provisional-derivations/regime_classifier.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_damodaran/provisional-derivations/story_dcf.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_druckenmiller/provisional-derivations/inflection_payoff.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_druckenmiller/provisional-derivations/liquidity_revision_map.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_duan_yongping/provisional-derivations/opportunity_cost.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_duan_yongping/provisional-derivations/user_value_evidence.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_fisher/provisional-derivations/research_productivity.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_fisher/provisional-derivations/scuttlebutt_graph.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_forensic_short/provisional-derivations/borrow_catalyst.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_graham/provisional-derivations/asset_floor.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_graham/provisional-derivations/normalized_earnings.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_jhunjhunwala/provisional-derivations/penetration_liquidity.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_jhunjhunwala/provisional-derivations/promoter_governance.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_klarman/provisional-derivations/cash_catalyst_compare.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_klarman/provisional-derivations/recovery_waterfall.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_li_lu/provisional-derivations/promise_integrity_ledger.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_li_lu/provisional-derivations/ten_year_return_bridge.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_lynch/provisional-derivations/category_classifier.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_lynch/provisional-derivations/story_numbers_check.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_marks/provisional-derivations/cycle_temperature.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_marks/provisional-derivations/implied_expectations.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_munger/provisional-derivations/failure_path_graph.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_munger/provisional-derivations/incentive_map.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_pabrai/provisional-derivations/discrete_payoff.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_pabrai/provisional-derivations/downside_floor.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_simons/provisional-derivations/leakage_audit.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_simons/provisional-derivations/oos_cost_test.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_sinclair/provisional-derivations/net_edge_sizer.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_sinclair/provisional-derivations/realized_vol_forecast.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_soros/provisional-derivations/reflexivity_graph.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_soros/provisional-derivations/reversal_monitor.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_taleb/provisional-derivations/payoff_ruin.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_thorp/provisional-derivations/edge_recalculator.derived-proxy-evidence.json +0 -64
- package/knowledge/solo-test/persona-v3-solo-test-formulas/master_thorp/provisional-derivations/fractional_kelly.derived-proxy-evidence.json +0 -64
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn a fund or index into typed-fact candidates, including look-through.
|
|
3
|
+
*
|
|
4
|
+
* A basket has no income statement, which is why twenty-five method seats used to abstain on
|
|
5
|
+
* QQQ. But Buffett defined look-through earnings in the 1990 shareholder letter for exactly
|
|
6
|
+
* this situation -- owning pieces of many businesses -- so aggregating the constituents'
|
|
7
|
+
* ratios by weight is the method's own answer, not a workaround bolted onto it.
|
|
8
|
+
*
|
|
9
|
+
* The aggregation itself lives in `funds.mjs`, which refuses to express an absolute-currency
|
|
10
|
+
* sum at all. This module's job is orchestration and honesty about coverage: every
|
|
11
|
+
* look-through fact carries the weight it was computed over, because a portfolio ratio
|
|
12
|
+
* measured across 8% of the basket is a different claim from one measured across 95%.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { LIMITS } from "./constants.mjs";
|
|
16
|
+
import {
|
|
17
|
+
MIN_LOOK_THROUGH_COVERAGE,
|
|
18
|
+
fetchFundHoldings,
|
|
19
|
+
fetchFundMetadata,
|
|
20
|
+
lookThroughAggregate,
|
|
21
|
+
topHoldingsCoverage,
|
|
22
|
+
} from "./funds.mjs";
|
|
23
|
+
import { fetchIndexAggregate, INDEX_PROXIES, normalizeIndexSymbol } from "./index-aggregate.mjs";
|
|
24
|
+
import { fetchImpliedErp } from "./damodaran.mjs";
|
|
25
|
+
import { chartUrl, fetchBasketBreadth, parseDailyCloses } from "./breadth.mjs";
|
|
26
|
+
import { flowInputs, recordFundObservation } from "./fund-observations.mjs";
|
|
27
|
+
import { fundFlow } from "./funds.mjs";
|
|
28
|
+
import { fetchText } from "./quotes.mjs";
|
|
29
|
+
import { deriveFundamentals } from "./fundamentals.mjs";
|
|
30
|
+
import { evaluateRules } from "./screen.mjs";
|
|
31
|
+
import { fetchCompanyFacts, fetchUniverse } from "./sec.mjs";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* How many constituents may be resolved for a look-through pass.
|
|
35
|
+
*
|
|
36
|
+
* Every constituent costs one filing fetch, and weights decay fast enough that the tail buys
|
|
37
|
+
* coverage very slowly: on a cap-weighted basket the top forty names usually clear 60%.
|
|
38
|
+
* ponytail: fixed cap, revisit if a genuinely equal-weighted basket needs the tail.
|
|
39
|
+
*/
|
|
40
|
+
export const LOOK_THROUGH_MAX_CONSTITUENTS = 40;
|
|
41
|
+
export const LOOK_THROUGH_TARGET_COVERAGE = 0.6;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The operating-company facts a basket can supply by aggregating its constituents.
|
|
45
|
+
*
|
|
46
|
+
* Only ratios appear here. An absolute-currency look-through would be "ETF revenue", which is
|
|
47
|
+
* a category error `funds.mjs` refuses to express at all.
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* Pack fact id -> the aggregation rule that governs it in `funds.mjs`.
|
|
51
|
+
*
|
|
52
|
+
* The two vocabularies differ on purpose: the aggregator is named for the SHAPE of a metric
|
|
53
|
+
* (a price multiple aggregates harmonically, a margin arithmetically), while a pack asks for
|
|
54
|
+
* the fact it reasons about. Keeping the translation explicit here means neither side has to
|
|
55
|
+
* rename anything, and an unmapped fact simply produces no look-through value rather than a
|
|
56
|
+
* silently mis-aggregated one.
|
|
57
|
+
*
|
|
58
|
+
* Only ratios appear. An absolute-currency look-through would be "ETF revenue", which is a
|
|
59
|
+
* category error `funds.mjs` refuses to express at all.
|
|
60
|
+
*/
|
|
61
|
+
export const LOOK_THROUGH_FACT_RULES = Object.freeze({
|
|
62
|
+
"financial.leverage": "leverage.debt_to_equity",
|
|
63
|
+
"valuation.revenue_growth": "growth.revenue_growth",
|
|
64
|
+
// Every one of these is a pure ratio that a weighted mean of constituents answers honestly,
|
|
65
|
+
// and every one of them was already legal to aggregate -- it simply had no line here, which
|
|
66
|
+
// is why eighteen seats stayed silent on a basket whose data was complete.
|
|
67
|
+
"accounting.cash_conversion": "accounting.cash_conversion",
|
|
68
|
+
"financial.gross_margin_5y": "profitability.gross_margin",
|
|
69
|
+
"financial.net_margin_5y": "profitability.net_margin",
|
|
70
|
+
"financial.return_on_equity_10y": "profitability.return_on_equity_10y",
|
|
71
|
+
"financial.incremental_return_on_capital": "profitability.incremental_return_on_capital",
|
|
72
|
+
"financial.interest_coverage": "coverage.interest_coverage",
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Facts a constituent publishes that are NOT aggregable, and why.
|
|
77
|
+
*
|
|
78
|
+
* Owner earnings, NCAV and the downside values are absolute currency amounts. Summing them
|
|
79
|
+
* across a basket produces "the ETF's owner earnings", which is the exact category error the
|
|
80
|
+
* whole look-through discipline exists to prevent. Turning them into a portfolio number needs
|
|
81
|
+
* a per-constituent denominator -- market capitalisation -- so they become yields first. That
|
|
82
|
+
* is real and worth doing; it is simply not this pass, and pretending otherwise by summing is
|
|
83
|
+
* the failure mode, not the shortcut.
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* Absolute company figures a fund owns a real, computable dollar share of.
|
|
87
|
+
*
|
|
88
|
+
* `LOOK_THROUGH_BLOCKED` refuses the naive version of this and is right to: summing every
|
|
89
|
+
* constituent's owner earnings produces "the ETF's owner earnings" as though the fund owned
|
|
90
|
+
* each business outright, which it does not. What the fund does own is a stake — its position
|
|
91
|
+
* value over the company's market capitalisation — and that stake's claim on the figure is
|
|
92
|
+
* ordinary look-through accounting:
|
|
93
|
+
*
|
|
94
|
+
* fund's claim = AUM x SUM over i of ( weight_i / market_cap_i ) x figure_i
|
|
95
|
+
*
|
|
96
|
+
* The result is in dollars and is the fund's, not the index's. Because the fund's own market
|
|
97
|
+
* capitalisation is its AUM, every seat that divides one of these by market capitalisation
|
|
98
|
+
* gets the weighted look-through yield without a single change to its method.
|
|
99
|
+
*/
|
|
100
|
+
export const LOOK_THROUGH_CLAIM_FACTS = Object.freeze([
|
|
101
|
+
"financial.owner_earnings",
|
|
102
|
+
"financial.free_cash_flow_5y",
|
|
103
|
+
"financial.net_current_asset_value",
|
|
104
|
+
"valuation.downside_asset_value",
|
|
105
|
+
"valuation.downside_floor",
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
export const LOOK_THROUGH_BLOCKED = Object.freeze({
|
|
109
|
+
"financial.owner_earnings": "absolute currency; needs a per-constituent market capitalisation to become a yield",
|
|
110
|
+
"financial.net_current_asset_value": "absolute currency; needs a per-constituent market capitalisation to become a ratio",
|
|
111
|
+
"valuation.downside_asset_value": "absolute currency; needs a per-constituent market capitalisation to become a ratio",
|
|
112
|
+
"valuation.downside_floor": "absolute currency; needs a per-constituent market capitalisation to become a ratio",
|
|
113
|
+
"capital_allocation.share_count": "a count, not a rate; it has no portfolio-level meaning",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Mechanical-screen rule id -> the pack fact id it supplies, mirroring the grounding adapter's
|
|
118
|
+
* own map. Percent rules publish 15 for 15%, and every pack ratio is a decimal.
|
|
119
|
+
*/
|
|
120
|
+
const SCREEN_RULE_FACTS = Object.freeze({
|
|
121
|
+
roe_10y: "financial.return_on_equity_10y",
|
|
122
|
+
interest_cover: "financial.interest_coverage",
|
|
123
|
+
gross_margin: "financial.gross_margin_5y",
|
|
124
|
+
ocf_over_ni: "accounting.cash_conversion",
|
|
125
|
+
net_margin: "financial.net_margin_5y",
|
|
126
|
+
// An absolute, and the only one the screen carries. It is not aggregated as a ratio -- it
|
|
127
|
+
// feeds the ownership-claim pass, where dollars are the right unit.
|
|
128
|
+
fcf_5y: "financial.free_cash_flow_5y",
|
|
129
|
+
});
|
|
130
|
+
const SCREEN_PERCENT_RULES = new Set(["roe_10y", "gross_margin", "net_margin"]);
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The unit a look-through fact must carry.
|
|
134
|
+
*
|
|
135
|
+
* The aggregate of a quantity IS that quantity, so it has to arrive under the same contract the
|
|
136
|
+
* single-company path declares. Emitting every aggregate as a bare `decimal` made the executor
|
|
137
|
+
* reject coverage and cash conversion, which are multiples -- the fact was present and unusable,
|
|
138
|
+
* which reads exactly like the fact being absent.
|
|
139
|
+
*/
|
|
140
|
+
const LOOK_THROUGH_UNITS = Object.freeze({
|
|
141
|
+
"financial.interest_coverage": "multiple",
|
|
142
|
+
"accounting.cash_conversion": "multiple",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
export const LOOK_THROUGH_FACT_IDS = Object.freeze(Object.keys(LOOK_THROUGH_FACT_RULES));
|
|
146
|
+
|
|
147
|
+
const finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The index whose published valuation describes a fund's portfolio.
|
|
151
|
+
*
|
|
152
|
+
* A cap-weighted tracker holds the index, so the index's aggregate P/E is the honest
|
|
153
|
+
* valuation of the fund's book -- and it is the only same-basis aggregate available for free.
|
|
154
|
+
* Derived by inverting the proxy map so the two directions cannot drift apart: whichever ETF
|
|
155
|
+
* stands in for an index is also the ETF that borrows that index's valuation.
|
|
156
|
+
*/
|
|
157
|
+
const VALUATION_INDEX_BY_FUND = Object.freeze(Object.fromEntries(
|
|
158
|
+
Object.entries(INDEX_PROXIES).flatMap(([index, proxy]) => (
|
|
159
|
+
[proxy.etf, ...(proxy.alternates || [])].map((etf) => [etf, index])
|
|
160
|
+
)),
|
|
161
|
+
));
|
|
162
|
+
|
|
163
|
+
export function valuationIndexFor(symbol) {
|
|
164
|
+
return VALUATION_INDEX_BY_FUND[String(symbol || "").trim().toUpperCase()] || null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Facts an index publishes about itself. Each carries its basis, because index P/E is quoted
|
|
169
|
+
* on incompatible bases and a fact that does not name its own is unusable.
|
|
170
|
+
*/
|
|
171
|
+
function valuationFacts(aggregate) {
|
|
172
|
+
const valuation = aggregate?.valuation;
|
|
173
|
+
if (!valuation || !finite(valuation.pe_trailing)) return [];
|
|
174
|
+
const facts = [];
|
|
175
|
+
const shared = {
|
|
176
|
+
source_kind: "market_snapshot",
|
|
177
|
+
source_url: valuation.source_url,
|
|
178
|
+
public_at: valuation.public_at,
|
|
179
|
+
observation_date: valuation.trade_date,
|
|
180
|
+
basis: valuation.basis,
|
|
181
|
+
title: `${valuation.name} valuation (${valuation.basis})`,
|
|
182
|
+
confidence: 0.8,
|
|
183
|
+
};
|
|
184
|
+
facts.push({
|
|
185
|
+
...shared,
|
|
186
|
+
fact_id: "index.aggregate_pe_ttm",
|
|
187
|
+
value: valuation.pe_trailing,
|
|
188
|
+
value_kind: "ratio",
|
|
189
|
+
unit: "multiple",
|
|
190
|
+
ratio_denominator: "trailing_twelve_month_earnings",
|
|
191
|
+
method: valuation.basis,
|
|
192
|
+
});
|
|
193
|
+
// The reciprocal is what a valuation method actually compares against a bond yield, so it
|
|
194
|
+
// is published as its own fact rather than left for each seat to recompute differently.
|
|
195
|
+
facts.push({
|
|
196
|
+
...shared,
|
|
197
|
+
fact_id: "index.aggregate_earnings_yield",
|
|
198
|
+
value: Number((1 / valuation.pe_trailing).toFixed(6)),
|
|
199
|
+
value_kind: "ratio",
|
|
200
|
+
unit: "decimal",
|
|
201
|
+
ratio_denominator: "price",
|
|
202
|
+
method: `reciprocal_of_${valuation.basis}_trailing_pe`,
|
|
203
|
+
});
|
|
204
|
+
if (finite(valuation.pe_forward)) {
|
|
205
|
+
facts.push({
|
|
206
|
+
...shared,
|
|
207
|
+
fact_id: "index.aggregate_pe_forward",
|
|
208
|
+
value: valuation.pe_forward,
|
|
209
|
+
value_kind: "ratio",
|
|
210
|
+
unit: "multiple",
|
|
211
|
+
ratio_denominator: "forward_twelve_month_earnings",
|
|
212
|
+
method: `${valuation.basis}_forward`,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
if (finite(valuation.dividend_yield)) {
|
|
216
|
+
facts.push({
|
|
217
|
+
...shared,
|
|
218
|
+
fact_id: "index.dividend_yield",
|
|
219
|
+
// Declared as percent by the source; the typed-fact contract is decimal.
|
|
220
|
+
value: valuation.dividend_yield / 100,
|
|
221
|
+
value_kind: "ratio",
|
|
222
|
+
unit: "decimal",
|
|
223
|
+
ratio_denominator: "price",
|
|
224
|
+
method: valuation.basis,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return facts;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Concentration and cost, which are properties of the wrapper rather than of the businesses. */
|
|
231
|
+
function structureFacts(holdings, metadata) {
|
|
232
|
+
const facts = [];
|
|
233
|
+
if (!holdings?.holdings?.length) return facts;
|
|
234
|
+
const shared = {
|
|
235
|
+
source_kind: "issuer_disclosure",
|
|
236
|
+
source_url: holdings.source_url,
|
|
237
|
+
public_at: holdings.public_at,
|
|
238
|
+
observation_date: holdings.as_of,
|
|
239
|
+
confidence: 0.9,
|
|
240
|
+
derivation: "rederived",
|
|
241
|
+
};
|
|
242
|
+
const sorted = [...holdings.holdings].sort((left, right) => (right.weight || 0) - (left.weight || 0));
|
|
243
|
+
const topTen = sorted.slice(0, 10).reduce((sum, row) => sum + (row.weight || 0), 0);
|
|
244
|
+
facts.push({
|
|
245
|
+
...shared,
|
|
246
|
+
fact_id: "fund.top_ten_weight",
|
|
247
|
+
value: Number(topTen.toFixed(6)),
|
|
248
|
+
value_kind: "ratio",
|
|
249
|
+
unit: "decimal",
|
|
250
|
+
ratio_denominator: "portfolio_weight",
|
|
251
|
+
method: "sum_of_ten_largest_published_weights",
|
|
252
|
+
title: `${holdings.symbol} top-ten concentration`,
|
|
253
|
+
});
|
|
254
|
+
// Herfindahl over published weights: one number that separates "a basket" from "three
|
|
255
|
+
// positions and some rounding".
|
|
256
|
+
const hhi = sorted.reduce((sum, row) => sum + ((row.weight || 0) ** 2), 0);
|
|
257
|
+
facts.push({
|
|
258
|
+
...shared,
|
|
259
|
+
fact_id: "fund.concentration_hhi",
|
|
260
|
+
value: Number(hhi.toFixed(6)),
|
|
261
|
+
value_kind: "ratio",
|
|
262
|
+
unit: "decimal",
|
|
263
|
+
ratio_denominator: "sum_of_squared_weights",
|
|
264
|
+
method: "herfindahl_over_published_weights",
|
|
265
|
+
title: `${holdings.symbol} concentration index`,
|
|
266
|
+
});
|
|
267
|
+
if (finite(metadata?.expense_ratio)) {
|
|
268
|
+
// Issuers publish the fee as a percent; the typed-fact contract is a decimal fraction.
|
|
269
|
+
// Passing it through unconverted is a hundredfold error that looks entirely plausible.
|
|
270
|
+
const asDecimal = metadata.expense_ratio_unit === "percent"
|
|
271
|
+
? metadata.expense_ratio / 100
|
|
272
|
+
: metadata.expense_ratio;
|
|
273
|
+
facts.push({
|
|
274
|
+
...shared,
|
|
275
|
+
fact_id: "fund.expense_ratio",
|
|
276
|
+
value: Number(asDecimal.toFixed(6)),
|
|
277
|
+
value_kind: "ratio",
|
|
278
|
+
unit: "decimal",
|
|
279
|
+
ratio_denominator: "net_assets",
|
|
280
|
+
derivation: "reported",
|
|
281
|
+
source_url: metadata.source_url || holdings.source_url,
|
|
282
|
+
public_at: metadata.public_at || holdings.public_at,
|
|
283
|
+
method: "issuer_published",
|
|
284
|
+
title: `${holdings.symbol} expense ratio`,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
return facts;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Resolve constituent fundamentals for a look-through pass.
|
|
292
|
+
*
|
|
293
|
+
* One filing fetch per constituent, bounded twice over: by `LOOK_THROUGH_MAX_CONSTITUENTS`
|
|
294
|
+
* and by stopping as soon as the target coverage weight is reached. A constituent that cannot
|
|
295
|
+
* be resolved -- a foreign filer, a share class the ticker map does not carry -- is skipped
|
|
296
|
+
* rather than substituted, and its weight simply does not count toward coverage. That keeps
|
|
297
|
+
* the coverage number honest, which is the only thing that makes the aggregate meaningful.
|
|
298
|
+
*/
|
|
299
|
+
/**
|
|
300
|
+
* True when a diagnostic names a fact the pack ended up producing anyway. Matching is by the
|
|
301
|
+
* fact id appearing in the note, so a note that names no fact is always kept.
|
|
302
|
+
*/
|
|
303
|
+
function factsProducedFor(note, facts) {
|
|
304
|
+
const produced = new Set(facts.map((fact) => fact.fact_id));
|
|
305
|
+
return [...produced].some((factId) => String(note).includes(factId))
|
|
306
|
+
|| (/implied equity risk premium/iu.test(note) && produced.has("valuation.implied_erp"))
|
|
307
|
+
|| (/valuation percentile/iu.test(note) && produced.has("cycle.valuation_percentile"));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export async function resolveConstituentFacts(holdings, { signal, asOf = null, concurrency = 6 } = {}) {
|
|
311
|
+
const perHolding = new Map();
|
|
312
|
+
// The aggregate inherits whatever span its inputs covered; without carrying it the basket
|
|
313
|
+
// fact arrives as a bare number that no duration contract can accept.
|
|
314
|
+
const perHoldingPeriods = new Map();
|
|
315
|
+
// A share count is not aggregable and is not aggregated. It is kept because a market
|
|
316
|
+
// capitalisation -- shares times price -- is what turns an absolute figure into the fund's
|
|
317
|
+
// own dollar claim on it.
|
|
318
|
+
const shareCounts = new Map();
|
|
319
|
+
const unavailable = [];
|
|
320
|
+
const ranked = [...holdings]
|
|
321
|
+
.sort((left, right) => (right.weight || 0) - (left.weight || 0))
|
|
322
|
+
.slice(0, LOOK_THROUGH_MAX_CONSTITUENTS);
|
|
323
|
+
if (!ranked.length) return { perHolding, perHoldingPeriods, shareCounts, unavailable, attempted: 0 };
|
|
324
|
+
|
|
325
|
+
let universe = null;
|
|
326
|
+
try {
|
|
327
|
+
universe = new Map((await fetchUniverse({ signal }))
|
|
328
|
+
.map((row) => [String(row.ticker).toUpperCase(), row.cik]));
|
|
329
|
+
} catch (error) {
|
|
330
|
+
return {
|
|
331
|
+
perHolding,
|
|
332
|
+
perHoldingPeriods,
|
|
333
|
+
shareCounts,
|
|
334
|
+
unavailable: [`look-through: SEC ticker universe unavailable (${String(error?.message || error)})`],
|
|
335
|
+
attempted: 0,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const queue = [...ranked];
|
|
340
|
+
const worker = async () => {
|
|
341
|
+
for (let next = queue.shift(); next; next = queue.shift()) {
|
|
342
|
+
const ticker = String(next.ticker || "").toUpperCase();
|
|
343
|
+
const cik = universe.get(ticker);
|
|
344
|
+
if (!cik) { unavailable.push(`look-through ${ticker}: no SEC registrant for this ticker`); continue; }
|
|
345
|
+
try {
|
|
346
|
+
// One document, both readers. The derived fundamentals and the mechanical screen read
|
|
347
|
+
// the same Company Facts filing, and fetching it twice per constituent would double
|
|
348
|
+
// the cost of a look-through for metrics that were already on disk. The screen is
|
|
349
|
+
// where the margins, cash conversion, ten-year ROE and interest coverage live -- which
|
|
350
|
+
// is why wiring only the fundamentals path left those aggregates permanently empty.
|
|
351
|
+
const companyFacts = await fetchCompanyFacts(cik, { signal });
|
|
352
|
+
const derived = deriveFundamentals({ companyFacts, asOf });
|
|
353
|
+
const screened = evaluateRules(companyFacts, { asOf });
|
|
354
|
+
const facts = {};
|
|
355
|
+
const periods = {};
|
|
356
|
+
for (const rule of screened.rules || []) {
|
|
357
|
+
const factId = SCREEN_RULE_FACTS[rule?.id];
|
|
358
|
+
if (!factId || rule.skipped || !Number.isFinite(rule.value)) continue;
|
|
359
|
+
// The screen publishes percentages as 15 for 15%; every pack ratio is a decimal.
|
|
360
|
+
facts[factId] = SCREEN_PERCENT_RULES.has(rule.id) ? rule.value / 100 : rule.value;
|
|
361
|
+
if (rule.period_start && rule.period_end) {
|
|
362
|
+
periods[factId] = { start: rule.period_start, end: rule.period_end };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
for (const [factId, metric] of Object.entries(derived.metrics || {})) {
|
|
366
|
+
if (!metric || !Number.isFinite(metric.value)) continue;
|
|
367
|
+
facts[factId] = metric.value;
|
|
368
|
+
if (factId === "capital_allocation.share_count") shareCounts.set(ticker, metric.value);
|
|
369
|
+
if (metric.period_start && metric.period_end) {
|
|
370
|
+
periods[factId] = { start: metric.period_start, end: metric.period_end };
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (Object.keys(facts).length) perHolding.set(ticker, facts);
|
|
374
|
+
if (Object.keys(periods).length) perHoldingPeriods.set(ticker, periods);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
unavailable.push(`look-through ${ticker}: ${String(error?.message || error)}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, ranked.length) }, worker));
|
|
381
|
+
return { perHolding, perHoldingPeriods, shareCounts, unavailable, attempted: ranked.length };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Aggregate constituent ratios into portfolio-level operating-company facts.
|
|
386
|
+
*
|
|
387
|
+
* `perHoldingFacts` is supplied by the caller rather than fetched here: resolving it means one
|
|
388
|
+
* filing per constituent, and the caller owns the budget for that. Passing an empty map is a
|
|
389
|
+
* valid, cheap run -- it yields no look-through facts and one explicit gap, which is the
|
|
390
|
+
* correct outcome and not a failure.
|
|
391
|
+
*/
|
|
392
|
+
/**
|
|
393
|
+
* The interval a basket-level aggregate covers.
|
|
394
|
+
*
|
|
395
|
+
* The outer envelope -- earliest start to latest end -- is the wrong answer. Constituents keep
|
|
396
|
+
* different fiscal calendars, so the union of forty five-year spans is five years plus a year
|
|
397
|
+
* of calendar edges, and a fact that genuinely covers five years then fails a five-year
|
|
398
|
+
* contract for a reason that has nothing to do with its coverage.
|
|
399
|
+
*
|
|
400
|
+
* The median end date and the median span describe what the aggregate actually covers: a
|
|
401
|
+
* typical constituent's reporting window, which is the window the number was averaged over.
|
|
402
|
+
*/
|
|
403
|
+
function aggregatePeriod(perHoldingPeriods, factId, tickers) {
|
|
404
|
+
const spans = [];
|
|
405
|
+
for (const ticker of tickers) {
|
|
406
|
+
const span = perHoldingPeriods?.get(ticker)?.[factId];
|
|
407
|
+
if (!span?.start || !span?.end) continue;
|
|
408
|
+
const start = Date.parse(span.start);
|
|
409
|
+
const end = Date.parse(span.end);
|
|
410
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) continue;
|
|
411
|
+
spans.push({ end, days: (end - start) / 86_400_000 });
|
|
412
|
+
}
|
|
413
|
+
if (!spans.length) return {};
|
|
414
|
+
const median = (values) => {
|
|
415
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
416
|
+
return sorted[Math.floor(sorted.length / 2)];
|
|
417
|
+
};
|
|
418
|
+
const end = median(spans.map((span) => span.end));
|
|
419
|
+
const days = median(spans.map((span) => span.days));
|
|
420
|
+
return {
|
|
421
|
+
period_start: new Date(end - days * 86_400_000).toISOString().slice(0, 10),
|
|
422
|
+
period_end: new Date(end).toISOString().slice(0, 10),
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* The dollar claim a fund has on an absolute company figure, summed across constituents.
|
|
428
|
+
*
|
|
429
|
+
* Requires a market capitalisation per constituent, which is its own filed share count times
|
|
430
|
+
* its last close — both already in hand from the fundamentals pass and the breadth pass. A
|
|
431
|
+
* constituent missing either is excluded and costs coverage rather than being assumed.
|
|
432
|
+
*/
|
|
433
|
+
/**
|
|
434
|
+
* A share count for a fund whose holdings file does not carry one.
|
|
435
|
+
*
|
|
436
|
+
* There are two routes and they are not equally good, which matters because a flow is a
|
|
437
|
+
* DIFFERENCE of two counts and therefore keeps only their error.
|
|
438
|
+
*
|
|
439
|
+
* The good route is the issuer's own assets over its own net asset value, on the same date
|
|
440
|
+
* from the same source: that ratio IS shares outstanding, an identity rather than an estimate,
|
|
441
|
+
* and it is trustworthy enough to difference.
|
|
442
|
+
*
|
|
443
|
+
* The weak route is the assets implied by disclosed positions over the fund's market price. It
|
|
444
|
+
* is fine for a market capitalisation -- the product is the size we already had -- and unfit
|
|
445
|
+
* for a flow, because a 95%-priced position sum and a premium to NAV each carry error that a
|
|
446
|
+
* subtraction leaves behind while cancelling the number. `flowInputs` refuses it by basis.
|
|
447
|
+
*/
|
|
448
|
+
async function impliedFundShares(symbol, netAssets, metadata, signal) {
|
|
449
|
+
if (finite(metadata?.nav) && metadata.nav > 0 && finite(metadata?.aum) && metadata.aum > 0) {
|
|
450
|
+
return { value: metadata.aum / metadata.nav, how: "issuer_aum_over_nav" };
|
|
451
|
+
}
|
|
452
|
+
if (!finite(netAssets) || netAssets <= 0) return null;
|
|
453
|
+
try {
|
|
454
|
+
const closes = parseDailyCloses(JSON.parse(await fetchText(chartUrl(symbol), LIMITS.QUOTE_FETCH_MS, signal)));
|
|
455
|
+
const close = closes?.at(-1);
|
|
456
|
+
if (!finite(close) || close <= 0) return null;
|
|
457
|
+
return { value: netAssets / close, how: "assets_from_disclosed_positions_over_market_price" };
|
|
458
|
+
} catch {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export function lookThroughClaims({ holdings, perHoldingFacts, perHoldingPeriods, shareCounts, closes, netAssets, holdingsMeta }) {
|
|
464
|
+
const facts = [];
|
|
465
|
+
const unavailable = [];
|
|
466
|
+
if (!finite(netAssets) || netAssets <= 0) {
|
|
467
|
+
unavailable.push("look-through claims: the fund's own size is unknown, so a dollar claim cannot be scaled");
|
|
468
|
+
return { facts, unavailable };
|
|
469
|
+
}
|
|
470
|
+
const priced = new Map();
|
|
471
|
+
for (const holding of holdings || []) {
|
|
472
|
+
const ticker = String(holding?.ticker || "").toUpperCase();
|
|
473
|
+
const shares = shareCounts?.get?.(ticker);
|
|
474
|
+
const close = closes?.[ticker];
|
|
475
|
+
if (!finite(holding?.weight) || !finite(shares) || shares <= 0 || !finite(close) || close <= 0) continue;
|
|
476
|
+
priced.set(ticker, { weight: holding.weight, marketCap: shares * close });
|
|
477
|
+
}
|
|
478
|
+
if (!priced.size) {
|
|
479
|
+
unavailable.push("look-through claims: no constituent supplied both a share count and a price");
|
|
480
|
+
return { facts, unavailable };
|
|
481
|
+
}
|
|
482
|
+
for (const factId of LOOK_THROUGH_CLAIM_FACTS) {
|
|
483
|
+
let claim = 0;
|
|
484
|
+
let covered = 0;
|
|
485
|
+
for (const [ticker, { weight, marketCap }] of priced) {
|
|
486
|
+
const value = perHoldingFacts?.get?.(ticker)?.[factId];
|
|
487
|
+
if (!finite(value)) continue;
|
|
488
|
+
claim += (weight / marketCap) * value;
|
|
489
|
+
covered += weight;
|
|
490
|
+
}
|
|
491
|
+
if (covered < MIN_LOOK_THROUGH_COVERAGE) {
|
|
492
|
+
unavailable.push(
|
|
493
|
+
`look-through claim ${factId}: ${(covered * 100).toFixed(1)}% of the basket by weight supplied it,`
|
|
494
|
+
+ ` below the ${(MIN_LOOK_THROUGH_COVERAGE * 100).toFixed(0)}% floor`,
|
|
495
|
+
);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
facts.push({
|
|
499
|
+
fact_id: factId,
|
|
500
|
+
value: Number((claim * netAssets).toFixed(2)),
|
|
501
|
+
// A claim on a one-year figure covers a year and a claim on a five-year total covers
|
|
502
|
+
// five. It inherits the window its inputs reported, for the same reason the ratio
|
|
503
|
+
// aggregates do.
|
|
504
|
+
...aggregatePeriod(perHoldingPeriods, factId, [...priced.keys()]),
|
|
505
|
+
value_kind: "monetary",
|
|
506
|
+
unit: "currency_units",
|
|
507
|
+
currency: "USD",
|
|
508
|
+
scale: 1,
|
|
509
|
+
source_kind: "issuer_disclosure",
|
|
510
|
+
source_url: holdingsMeta?.source_url,
|
|
511
|
+
public_at: holdingsMeta?.public_at,
|
|
512
|
+
observation_date: holdingsMeta?.as_of,
|
|
513
|
+
confidence: 0.65,
|
|
514
|
+
derivation: "rederived",
|
|
515
|
+
method: "fund_ownership_share_of_constituent_figure",
|
|
516
|
+
coverage_weight: Number(covered.toFixed(6)),
|
|
517
|
+
title: `${holdingsMeta?.symbol || "fund"} look-through claim on ${factId}`,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
return { facts, unavailable };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export function lookThroughFacts({ holdings, perHoldingFacts, perHoldingPeriods, factIds, holdingsMeta }) {
|
|
524
|
+
const facts = [];
|
|
525
|
+
const unavailable = [];
|
|
526
|
+
if (!holdings?.length) return { facts, unavailable };
|
|
527
|
+
const coverage = topHoldingsCoverage(holdings, { minWeight: LOOK_THROUGH_TARGET_COVERAGE });
|
|
528
|
+
for (const factId of factIds) {
|
|
529
|
+
const rule = LOOK_THROUGH_FACT_RULES[factId];
|
|
530
|
+
if (!rule) {
|
|
531
|
+
unavailable.push(`look-through ${factId}: ${LOOK_THROUGH_BLOCKED[factId] || "no aggregation rule is defined for this fact"}`);
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
// The aggregator reads one number per ticker; the rule name selects how it is combined.
|
|
535
|
+
const byTicker = new Map([...perHoldingFacts]
|
|
536
|
+
.filter(([, facts]) => Number.isFinite(facts?.[factId]))
|
|
537
|
+
.map(([ticker, facts]) => [ticker, facts[factId]]));
|
|
538
|
+
const aggregate = lookThroughAggregate({
|
|
539
|
+
holdings: coverage.holdings,
|
|
540
|
+
perHoldingFacts: byTicker,
|
|
541
|
+
factId: rule,
|
|
542
|
+
coverageWeight: coverage.coverage_weight,
|
|
543
|
+
});
|
|
544
|
+
if (!aggregate || !finite(aggregate.value)) {
|
|
545
|
+
unavailable.push(`look-through ${factId}: ${aggregate?.unavailable?.join("; ") || "no constituent supplied this fact"}`);
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
facts.push({
|
|
549
|
+
fact_id: factId,
|
|
550
|
+
value: aggregate.value,
|
|
551
|
+
value_kind: "ratio",
|
|
552
|
+
unit: LOOK_THROUGH_UNITS[factId] || "decimal",
|
|
553
|
+
ratio_denominator: `weight_${aggregate.method}`,
|
|
554
|
+
source_kind: "issuer_disclosure",
|
|
555
|
+
source_url: holdingsMeta?.source_url,
|
|
556
|
+
public_at: holdingsMeta?.public_at,
|
|
557
|
+
observation_date: holdingsMeta?.as_of,
|
|
558
|
+
...aggregatePeriod(perHoldingPeriods, factId, [...byTicker.keys()]),
|
|
559
|
+
confidence: 0.7,
|
|
560
|
+
derivation: "rederived",
|
|
561
|
+
method: aggregate.method,
|
|
562
|
+
coverage_weight: aggregate.coverage_weight,
|
|
563
|
+
title: `look-through ${factId} over ${aggregate.constituent_count} constituents`,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
return { facts, unavailable };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Compose everything a fund or index instrument can contribute.
|
|
571
|
+
*
|
|
572
|
+
* An index has no issuer to publish holdings, so it borrows its tracking ETF's -- labelled a
|
|
573
|
+
* proxy in the returned provenance, because the constituents themselves are a licensed asset
|
|
574
|
+
* and presenting borrowed weights as the index would be both a licensing and an accuracy
|
|
575
|
+
* claim this project has no basis for.
|
|
576
|
+
*/
|
|
577
|
+
export async function gatherInstrumentFacts({
|
|
578
|
+
symbol,
|
|
579
|
+
instrument,
|
|
580
|
+
perHoldingFacts = null,
|
|
581
|
+
lookThroughFactIds = [],
|
|
582
|
+
signal,
|
|
583
|
+
asOf = null,
|
|
584
|
+
} = {}) {
|
|
585
|
+
const facts = [];
|
|
586
|
+
const unavailable = [];
|
|
587
|
+
const provenance = {};
|
|
588
|
+
const normalized = normalizeIndexSymbol(symbol);
|
|
589
|
+
const proxy = INDEX_PROXIES[normalized];
|
|
590
|
+
const holdingsSymbol = instrument?.index_like ? proxy?.etf : symbol;
|
|
591
|
+
// A tracker has no valuation of its own to publish, so it reads the index it holds. Without
|
|
592
|
+
// this a fund run reported "WSJ publishes only three indices" as a gap while the very index
|
|
593
|
+
// it tracks was one of the three.
|
|
594
|
+
const valuationSymbol = instrument?.index_like ? normalized : (valuationIndexFor(symbol) || normalized);
|
|
595
|
+
if (!instrument?.index_like && valuationSymbol !== normalized) {
|
|
596
|
+
provenance.valuation_source = {
|
|
597
|
+
kind: "tracked_index_valuation",
|
|
598
|
+
index: valuationSymbol,
|
|
599
|
+
why: "a cap-weighted tracker holds the index, so the index aggregate describes the fund's book on the same basis",
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (instrument?.index_like) {
|
|
604
|
+
provenance.constituent_source = proxy
|
|
605
|
+
? { kind: "tracking_etf_proxy", etf: proxy.etf, licensor: proxy.licensor, why: proxy.why }
|
|
606
|
+
: { kind: "unavailable", why: "no tracking-ETF proxy is registered for this index" };
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Fee and size come from the issuer, not from the index: an index has no expense ratio.
|
|
610
|
+
// This was previously read off the index aggregate, which never carries one, so the fee
|
|
611
|
+
// fact was unreachable code that silently produced nothing.
|
|
612
|
+
const [aggregate, holdings, metadata, erp] = await Promise.all([
|
|
613
|
+
fetchIndexAggregate({ symbol: valuationSymbol, signal, asOf })
|
|
614
|
+
.catch((error) => ({ unavailable: [`index aggregate: ${String(error?.message || error)}`] })),
|
|
615
|
+
holdingsSymbol
|
|
616
|
+
? fetchFundHoldings(holdingsSymbol, { signal })
|
|
617
|
+
.catch((error) => ({ unavailable: [`fund holdings: ${String(error?.message || error)}`] }))
|
|
618
|
+
: Promise.resolve(null),
|
|
619
|
+
holdingsSymbol
|
|
620
|
+
? fetchFundMetadata(holdingsSymbol, { signal })
|
|
621
|
+
.catch((error) => ({ unavailable: [`fund metadata: ${String(error?.message || error)}`] }))
|
|
622
|
+
: Promise.resolve(null),
|
|
623
|
+
fetchImpliedErp({ signal, asOf })
|
|
624
|
+
.catch((error) => ({ unavailable: [`implied equity risk premium: ${String(error?.message || error)}`] })),
|
|
625
|
+
]);
|
|
626
|
+
|
|
627
|
+
facts.push(...valuationFacts(aggregate));
|
|
628
|
+
if (erp && finite(erp.latest)) {
|
|
629
|
+
// The premium the market is actually pricing, with where it sits in its own history. A
|
|
630
|
+
// level alone cannot say whether the market is paying up; the percentile is the claim.
|
|
631
|
+
facts.push({
|
|
632
|
+
fact_id: "valuation.implied_erp",
|
|
633
|
+
value: erp.latest,
|
|
634
|
+
value_kind: "ratio",
|
|
635
|
+
unit: "decimal",
|
|
636
|
+
ratio_denominator: "equity_over_riskfree",
|
|
637
|
+
source_kind: "published_dataset",
|
|
638
|
+
source_url: erp.source_url,
|
|
639
|
+
public_at: erp.public_at,
|
|
640
|
+
observation_date: erp.observation_date,
|
|
641
|
+
confidence: 0.85,
|
|
642
|
+
derivation: "reported",
|
|
643
|
+
method: erp.basis,
|
|
644
|
+
title: "Damodaran implied equity risk premium",
|
|
645
|
+
});
|
|
646
|
+
if (erp.percentile) {
|
|
647
|
+
facts.push({
|
|
648
|
+
fact_id: "cycle.valuation_percentile",
|
|
649
|
+
value: erp.percentile.percentile,
|
|
650
|
+
value_kind: "ratio",
|
|
651
|
+
unit: "decimal",
|
|
652
|
+
ratio_denominator: `rank_within_${erp.percentile.sample_size}_published_months`,
|
|
653
|
+
source_kind: "published_dataset",
|
|
654
|
+
source_url: erp.source_url,
|
|
655
|
+
public_at: erp.public_at,
|
|
656
|
+
observation_date: erp.observation_date,
|
|
657
|
+
confidence: 0.8,
|
|
658
|
+
derivation: "rederived",
|
|
659
|
+
method: `percentile_of_${erp.basis}_since_${erp.percentile.sample_start}`,
|
|
660
|
+
title: "implied equity risk premium percentile",
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
unavailable.push(...(erp?.unavailable || []));
|
|
665
|
+
unavailable.push(...(aggregate?.unavailable || []));
|
|
666
|
+
let breadthResult = null;
|
|
667
|
+
if (holdings?.holdings?.length) {
|
|
668
|
+
facts.push(...structureFacts(holdings, metadata));
|
|
669
|
+
// Breadth is computed from the basket rather than bought: every free screener that
|
|
670
|
+
// publishes it forbids the query, and the holdings are already in hand.
|
|
671
|
+
breadthResult = await fetchBasketBreadth(holdings.holdings, { signal })
|
|
672
|
+
.catch((error) => ({ available: false, unavailable: [`breadth: ${String(error?.message || error)}`] }));
|
|
673
|
+
const breadth = breadthResult;
|
|
674
|
+
unavailable.push(...(breadth.unavailable || []));
|
|
675
|
+
if (breadth.available) {
|
|
676
|
+
const shared = {
|
|
677
|
+
source_kind: "market_snapshot",
|
|
678
|
+
source_url: holdings.source_url,
|
|
679
|
+
public_at: holdings.public_at,
|
|
680
|
+
observation_date: holdings.as_of,
|
|
681
|
+
value_kind: "ratio",
|
|
682
|
+
unit: "decimal",
|
|
683
|
+
derivation: "rederived",
|
|
684
|
+
confidence: 0.8,
|
|
685
|
+
coverage_weight: breadth.coverage_weight,
|
|
686
|
+
method: breadth.method,
|
|
687
|
+
};
|
|
688
|
+
facts.push({
|
|
689
|
+
...shared,
|
|
690
|
+
fact_id: "index.breadth_above_200dma",
|
|
691
|
+
value: breadth.weighted_above,
|
|
692
|
+
ratio_denominator: "measured_basket_weight",
|
|
693
|
+
title: "share of basket weight above its 200-day average",
|
|
694
|
+
});
|
|
695
|
+
if (finite(breadth.net_assets) && !finite(metadata?.aum)) {
|
|
696
|
+
facts.push({
|
|
697
|
+
...shared,
|
|
698
|
+
fact_id: "fund.aum",
|
|
699
|
+
value: breadth.net_assets,
|
|
700
|
+
value_kind: "monetary",
|
|
701
|
+
unit: "currency_units",
|
|
702
|
+
currency: "USD",
|
|
703
|
+
scale: 1,
|
|
704
|
+
ratio_denominator: undefined,
|
|
705
|
+
method: "sum_of_disclosed_units_at_last_close",
|
|
706
|
+
title: `${holdings.symbol} assets from disclosed positions`,
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
// The fund's own shares outstanding. This is the fund as an issuer, not an aggregate of
|
|
710
|
+
// anything -- and it is what lets a seat build the fund's market capitalisation with the
|
|
711
|
+
// same arithmetic it uses on a company, which is why ten seats could not read a basket.
|
|
712
|
+
const fundShares = finite(holdings.shares_outstanding)
|
|
713
|
+
? { value: holdings.shares_outstanding, how: "issuer_disclosed_shares_outstanding" }
|
|
714
|
+
: await impliedFundShares(holdings.symbol, breadth.net_assets, metadata, signal);
|
|
715
|
+
if (fundShares) {
|
|
716
|
+
facts.push({
|
|
717
|
+
...shared,
|
|
718
|
+
fact_id: "capital_allocation.share_count",
|
|
719
|
+
value: Number(fundShares.value.toFixed(2)),
|
|
720
|
+
value_kind: "count",
|
|
721
|
+
unit: "shares",
|
|
722
|
+
ratio_denominator: undefined,
|
|
723
|
+
derivation: fundShares.how === "issuer_disclosed_shares_outstanding" ? "reported" : "rederived",
|
|
724
|
+
method: fundShares.how,
|
|
725
|
+
title: `${holdings.symbol} shares outstanding`,
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
// What this run saw about the fund's size, appended to its own ledger. No issuer serves
|
|
729
|
+
// a share-count history keylessly, so the history is built one run at a time and a flow
|
|
730
|
+
// becomes computable from the second observation onward.
|
|
731
|
+
const observed = recordFundObservation({
|
|
732
|
+
symbol: holdings.symbol,
|
|
733
|
+
asOf: holdings.as_of,
|
|
734
|
+
sharesOutstanding: fundShares?.value ?? null,
|
|
735
|
+
sharesBasis: fundShares?.how ?? null,
|
|
736
|
+
nav: finite(metadata?.nav) ? metadata.nav : null,
|
|
737
|
+
netAssets: finite(metadata?.aum) ? metadata.aum : breadth.net_assets,
|
|
738
|
+
sourceUrl: holdings.source_url,
|
|
739
|
+
});
|
|
740
|
+
const inputs = flowInputs(observed.observations);
|
|
741
|
+
const flow = inputs
|
|
742
|
+
? fundFlow({ sharesNow: inputs.sharesNow, sharesPrior: inputs.sharesPrior, nav: inputs.nav, issuer: holdings.issuer })
|
|
743
|
+
: null;
|
|
744
|
+
if (flow && finite(flow.value)) {
|
|
745
|
+
facts.push({
|
|
746
|
+
...shared,
|
|
747
|
+
fact_id: "fund.net_flow",
|
|
748
|
+
value: flow.value,
|
|
749
|
+
value_kind: "monetary",
|
|
750
|
+
unit: "currency_units",
|
|
751
|
+
currency: "USD",
|
|
752
|
+
scale: 1,
|
|
753
|
+
ratio_denominator: undefined,
|
|
754
|
+
derivation: "rederived",
|
|
755
|
+
observation_date: inputs.asOf,
|
|
756
|
+
method: `${flow.construction.formula}; ${inputs.priorAsOf} to ${inputs.asOf}, ${inputs.gapDays} days apart`,
|
|
757
|
+
title: `${holdings.symbol} net creation over ${inputs.gapDays} days`,
|
|
758
|
+
});
|
|
759
|
+
if (finite(inputs.netAssets) && inputs.netAssets > 0) {
|
|
760
|
+
facts.push({
|
|
761
|
+
...shared,
|
|
762
|
+
fact_id: "fund.net_flow_ratio",
|
|
763
|
+
value: Number((flow.value / inputs.netAssets).toFixed(6)),
|
|
764
|
+
ratio_denominator: "net_assets",
|
|
765
|
+
derivation: "rederived",
|
|
766
|
+
observation_date: inputs.asOf,
|
|
767
|
+
method: `net creation over assets; ${inputs.priorAsOf} to ${inputs.asOf}, ${inputs.gapDays} days apart`,
|
|
768
|
+
title: `${holdings.symbol} net creation as a share of assets`,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
} else {
|
|
772
|
+
const filed = observed.observations.filter((row) => row.shares_basis === "issuer_disclosed_shares_outstanding").length;
|
|
773
|
+
unavailable.push(filed >= 1
|
|
774
|
+
? `fund flow for ${holdings.symbol}: ${filed} issuer-published share-count observation(s) on record;`
|
|
775
|
+
+ " a flow needs two, and no issuer serves a keyless history. The ledger grows one run at a time."
|
|
776
|
+
: `fund flow for ${holdings.symbol}: ${holdings.issuer} publishes no shares outstanding, and the count`
|
|
777
|
+
+ " reconstructed from assets over price may size the fund but must not be differenced into a flow --"
|
|
778
|
+
+ " the subtraction cancels the number and keeps the error.");
|
|
779
|
+
}
|
|
780
|
+
// The gap between weighted and counted breadth IS the concentration story: a
|
|
781
|
+
// cap-weighted basket can be above its average on weight while most members are below.
|
|
782
|
+
facts.push({
|
|
783
|
+
...shared,
|
|
784
|
+
fact_id: "index.breadth_counted_above_200dma",
|
|
785
|
+
value: breadth.counted_above,
|
|
786
|
+
ratio_denominator: "measured_constituent_count",
|
|
787
|
+
title: "share of measured constituents above their 200-day average",
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
provenance.holdings_as_of = holdings.as_of;
|
|
791
|
+
provenance.holdings_count = holdings.holdings.length;
|
|
792
|
+
}
|
|
793
|
+
unavailable.push(...(holdings?.unavailable || []));
|
|
794
|
+
unavailable.push(...(metadata?.unavailable || []));
|
|
795
|
+
|
|
796
|
+
if (holdings?.holdings?.length && lookThroughFactIds.length) {
|
|
797
|
+
let resolved = perHoldingFacts;
|
|
798
|
+
let resolvedPeriods = null;
|
|
799
|
+
let resolvedShareCounts = null;
|
|
800
|
+
if (!resolved) {
|
|
801
|
+
const constituents = await resolveConstituentFacts(holdings.holdings, { signal, asOf });
|
|
802
|
+
resolved = constituents.perHolding;
|
|
803
|
+
resolvedPeriods = constituents.perHoldingPeriods;
|
|
804
|
+
resolvedShareCounts = constituents.shareCounts;
|
|
805
|
+
unavailable.push(...constituents.unavailable);
|
|
806
|
+
provenance.look_through = {
|
|
807
|
+
constituents_attempted: constituents.attempted,
|
|
808
|
+
constituents_resolved: resolved.size,
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
const through = lookThroughFacts({
|
|
812
|
+
holdings: holdings.holdings,
|
|
813
|
+
perHoldingFacts: resolved,
|
|
814
|
+
perHoldingPeriods: resolvedPeriods,
|
|
815
|
+
factIds: lookThroughFactIds,
|
|
816
|
+
holdingsMeta: holdings,
|
|
817
|
+
});
|
|
818
|
+
facts.push(...through.facts);
|
|
819
|
+
unavailable.push(...through.unavailable);
|
|
820
|
+
|
|
821
|
+
// The fund's own size is what scales a per-dollar claim back into dollars. Prefer the
|
|
822
|
+
// issuer's published figure and fall back to the assets implied by the disclosed
|
|
823
|
+
// positions, which is the same number the size fact above is built from.
|
|
824
|
+
const netAssets = finite(metadata?.aum) ? metadata.aum : breadthResult?.net_assets;
|
|
825
|
+
const claims = lookThroughClaims({
|
|
826
|
+
holdings: holdings.holdings,
|
|
827
|
+
perHoldingFacts: resolved,
|
|
828
|
+
perHoldingPeriods: resolvedPeriods,
|
|
829
|
+
shareCounts: resolvedShareCounts,
|
|
830
|
+
closes: breadthResult?.closes,
|
|
831
|
+
netAssets,
|
|
832
|
+
holdingsMeta: holdings,
|
|
833
|
+
});
|
|
834
|
+
facts.push(...claims.facts);
|
|
835
|
+
unavailable.push(...claims.unavailable);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return {
|
|
839
|
+
symbol,
|
|
840
|
+
research_model: instrument?.research_model || "market_instrument",
|
|
841
|
+
provenance,
|
|
842
|
+
facts,
|
|
843
|
+
// Several facts have more than one route -- the implied ERP and the valuation percentile
|
|
844
|
+
// are computed from the published workbook when the index feed cannot supply them -- and
|
|
845
|
+
// each route reports its own failure. Keeping a note about a fact the pack actually
|
|
846
|
+
// produced reads as a gap that is filled, which is worse than saying nothing: it invites
|
|
847
|
+
// a reader to go and solve a problem that is already solved.
|
|
848
|
+
unavailable: unavailable.filter((note) => !factsProducedFor(note, facts)),
|
|
849
|
+
limits: {
|
|
850
|
+
max_constituents: LOOK_THROUGH_MAX_CONSTITUENTS,
|
|
851
|
+
target_coverage: LOOK_THROUGH_TARGET_COVERAGE,
|
|
852
|
+
fetch_timeout_ms: LIMITS.QUOTE_FETCH_MS,
|
|
853
|
+
},
|
|
854
|
+
};
|
|
855
|
+
}
|