alphacouncil-agent 0.9.2 → 1.0.0
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/commands/alpha.md +22 -3
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +4 -4
- package/.grok/commands/alpha.md +22 -3
- package/.opencode/command/alpha.md +22 -3
- package/AGENTS.md +42 -4
- package/CHANGELOG.md +164 -0
- package/CLAUDE.md +59 -7
- package/README.ja.md +25 -11
- package/README.md +44 -12
- package/README.zh-CN.md +37 -12
- package/commands/alpha.md +22 -3
- 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 +296 -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 +43 -6
- package/docs/personas.md +2 -1
- package/docs/plans/v1.0-roadmap.md +487 -0
- package/docs/releases/v0.9.3.md +86 -0
- package/docs/releases/v0.9.4.md +79 -0
- package/docs/releases/v0.9.5.md +96 -0
- package/docs/releases/v1.0.0.md +164 -0
- package/docs/report-contract.md +75 -2
- 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/{debt_driver_stress.ai-review.json → policy_real_rate.ai-review.json} +49 -49
- package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_dalio/reviews/{regime_classifier.ai-review.json → real_curve_slope.ai-review.json} +49 -49
- 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/{opportunity_cost.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/{user_value_evidence.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_forensic_short/reviews/{borrow_catalyst.ai-review.json → cash_conversion_gap.ai-review.json} +49 -49
- 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/{incentive_map.ai-review.json → reported_earnings_cash_gap.ai-review.json} +49 -49
- 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/{oos_cost_test.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/{leakage_audit.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/{reversal_monitor.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/{reflexivity_graph.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/{edge_recalculator.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/{fractional_kelly.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 +27 -10
- 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 +26 -8
- 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 +25 -8
- 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 +24 -8
- 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 +26 -10
- 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 +26 -10
- 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 +24 -8
- 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 +23 -8
- 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 +25 -8
- 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 +31 -11
- 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 +27 -10
- 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 +20 -8
- 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 +26 -8
- 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 +25 -10
- 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 +22 -6
- 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 +27 -10
- 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 +28 -10
- 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 +25 -10
- 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 +24 -10
- 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 +23 -10
- 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 +22 -6
- 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 +22 -8
- 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 +22 -10
- 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 +24 -11
- 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 +23 -9
- 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 +25 -10
- 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 +122 -0
- package/mcp/lib/codex.mjs +3 -3
- package/mcp/lib/constants.mjs +76 -25
- package/mcp/lib/council-options.mjs +95 -40
- package/mcp/lib/council-selection.mjs +95 -9
- 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/fundamentals.mjs +801 -0
- package/mcp/lib/funds.mjs +750 -0
- package/mcp/lib/gates.mjs +149 -15
- package/mcp/lib/grounding.mjs +188 -36
- package/mcp/lib/index-aggregate.mjs +718 -0
- package/mcp/lib/insider-ownership.mjs +149 -0
- package/mcp/lib/instrument-facts.mjs +557 -0
- package/mcp/lib/instruments.mjs +191 -0
- package/mcp/lib/lang.mjs +109 -2
- package/mcp/lib/markdown.mjs +620 -138
- package/mcp/lib/master-catalog.mjs +32 -3
- package/mcp/lib/orchestrator.mjs +1121 -290
- package/mcp/lib/packets.mjs +227 -21
- package/mcp/lib/personas/engine.mjs +44 -31
- package/mcp/lib/personas-v3/compiler.mjs +2 -0
- 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/loader.mjs +10 -7
- 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-acquisition.mjs +10 -3
- 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 +119 -2
- package/mcp/lib/quotes.mjs +5 -0
- package/mcp/lib/rpc.mjs +101 -42
- package/mcp/lib/run-store.mjs +28 -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 +339 -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/persona-v3.schema.json +37 -2
- 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 +63 -8
- 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 +182 -16
- package/scripts/lib/persona-v3-solo-test-packs.mjs +143 -16
- package/scripts/package-smoke.mjs +32 -12
- package/scripts/report-persona-corpus-gaps.mjs +4 -1
- package/scripts/review-persona-v3-solo-formulas.mjs +12 -8
- package/scripts/run-tests.mjs +23 -2
- package/scripts/scaffold-persona-v3-staging.mjs +1 -1
- package/skills/agent-skills-governance/SKILL.md +8 -2
- package/skills/alphacouncil-agent/SKILL.md +60 -9
- package/skills/public-equity-investing/SKILL.md +17 -0
- 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,718 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aggregate index facts: valuation, breadth, positioning and volatility history.
|
|
3
|
+
*
|
|
4
|
+
* An index or index ETF cannot be valued the way an operating company is. There is no filer,
|
|
5
|
+
* no Company Facts screen and no single earnings statement, so the seats that need a valuation
|
|
6
|
+
* level, a cycle reading or a factor input have nothing to stand on unless the aggregate is
|
|
7
|
+
* fetched as its own dated fact. That is what this module supplies.
|
|
8
|
+
*
|
|
9
|
+
* TWO RULES ARE LOAD-BEARING HERE AND ARE ENCODED, NOT JUST DOCUMENTED.
|
|
10
|
+
*
|
|
11
|
+
* 1. BASIS. Index P/E is quoted on mutually incompatible bases. On 2026-07-24 the same S&P 500
|
|
12
|
+
* was 25.17x on WSJ's index basis, 28.53x on multpl.com's GAAP as-reported trailing basis,
|
|
13
|
+
* and 29.80x on iShares' IVV portfolio harmonic mean. None of those is wrong; they answer
|
|
14
|
+
* different questions. A fact therefore carries exactly one `basis` and names it, sources are
|
|
15
|
+
* never blended for one metric, and a percentile is refused unless its history declares the
|
|
16
|
+
* same basis. See `PE_BASIS` and `PE_BASIS_DIVERGENCE`.
|
|
17
|
+
*
|
|
18
|
+
* 2. LICENSING. S&P 500 and DJIA constituents and weights are licensed assets of S&P Dow Jones
|
|
19
|
+
* Indices. Scraping a constituent list and presenting it as the index is not a supported free
|
|
20
|
+
* path. The supported free path is the tracking ETF's published holdings, used as an explicit
|
|
21
|
+
* proxy and LABELLED as one (`^GSPC` -> IVV, `^NDX` -> QQQ, `^DJI` -> DIA). Holdings fetching
|
|
22
|
+
* belongs to another module; this one exposes the proxy mapping and accepts holdings passed
|
|
23
|
+
* in, and always stamps `is_proxy: true` on the result. See `INDEX_PROXIES`.
|
|
24
|
+
*
|
|
25
|
+
* Two of the upstreams (WSJ peyields, WSJ markets diary) are undocumented internal APIs. They
|
|
26
|
+
* can change shape without notice and without a version, so every field is validated for
|
|
27
|
+
* presence, type AND plausible magnitude before it becomes a number. A shape change must
|
|
28
|
+
* degrade to a named gap in `unavailable`; it must never become a silently wrong number.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { LIMITS } from "./constants.mjs";
|
|
32
|
+
import { fetchText, resolveMarketSymbol } from "./quotes.mjs";
|
|
33
|
+
|
|
34
|
+
// ---- Sources ---------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
export const SOURCE_URLS = Object.freeze({
|
|
37
|
+
// Undocumented WSJ internal APIs. Both need a non-default User-Agent, which `fetchText`
|
|
38
|
+
// already sends. Note this is the opposite of the api.nasdaq.com rule below, so there is
|
|
39
|
+
// deliberately no single global UA policy in this module.
|
|
40
|
+
wsj_index_valuation:
|
|
41
|
+
"https://www.wsj.com/market-data/stocks/peyields?id=%7B%22indexType%22%3A%22OTHERS%22%7D&type=mdc_peAndYields",
|
|
42
|
+
wsj_market_breadth:
|
|
43
|
+
"https://www.wsj.com/market-data/stocks/marketsdiary?id=%7B%22application%22%3A%22WSJ%22%2C%22marketsDiaryType%22%3A%22diaries%22%7D&type=mdc_marketsdiary",
|
|
44
|
+
// Daily file keyed by trade date; 403 on any non-trading day AND on the current session
|
|
45
|
+
// before it is published, hence the walk-back in `fetchPutCallRatios`.
|
|
46
|
+
cboe_put_call_daily: "https://cdn.cboe.com/data/us/options/market_statistics/daily/",
|
|
47
|
+
cboe_vix_history: "https://cdn.cboe.com/api/global/us_indices/daily_prices/VIX_History.csv",
|
|
48
|
+
// Nasdaq publishes its own index membership, so this one is not S&P-licensed. It is exposed
|
|
49
|
+
// rather than fetched because constituent/holdings fetching is another module's job. TRAP if
|
|
50
|
+
// it is ever wired: a default curl/node User-Agent gets HTTP/2 INTERNAL_ERROR from this host.
|
|
51
|
+
nasdaq_100_constituents: "https://api.nasdaq.com/api/quote/list-type/nasdaq100",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Damodaran's implied ERP is the natural input for an equity-risk-premium seat, and both files
|
|
56
|
+
* are reachable. Neither is parsed: `.xlsx` is a zip of XML whose sheet layout is undocumented
|
|
57
|
+
* and moves between vintages, and `.xls` is the pre-2007 BIFF binary format. Hand-rolling a
|
|
58
|
+
* reader for either on Node built-ins would produce a number nobody can check, which is strictly
|
|
59
|
+
* worse than a named gap. The URLs are published here so a human can open them.
|
|
60
|
+
*/
|
|
61
|
+
export const DAMODARAN_SOURCES = Object.freeze([
|
|
62
|
+
Object.freeze({
|
|
63
|
+
id: "implied_erp_monthly",
|
|
64
|
+
label: "Damodaran implied equity risk premium, monthly",
|
|
65
|
+
url: "https://pages.stern.nyu.edu/~adamodar/pc/implprem/ERPbymonth.xlsx",
|
|
66
|
+
format: "xlsx",
|
|
67
|
+
parser_implemented: false,
|
|
68
|
+
// The /pc/datasets/ sibling path 404s for this file; /pc/implprem/ is the live one.
|
|
69
|
+
reason: "xlsx is a zip of undocumented sheet XML; no dependency-free parser is trustworthy here",
|
|
70
|
+
}),
|
|
71
|
+
Object.freeze({
|
|
72
|
+
id: "implied_erp_annual",
|
|
73
|
+
label: "Damodaran implied equity risk premium, annual history",
|
|
74
|
+
url: "https://pages.stern.nyu.edu/~adamodar/pc/datasets/histimpl.xls",
|
|
75
|
+
format: "xls_biff",
|
|
76
|
+
parser_implemented: false,
|
|
77
|
+
reason: "legacy BIFF binary workbook; not readable with Node built-ins",
|
|
78
|
+
}),
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
// ---- Basis ------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
export const PE_BASIS = Object.freeze({
|
|
84
|
+
/** WSJ's own index-level aggregation, as published on the peyields endpoint. */
|
|
85
|
+
WSJ_INDEX: "wsj_index_basis",
|
|
86
|
+
/** GAAP as-reported trailing twelve months (multpl.com and similar). */
|
|
87
|
+
GAAP_AS_REPORTED_TTM: "gaap_as_reported_ttm",
|
|
88
|
+
/** Fund-sponsor portfolio harmonic mean (iShares/Invesco fact sheets). */
|
|
89
|
+
ETF_PORTFOLIO_HARMONIC: "etf_portfolio_harmonic_mean",
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* One observed day where all three bases disagree by more than 18%, kept as data so the
|
|
94
|
+
* no-blending rule is checkable rather than folklore. A dashboard that mixes these looks broken.
|
|
95
|
+
*/
|
|
96
|
+
export const PE_BASIS_DIVERGENCE = Object.freeze({
|
|
97
|
+
index: "^GSPC",
|
|
98
|
+
trade_date: "2026-07-24",
|
|
99
|
+
readings: Object.freeze([
|
|
100
|
+
Object.freeze({ basis: PE_BASIS.WSJ_INDEX, pe_trailing: 25.17, source: "WSJ peyields" }),
|
|
101
|
+
Object.freeze({ basis: PE_BASIS.GAAP_AS_REPORTED_TTM, pe_trailing: 28.53, source: "multpl.com" }),
|
|
102
|
+
Object.freeze({ basis: PE_BASIS.ETF_PORTFOLIO_HARMONIC, pe_trailing: 29.8, source: "iShares IVV" }),
|
|
103
|
+
]),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ---- Index proxies (licensing) ----------------------------------------------
|
|
107
|
+
|
|
108
|
+
export const PROXY_LICENSE_NOTE =
|
|
109
|
+
"Index membership and weights are licensed by the index provider. This is the tracking ETF's "
|
|
110
|
+
+ "published holdings used as an explicit proxy for the index, not the index itself.";
|
|
111
|
+
|
|
112
|
+
export const INDEX_PROXIES = Object.freeze({
|
|
113
|
+
"^GSPC": Object.freeze({
|
|
114
|
+
index_name: "S&P 500",
|
|
115
|
+
etf: "IVV",
|
|
116
|
+
alternates: Object.freeze(["SPY", "VOO"]),
|
|
117
|
+
licensor: "S&P Dow Jones Indices",
|
|
118
|
+
why: "S&P 500 constituents and weights are a licensed S&P DJI asset; the free path is a tracking ETF's published holdings, labelled as a proxy",
|
|
119
|
+
}),
|
|
120
|
+
"^NDX": Object.freeze({
|
|
121
|
+
index_name: "Nasdaq-100",
|
|
122
|
+
etf: "QQQ",
|
|
123
|
+
alternates: Object.freeze(["QQQM"]),
|
|
124
|
+
licensor: "Nasdaq, Inc.",
|
|
125
|
+
why: "Nasdaq publishes NDX membership itself, but weights still come from the tracking ETF, so the same proxy labelling applies",
|
|
126
|
+
}),
|
|
127
|
+
"^DJI": Object.freeze({
|
|
128
|
+
index_name: "Dow Jones Industrial Average",
|
|
129
|
+
etf: "DIA",
|
|
130
|
+
alternates: Object.freeze([]),
|
|
131
|
+
licensor: "S&P Dow Jones Indices",
|
|
132
|
+
why: "DJIA constituents and its divisor are licensed S&P DJI assets; the free path is DIA's published holdings, labelled as a proxy",
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const INDEX_ALIASES = Object.freeze({
|
|
137
|
+
GSPC: "^GSPC", "^GSPC": "^GSPC", SPX: "^GSPC", "^SPX": "^GSPC", SP500: "^GSPC",
|
|
138
|
+
NDX: "^NDX", "^NDX": "^NDX", NASDAQ100: "^NDX",
|
|
139
|
+
DJI: "^DJI", "^DJI": "^DJI", DJIA: "^DJI",
|
|
140
|
+
RUT: "^RUT", "^RUT": "^RUT",
|
|
141
|
+
IXIC: "^IXIC", "^IXIC": "^IXIC",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
/** Normalise an index identifier. Unknown input passes through unchanged rather than being guessed at. */
|
|
145
|
+
export function normalizeIndexSymbol(input) {
|
|
146
|
+
const resolved = String(resolveMarketSymbol(input) || "").trim().toUpperCase();
|
|
147
|
+
return Object.prototype.hasOwnProperty.call(INDEX_ALIASES, resolved) ? INDEX_ALIASES[resolved] : resolved;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The proxy record for an index, optionally carrying holdings fetched elsewhere.
|
|
152
|
+
* `is_proxy` is unconditional: the caller must never be able to read this as the index itself.
|
|
153
|
+
*/
|
|
154
|
+
export function proxyConstituents(symbol, holdings = null) {
|
|
155
|
+
const normalized = normalizeIndexSymbol(symbol);
|
|
156
|
+
const proxy = INDEX_PROXIES[normalized];
|
|
157
|
+
if (!proxy) return null;
|
|
158
|
+
const rows = Array.isArray(holdings) ? Object.freeze([...holdings]) : null;
|
|
159
|
+
return Object.freeze({
|
|
160
|
+
is_proxy: true,
|
|
161
|
+
proxy_for: normalized,
|
|
162
|
+
index_name: proxy.index_name,
|
|
163
|
+
proxy_etf: proxy.etf,
|
|
164
|
+
proxy_alternates: proxy.alternates,
|
|
165
|
+
licensor: proxy.licensor,
|
|
166
|
+
proxy_reason: proxy.why,
|
|
167
|
+
license_note: PROXY_LICENSE_NOTE,
|
|
168
|
+
holdings: rows,
|
|
169
|
+
holdings_source: rows ? "caller_supplied_etf_holdings" : null,
|
|
170
|
+
holdings_count: rows ? rows.length : null,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ---- Strict validation helpers ----------------------------------------------
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Magnitude bands. Presence and type checks catch a renamed field; only a band catches the
|
|
178
|
+
* nastier case where a field keeps its name but changes meaning (a ratio becoming a percentage,
|
|
179
|
+
* a level becoming a decimal fraction).
|
|
180
|
+
*/
|
|
181
|
+
const BANDS = Object.freeze({
|
|
182
|
+
PE: Object.freeze({ min: 1, max: 1000 }),
|
|
183
|
+
YIELD_PCT: Object.freeze({ min: 0, max: 25 }),
|
|
184
|
+
PUT_CALL: Object.freeze({ min: 0, max: 20 }),
|
|
185
|
+
VIX: Object.freeze({ min: 1, max: 250 }),
|
|
186
|
+
COUNT: Object.freeze({ min: 0, max: 1e6 }),
|
|
187
|
+
TRIN: Object.freeze({ min: 0, max: 50 }),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const MONTHS = Object.freeze({
|
|
191
|
+
january: 1, february: 2, march: 3, april: 4, may: 5, june: 6,
|
|
192
|
+
july: 7, august: 8, september: 9, october: 10, november: 11, december: 12,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
function requireObject(value, what) {
|
|
196
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${what}: expected an object`);
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function requireArray(value, what) {
|
|
201
|
+
if (!Array.isArray(value) || !value.length) throw new Error(`${what}: expected a non-empty array`);
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function requireString(value, what) {
|
|
206
|
+
const text = typeof value === "string" ? value.trim() : "";
|
|
207
|
+
if (!text) throw new Error(`${what}: expected a non-empty string`);
|
|
208
|
+
return text;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* A published number, or null when the field is absent. Absent secondary fields degrade to null;
|
|
213
|
+
* a field that is present but implausible throws, because that is the shape change that would
|
|
214
|
+
* otherwise become a confident wrong number.
|
|
215
|
+
*/
|
|
216
|
+
function boundedNumber(raw, what, { min, max, allowNull = false }) {
|
|
217
|
+
if (raw === null || raw === undefined || raw === "") {
|
|
218
|
+
if (allowNull) return null;
|
|
219
|
+
throw new Error(`${what}: missing`);
|
|
220
|
+
}
|
|
221
|
+
const value = Number(String(raw).replace(/,/gu, ""));
|
|
222
|
+
if (!Number.isFinite(value)) throw new Error(`${what}: not numeric (${JSON.stringify(raw)})`);
|
|
223
|
+
if (value < min || value > max) throw new Error(`${what}: ${value} is outside the plausible band ${min}..${max}`);
|
|
224
|
+
return value;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** `2026-07-24T00:00:00` -> `2026-07-24`. */
|
|
228
|
+
function isoDateFromWsj(raw, what) {
|
|
229
|
+
const match = /^(\d{4}-\d{2}-\d{2})(?:T|$)/u.exec(String(raw || "").trim());
|
|
230
|
+
if (!match) throw new Error(`${what}: unrecognised date ${JSON.stringify(raw)}`);
|
|
231
|
+
return match[1];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** `Monday, July 27, 2026` -> `2026-07-27`, parsed explicitly rather than via `new Date`. */
|
|
235
|
+
function isoDateFromLongForm(raw, what) {
|
|
236
|
+
const match = /^[A-Za-z]+,\s*([A-Za-z]+)\s+(\d{1,2}),\s*(\d{4})$/u.exec(String(raw || "").trim());
|
|
237
|
+
const month = match ? MONTHS[match[1].toLowerCase()] : null;
|
|
238
|
+
if (!match || !month) throw new Error(`${what}: unrecognised date ${JSON.stringify(raw)}`);
|
|
239
|
+
const day = Number(match[2]);
|
|
240
|
+
if (day < 1 || day > 31) throw new Error(`${what}: impossible day in ${JSON.stringify(raw)}`);
|
|
241
|
+
return `${match[3]}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** The trade date IS the publication anchor for these feeds; the run clock is never used. */
|
|
245
|
+
const publicAt = (isoDay) => `${isoDay}T00:00:00.000Z`;
|
|
246
|
+
|
|
247
|
+
const isoDay = (value) => {
|
|
248
|
+
const text = String(value || "").slice(0, 10);
|
|
249
|
+
return /^\d{4}-\d{2}-\d{2}$/u.test(text) ? text : new Date().toISOString().slice(0, 10);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// ---- Index valuation (WSJ peyields) -----------------------------------------
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* WSJ's internal instrument tickers, which are not exchange symbols: the NASDAQ 100 row is
|
|
256
|
+
* published under `RIXF` and the S&P 500 under `INX`. This map exists precisely so nobody
|
|
257
|
+
* matches on the display name, which WSJ is free to reword.
|
|
258
|
+
*/
|
|
259
|
+
export const WSJ_INDEX_TICKERS = Object.freeze({ INX: "^GSPC", RIXF: "^NDX", RUT: "^RUT" });
|
|
260
|
+
|
|
261
|
+
export function parseWsjPeYields(payload, { sourceUrl = SOURCE_URLS.wsj_index_valuation } = {}) {
|
|
262
|
+
const root = requireObject(payload, "WSJ peyields");
|
|
263
|
+
if (root.type !== "mdc_peAndYields") {
|
|
264
|
+
throw new Error(`WSJ peyields: unexpected payload type ${JSON.stringify(root.type)}`);
|
|
265
|
+
}
|
|
266
|
+
const data = requireObject(root.data, "WSJ peyields data");
|
|
267
|
+
const tradeDate = isoDateFromWsj(data.tradeDate, "WSJ peyields tradeDate");
|
|
268
|
+
const rows = requireArray(data.instruments, "WSJ peyields instruments");
|
|
269
|
+
const instruments = rows.map((row, index) => {
|
|
270
|
+
const where = `WSJ peyields instrument #${index}`;
|
|
271
|
+
const item = requireObject(row, where);
|
|
272
|
+
const ticker = requireString(item.ticker, `${where} ticker`);
|
|
273
|
+
return Object.freeze({
|
|
274
|
+
symbol: WSJ_INDEX_TICKERS[ticker] || null,
|
|
275
|
+
name: requireString(item.name, `${where} name`),
|
|
276
|
+
ticker,
|
|
277
|
+
pe_trailing: boundedNumber(item.priceEarningsRatio, `${where} priceEarningsRatio`, BANDS.PE),
|
|
278
|
+
pe_forward: boundedNumber(item.priceEarningsRatioEstimate, `${where} priceEarningsRatioEstimate`, { ...BANDS.PE, allowNull: true }),
|
|
279
|
+
pe_52w_ago: boundedNumber(item.priceEarningsRatio52WeekAgo, `${where} priceEarningsRatio52WeekAgo`, { ...BANDS.PE, allowNull: true }),
|
|
280
|
+
dividend_yield: boundedNumber(item.yield, `${where} yield`, { ...BANDS.YIELD_PCT, allowNull: true }),
|
|
281
|
+
// Units are spelled out because they differ inside one fact: the P/E fields are plain
|
|
282
|
+
// ratios, the dividend yield is a percent as published, and `earningsYield` below returns
|
|
283
|
+
// a decimal fraction. Reading any of the three as another is a 100x error.
|
|
284
|
+
pe_unit: "ratio",
|
|
285
|
+
dividend_yield_unit: "percent",
|
|
286
|
+
basis: PE_BASIS.WSJ_INDEX,
|
|
287
|
+
basis_note: "WSJ index-level aggregation; not comparable with GAAP as-reported TTM or an ETF portfolio harmonic mean",
|
|
288
|
+
trade_date: tradeDate,
|
|
289
|
+
public_at: publicAt(tradeDate),
|
|
290
|
+
source_url: sourceUrl,
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
return Object.freeze({
|
|
294
|
+
trade_date: tradeDate,
|
|
295
|
+
public_at: publicAt(tradeDate),
|
|
296
|
+
basis: PE_BASIS.WSJ_INDEX,
|
|
297
|
+
instruments: Object.freeze(instruments),
|
|
298
|
+
source_url: sourceUrl,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export async function fetchIndexValuation({ signal } = {}) {
|
|
303
|
+
const url = SOURCE_URLS.wsj_index_valuation;
|
|
304
|
+
const text = await fetchText(url, LIMITS.QUOTE_FETCH_MS, signal);
|
|
305
|
+
return parseWsjPeYields(JSON.parse(text), { sourceUrl: url });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** 1/pe as a decimal fraction. Null for an absent, zero or negative P/E, never a signed inverse. */
|
|
309
|
+
export function earningsYield(pe) {
|
|
310
|
+
const value = Number(pe);
|
|
311
|
+
if (!Number.isFinite(value) || value <= 0) return null;
|
|
312
|
+
return Number((1 / value).toFixed(6));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ---- Market breadth (WSJ markets diary) --------------------------------------
|
|
316
|
+
|
|
317
|
+
const BREADTH_FIELDS = Object.freeze({
|
|
318
|
+
issuestraded: Object.freeze({ key: "issues_traded", band: BANDS.COUNT, required: false }),
|
|
319
|
+
advances: Object.freeze({ key: "advances", band: BANDS.COUNT, required: true }),
|
|
320
|
+
declines: Object.freeze({ key: "declines", band: BANDS.COUNT, required: true }),
|
|
321
|
+
unchanged: Object.freeze({ key: "unchanged", band: BANDS.COUNT, required: false }),
|
|
322
|
+
newhighs: Object.freeze({ key: "new_highs", band: BANDS.COUNT, required: false }),
|
|
323
|
+
newlows: Object.freeze({ key: "new_lows", band: BANDS.COUNT, required: false }),
|
|
324
|
+
closingarmstrin: Object.freeze({ key: "trin", band: BANDS.TRIN, required: false }),
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Only the `latestClose` column is carried. WSJ also publishes `previousClose` and `weekAgo`,
|
|
329
|
+
* but it does not publish the dates those columns belong to, and inferring them across holidays
|
|
330
|
+
* would produce an undated number wearing a date. Those columns are deliberately dropped.
|
|
331
|
+
*/
|
|
332
|
+
function parseBreadthSet(set, index, tradeDate) {
|
|
333
|
+
const where = `WSJ markets diary set #${index}`;
|
|
334
|
+
const node = requireObject(set, where);
|
|
335
|
+
const header = requireArray(node.headerFields, `${where} headerFields`);
|
|
336
|
+
const first = requireObject(header[0], `${where} headerFields[0]`);
|
|
337
|
+
if (first.value !== "name") throw new Error(`${where}: first header column is ${JSON.stringify(first.value)}, expected "name"`);
|
|
338
|
+
const venue = requireString(first.label, `${where} venue label`);
|
|
339
|
+
const rows = requireArray(node.instruments, `${where} instruments`);
|
|
340
|
+
|
|
341
|
+
const values = {};
|
|
342
|
+
for (const row of rows) {
|
|
343
|
+
const item = requireObject(row, `${where} row`);
|
|
344
|
+
const spec = BREADTH_FIELDS[item.id];
|
|
345
|
+
if (!spec) continue;
|
|
346
|
+
// The NYSE table repeats volume ids for primary-market and composite rows; those are not
|
|
347
|
+
// extracted. A repeat of a field that IS extracted means the table changed meaning.
|
|
348
|
+
if (Object.prototype.hasOwnProperty.call(values, spec.key)) {
|
|
349
|
+
throw new Error(`${where}: duplicate row for ${item.id}`);
|
|
350
|
+
}
|
|
351
|
+
values[spec.key] = boundedNumber(item.latestClose, `${where} ${item.id}`, { ...spec.band, allowNull: true });
|
|
352
|
+
}
|
|
353
|
+
for (const spec of Object.values(BREADTH_FIELDS)) {
|
|
354
|
+
if (spec.required && !Number.isFinite(values[spec.key])) throw new Error(`${where}: missing ${spec.key}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const { advances, declines, new_highs: highs, new_lows: lows } = values;
|
|
358
|
+
return Object.freeze({
|
|
359
|
+
venue,
|
|
360
|
+
issues_traded: values.issues_traded ?? null,
|
|
361
|
+
advances,
|
|
362
|
+
declines,
|
|
363
|
+
unchanged: values.unchanged ?? null,
|
|
364
|
+
new_highs: highs ?? null,
|
|
365
|
+
new_lows: lows ?? null,
|
|
366
|
+
trin: values.trin ?? null,
|
|
367
|
+
net_advances: advances - declines,
|
|
368
|
+
advance_decline_ratio: declines > 0 ? Number((advances / declines).toFixed(4)) : null,
|
|
369
|
+
net_new_highs: Number.isFinite(highs) && Number.isFinite(lows) ? highs - lows : null,
|
|
370
|
+
unit: "issue_counts",
|
|
371
|
+
trade_date: tradeDate,
|
|
372
|
+
public_at: publicAt(tradeDate),
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function parseWsjMarketsDiary(payload, { sourceUrl = SOURCE_URLS.wsj_market_breadth } = {}) {
|
|
377
|
+
const root = requireObject(payload, "WSJ markets diary");
|
|
378
|
+
if (root.type !== "mdc_marketsdiary") {
|
|
379
|
+
throw new Error(`WSJ markets diary: unexpected payload type ${JSON.stringify(root.type)}`);
|
|
380
|
+
}
|
|
381
|
+
const data = requireObject(root.data, "WSJ markets diary data");
|
|
382
|
+
const tradeDate = isoDateFromLongForm(data.timestamp, "WSJ markets diary timestamp");
|
|
383
|
+
const sets = requireArray(data.instrumentSets, "WSJ markets diary instrumentSets");
|
|
384
|
+
const venues = sets.map((set, index) => parseBreadthSet(set, index, tradeDate));
|
|
385
|
+
return Object.freeze({
|
|
386
|
+
trade_date: tradeDate,
|
|
387
|
+
public_at: publicAt(tradeDate),
|
|
388
|
+
venues: Object.freeze(venues),
|
|
389
|
+
by_venue: Object.freeze(Object.fromEntries(venues.map((row) => [row.venue, row]))),
|
|
390
|
+
source_url: sourceUrl,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export async function fetchMarketBreadth({ signal } = {}) {
|
|
395
|
+
const url = SOURCE_URLS.wsj_market_breadth;
|
|
396
|
+
const text = await fetchText(url, LIMITS.QUOTE_FETCH_MS, signal);
|
|
397
|
+
return parseWsjMarketsDiary(JSON.parse(text), { sourceUrl: url });
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ---- Put/call ratios (CBOE daily statistics) ---------------------------------
|
|
401
|
+
|
|
402
|
+
export const CBOE_MAX_TRADING_DAY_LOOKBACK = 5;
|
|
403
|
+
|
|
404
|
+
const CBOE_RATIOS = Object.freeze({
|
|
405
|
+
"TOTAL PUT/CALL RATIO": "total",
|
|
406
|
+
"INDEX PUT/CALL RATIO": "index",
|
|
407
|
+
"EQUITY PUT/CALL RATIO": "equity",
|
|
408
|
+
"EXCHANGE TRADED PRODUCTS PUT/CALL RATIO": "exchange_traded_products",
|
|
409
|
+
"CBOE VOLATILITY INDEX (VIX) PUT/CALL RATIO": "vix",
|
|
410
|
+
"SPX + SPXW PUT/CALL RATIO": "spx_spxw",
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Candidate trade dates, newest first, skipping weekends.
|
|
415
|
+
*
|
|
416
|
+
* The daily file 403s on any non-trading day and also on the current session until it is
|
|
417
|
+
* published, so the most recent trading day has to be discovered by trying. Weekends are skipped
|
|
418
|
+
* because CBOE never publishes one; holidays are NOT skipped, because no reliable US market
|
|
419
|
+
* calendar is derivable here and a wrong one would silently skip a real trading day.
|
|
420
|
+
*/
|
|
421
|
+
export function tradingDayCandidates(asOf = null, maxAttempts = CBOE_MAX_TRADING_DAY_LOOKBACK) {
|
|
422
|
+
const limit = Number.isFinite(maxAttempts) && maxAttempts > 0 ? Math.floor(maxAttempts) : 1;
|
|
423
|
+
const cursor = new Date(`${isoDay(asOf)}T00:00:00Z`);
|
|
424
|
+
const days = [];
|
|
425
|
+
// Bounded so a pathological input cannot spin: at most two weekend days per five weekdays.
|
|
426
|
+
for (let step = 0; step < limit * 2 + 7 && days.length < limit; step += 1) {
|
|
427
|
+
const weekday = cursor.getUTCDay();
|
|
428
|
+
if (weekday !== 0 && weekday !== 6) days.push(cursor.toISOString().slice(0, 10));
|
|
429
|
+
cursor.setUTCDate(cursor.getUTCDate() - 1);
|
|
430
|
+
}
|
|
431
|
+
return days;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** A published ratio, or null. CBOE writes `0.00` for a product with no contracts traded that
|
|
435
|
+
* session; that is an absence, not a put/call ratio of zero, so it must not read as extreme
|
|
436
|
+
* call skew. */
|
|
437
|
+
function cboeRatio(raw, what) {
|
|
438
|
+
const value = boundedNumber(raw, what, { ...BANDS.PUT_CALL, allowNull: true });
|
|
439
|
+
return value === null || value === 0 ? null : value;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function cboeVolumeBlock(payload, key) {
|
|
443
|
+
const rows = payload[key];
|
|
444
|
+
if (rows === undefined) return null;
|
|
445
|
+
const where = `CBOE daily options "${key}"`;
|
|
446
|
+
const out = {};
|
|
447
|
+
for (const row of requireArray(rows, where)) {
|
|
448
|
+
const item = requireObject(row, `${where} row`);
|
|
449
|
+
const slot = item.name === "VOLUME" ? "volume" : (item.name === "OPEN INTEREST" ? "open_interest" : null);
|
|
450
|
+
if (!slot) continue;
|
|
451
|
+
out[slot] = Object.freeze({
|
|
452
|
+
call: boundedNumber(item.call, `${where} ${item.name} call`, { min: 0, max: 1e12 }),
|
|
453
|
+
put: boundedNumber(item.put, `${where} ${item.name} put`, { min: 0, max: 1e12 }),
|
|
454
|
+
total: boundedNumber(item.total, `${where} ${item.name} total`, { min: 0, max: 1e12 }),
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
return Object.keys(out).length ? Object.freeze(out) : null;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export function parseCboeDailyOptions(payload, { tradeDate, sourceUrl, requestedAsOf = null, attempts = [] } = {}) {
|
|
461
|
+
const root = requireObject(payload, "CBOE daily options");
|
|
462
|
+
const rows = requireArray(root.ratios, "CBOE daily options ratios");
|
|
463
|
+
const published = new Map();
|
|
464
|
+
for (const row of rows) {
|
|
465
|
+
const item = requireObject(row, "CBOE daily options ratio row");
|
|
466
|
+
published.set(requireString(item.name, "CBOE daily options ratio name"), item.value);
|
|
467
|
+
}
|
|
468
|
+
if (!published.has("TOTAL PUT/CALL RATIO")) {
|
|
469
|
+
throw new Error("CBOE daily options: TOTAL PUT/CALL RATIO is absent");
|
|
470
|
+
}
|
|
471
|
+
const ratios = {};
|
|
472
|
+
for (const [name, key] of Object.entries(CBOE_RATIOS)) {
|
|
473
|
+
ratios[key] = published.has(name) ? cboeRatio(published.get(name), `CBOE daily options "${name}"`) : null;
|
|
474
|
+
}
|
|
475
|
+
if (ratios.total === null) throw new Error("CBOE daily options: TOTAL PUT/CALL RATIO is not a usable number");
|
|
476
|
+
return Object.freeze({
|
|
477
|
+
trade_date: tradeDate,
|
|
478
|
+
// The caller asked for `requestedAsOf` and got `tradeDate`; keeping both stops a Monday
|
|
479
|
+
// reading from being presented as today's.
|
|
480
|
+
requested_as_of: requestedAsOf,
|
|
481
|
+
is_most_recent_available: true,
|
|
482
|
+
public_at: publicAt(tradeDate),
|
|
483
|
+
unit: "ratio_of_put_to_call_volume",
|
|
484
|
+
ratios: Object.freeze(ratios),
|
|
485
|
+
volume: Object.freeze({
|
|
486
|
+
total: cboeVolumeBlock(root, "SUM OF ALL PRODUCTS"),
|
|
487
|
+
index: cboeVolumeBlock(root, "INDEX OPTIONS"),
|
|
488
|
+
equity: cboeVolumeBlock(root, "EQUITY OPTIONS"),
|
|
489
|
+
exchange_traded_products: cboeVolumeBlock(root, "EXCHANGE TRADED PRODUCTS"),
|
|
490
|
+
}),
|
|
491
|
+
skipped_dates: Object.freeze([...attempts]),
|
|
492
|
+
source_url: sourceUrl,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export async function fetchPutCallRatios({ signal, asOf = null, maxAttempts = CBOE_MAX_TRADING_DAY_LOOKBACK } = {}) {
|
|
497
|
+
const candidates = tradingDayCandidates(asOf, maxAttempts);
|
|
498
|
+
const attempts = [];
|
|
499
|
+
for (const date of candidates) {
|
|
500
|
+
// An upstream cancellation must not be retried four more times.
|
|
501
|
+
if (signal?.aborted) throw new Error("CBOE daily options: cancelled before a trading day was found");
|
|
502
|
+
const url = `${SOURCE_URLS.cboe_put_call_daily}${date}_daily_options`;
|
|
503
|
+
try {
|
|
504
|
+
const text = await fetchText(url, LIMITS.QUOTE_FETCH_MS, signal);
|
|
505
|
+
return parseCboeDailyOptions(JSON.parse(text), {
|
|
506
|
+
tradeDate: date, sourceUrl: url, requestedAsOf: isoDay(asOf), attempts,
|
|
507
|
+
});
|
|
508
|
+
} catch (error) {
|
|
509
|
+
attempts.push(`${date}: ${String(error?.message || error)}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
throw new Error(`CBOE daily options unavailable for the last ${candidates.length} weekdays (${attempts.join("; ")})`);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ---- VIX history (CBOE) ------------------------------------------------------
|
|
516
|
+
|
|
517
|
+
const VIX_HEADER = Object.freeze(["DATE", "OPEN", "HIGH", "LOW", "CLOSE"]);
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* A malformed or non-numeric row is dropped rather than carried forward, exactly as a missing
|
|
521
|
+
* FRED observation is. The band check is applied to the latest value only: history is used for
|
|
522
|
+
* ranking, but the latest close is the number that becomes a published fact.
|
|
523
|
+
*/
|
|
524
|
+
export function parseVixHistoryCsv(csv, { sourceUrl = SOURCE_URLS.cboe_vix_history, asOf = null } = {}) {
|
|
525
|
+
const lines = String(csv || "").trim().split(/\r?\n/);
|
|
526
|
+
if (lines.length < 2) throw new Error("CBOE VIX history: empty CSV");
|
|
527
|
+
const header = lines[0].trim().toUpperCase().split(",").map((cell) => cell.trim());
|
|
528
|
+
if (VIX_HEADER.some((column, index) => header[index] !== column)) {
|
|
529
|
+
throw new Error(`CBOE VIX history: unexpected header ${JSON.stringify(lines[0])}`);
|
|
530
|
+
}
|
|
531
|
+
const observations = [];
|
|
532
|
+
for (const line of lines.slice(1)) {
|
|
533
|
+
const cells = line.split(",");
|
|
534
|
+
const match = /^(\d{2})\/(\d{2})\/(\d{4})$/u.exec(String(cells[0] || "").trim());
|
|
535
|
+
if (!match) continue;
|
|
536
|
+
const close = Number(cells[4]);
|
|
537
|
+
if (!Number.isFinite(close) || close <= 0) continue;
|
|
538
|
+
observations.push({ date: `${match[3]}-${match[1]}-${match[2]}`, value: close });
|
|
539
|
+
}
|
|
540
|
+
if (!observations.length) throw new Error("CBOE VIX history: no numeric observations");
|
|
541
|
+
const cutoff = asOf ? String(asOf).slice(0, 10) : null;
|
|
542
|
+
const visible = cutoff ? observations.filter((row) => row.date <= cutoff) : observations;
|
|
543
|
+
if (!visible.length) throw new Error(`CBOE VIX history: no observation at or before ${cutoff}`);
|
|
544
|
+
const last = visible.at(-1);
|
|
545
|
+
boundedNumber(last.value, "CBOE VIX history latest close", BANDS.VIX);
|
|
546
|
+
return Object.freeze({
|
|
547
|
+
id: "VIX",
|
|
548
|
+
unit: "index_points",
|
|
549
|
+
label: "CBOE Volatility Index, daily close",
|
|
550
|
+
observations: visible,
|
|
551
|
+
latest: last.value,
|
|
552
|
+
observation_date: last.date,
|
|
553
|
+
public_at: publicAt(last.date),
|
|
554
|
+
source_url: sourceUrl,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export async function fetchVixHistory({ signal, asOf = null } = {}) {
|
|
559
|
+
const url = SOURCE_URLS.cboe_vix_history;
|
|
560
|
+
// A ~470KB CSV needs more headroom than a JSON snapshot, matching the FRED CSV budget.
|
|
561
|
+
const text = await fetchText(url, LIMITS.QUOTE_FETCH_MS * 2, signal);
|
|
562
|
+
return parseVixHistoryCsv(text, { sourceUrl: url, asOf });
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// ---- Percentiles -------------------------------------------------------------
|
|
566
|
+
|
|
567
|
+
/** Value `windowDays` calendar days before the latest observation, or null when unavailable. */
|
|
568
|
+
export function valueBefore(observations, windowDays) {
|
|
569
|
+
const rows = Array.isArray(observations) ? observations : observations?.observations;
|
|
570
|
+
if (!Array.isArray(rows) || !rows.length) return null;
|
|
571
|
+
const cutoff = new Date(`${rows.at(-1).date}T00:00:00Z`);
|
|
572
|
+
cutoff.setUTCDate(cutoff.getUTCDate() - windowDays);
|
|
573
|
+
const target = cutoff.toISOString().slice(0, 10);
|
|
574
|
+
const prior = rows.filter((row) => row.date <= target);
|
|
575
|
+
return prior.length ? prior.at(-1) : null;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Where the latest observation sits in its own history, as a 0-1 rank.
|
|
580
|
+
*
|
|
581
|
+
* Field-for-field the same contract as `fred.mjs` `percentileRank`, so a reader never has to ask
|
|
582
|
+
* which percentile convention a given number follows. Accepts a series object or a bare
|
|
583
|
+
* observation array. Returns null below `minSample`: "the 92nd percentile" means nothing without
|
|
584
|
+
* saying percentile of what and since when.
|
|
585
|
+
*/
|
|
586
|
+
export function percentileRank(series, { sinceDays = 365 * 10, minSample = 30 } = {}) {
|
|
587
|
+
const rows = Array.isArray(series) ? series : series?.observations;
|
|
588
|
+
if (!Array.isArray(rows) || rows.length < 2) return null;
|
|
589
|
+
const start = valueBefore(rows, sinceDays);
|
|
590
|
+
const sample = start ? rows.filter((row) => row.date >= start.date) : rows;
|
|
591
|
+
if (sample.length < minSample) return null;
|
|
592
|
+
const latest = rows.at(-1).value;
|
|
593
|
+
const below = sample.filter((row) => row.value < latest).length;
|
|
594
|
+
return {
|
|
595
|
+
percentile: Number((below / sample.length).toFixed(4)),
|
|
596
|
+
sample_size: sample.length,
|
|
597
|
+
sample_start: sample[0].date,
|
|
598
|
+
sample_end: rows.at(-1).date,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
export const VALUATION_PERCENTILE_GAP =
|
|
603
|
+
"no same-basis index P/E history is available from an implemented source; a percentile computed "
|
|
604
|
+
+ "from one observation, or against a differently-based history, would be fabricated";
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* A valuation percentile that always states its metric, its basis and its sample.
|
|
608
|
+
*
|
|
609
|
+
* Refuses in three cases, all of which are gaps rather than zeroes: no history at all, a sample
|
|
610
|
+
* too short to rank against, or a history whose declared basis differs from the fact's. The last
|
|
611
|
+
* one is the whole point -- ranking a WSJ-basis 25.17x inside a GAAP-basis history would look
|
|
612
|
+
* like a cheap market when nothing about the market changed.
|
|
613
|
+
*/
|
|
614
|
+
export function valuationPercentile(fact, { metric = "pe_trailing", history = null, sinceDays = 365 * 10 } = {}) {
|
|
615
|
+
const value = Number(fact?.[metric]);
|
|
616
|
+
if (!Number.isFinite(value)) return null;
|
|
617
|
+
const historyBasis = Array.isArray(history) ? null : history?.basis;
|
|
618
|
+
if (historyBasis && fact?.basis && historyBasis !== fact.basis) return null;
|
|
619
|
+
const rank = percentileRank(history, { sinceDays });
|
|
620
|
+
if (!rank) return null;
|
|
621
|
+
return Object.freeze({ metric, basis: fact?.basis ?? null, value, ...rank });
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// ---- Orchestration -----------------------------------------------------------
|
|
625
|
+
|
|
626
|
+
async function settle(label, run, unavailable) {
|
|
627
|
+
try {
|
|
628
|
+
return await run();
|
|
629
|
+
} catch (error) {
|
|
630
|
+
unavailable.push(`${label}: ${String(error?.message || error)}`);
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** The one index the caller asked for. A feed that simply does not cover it is a named gap. */
|
|
636
|
+
function selectValuation(valuationSet, symbol, unavailable) {
|
|
637
|
+
const valuation = valuationSet?.instruments.find((row) => row.symbol === symbol) || null;
|
|
638
|
+
if (valuationSet && !valuation) {
|
|
639
|
+
const covered = valuationSet.instruments.map((row) => row.symbol || row.ticker).join(", ");
|
|
640
|
+
unavailable.push(`index valuation for ${symbol}: WSJ peyields publishes ${covered} only`);
|
|
641
|
+
}
|
|
642
|
+
return valuation;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/** Earnings yield inherits the P/E's basis and dates; it is a restatement, not a new observation. */
|
|
646
|
+
function earningsYieldFact(valuation) {
|
|
647
|
+
const value = valuation ? earningsYield(valuation.pe_trailing) : null;
|
|
648
|
+
if (!Number.isFinite(value)) return null;
|
|
649
|
+
return Object.freeze({
|
|
650
|
+
value,
|
|
651
|
+
unit: "decimal_fraction",
|
|
652
|
+
derived_from: "pe_trailing",
|
|
653
|
+
basis: valuation.basis,
|
|
654
|
+
trade_date: valuation.trade_date,
|
|
655
|
+
public_at: valuation.public_at,
|
|
656
|
+
source_url: valuation.source_url,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/** Gaps that exist by design rather than by upstream failure, so they are stated every run. */
|
|
661
|
+
function recordStructuralGaps(symbol, constituents, unavailable) {
|
|
662
|
+
if (!constituents) {
|
|
663
|
+
unavailable.push(`constituent proxy for ${symbol}: no tracking-ETF proxy is mapped for this index`);
|
|
664
|
+
} else if (!constituents.holdings) {
|
|
665
|
+
unavailable.push(
|
|
666
|
+
`constituents for ${symbol}: not fetched here; pass ${constituents.proxy_etf} holdings in. `
|
|
667
|
+
+ `Membership and weights are licensed by ${constituents.licensor} and must be labelled a proxy.`,
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
for (const source of DAMODARAN_SOURCES) {
|
|
671
|
+
if (!source.parser_implemented) unavailable.push(`${source.label} (${source.url}): ${source.reason}`);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Every aggregate fact available for one index, with a named gap for everything that is not.
|
|
677
|
+
*
|
|
678
|
+
* `holdings` is the hook for the ETF-proxy path: pass the tracking ETF's published holdings in
|
|
679
|
+
* and they are attached, labelled as a proxy. Nothing here scrapes a constituent list.
|
|
680
|
+
* `peHistory` is the hook for a same-basis P/E history; without one the valuation percentile
|
|
681
|
+
* stays null rather than being computed from the single point this module can see.
|
|
682
|
+
*/
|
|
683
|
+
export async function fetchIndexAggregate({ symbol, signal, asOf = null, holdings = null, peHistory = null } = {}) {
|
|
684
|
+
const normalized = normalizeIndexSymbol(symbol);
|
|
685
|
+
if (!normalized) throw new Error("fetchIndexAggregate requires an index symbol");
|
|
686
|
+
const unavailable = [];
|
|
687
|
+
|
|
688
|
+
const [valuationSet, breadth, putCall, vix] = await Promise.all([
|
|
689
|
+
settle("index valuation (WSJ peyields)", () => fetchIndexValuation({ signal }), unavailable),
|
|
690
|
+
settle("market breadth (WSJ markets diary)", () => fetchMarketBreadth({ signal }), unavailable),
|
|
691
|
+
settle("put/call ratios (CBOE daily)", () => fetchPutCallRatios({ signal, asOf }), unavailable),
|
|
692
|
+
settle("VIX history (CBOE)", () => fetchVixHistory({ signal, asOf }), unavailable),
|
|
693
|
+
]);
|
|
694
|
+
|
|
695
|
+
const valuation = selectValuation(valuationSet, normalized, unavailable);
|
|
696
|
+
const percentile = valuation ? valuationPercentile(valuation, { history: peHistory }) : null;
|
|
697
|
+
if (valuation && !percentile) unavailable.push(`index P/E valuation percentile for ${normalized}: ${VALUATION_PERCENTILE_GAP}`);
|
|
698
|
+
const constituents = proxyConstituents(normalized, holdings);
|
|
699
|
+
recordStructuralGaps(normalized, constituents, unavailable);
|
|
700
|
+
|
|
701
|
+
return Object.freeze({
|
|
702
|
+
symbol: normalized,
|
|
703
|
+
as_of: asOf ? String(asOf) : new Date().toISOString(),
|
|
704
|
+
valuation,
|
|
705
|
+
earnings_yield: earningsYieldFact(valuation),
|
|
706
|
+
valuation_percentile: percentile,
|
|
707
|
+
breadth,
|
|
708
|
+
put_call: putCall,
|
|
709
|
+
vix,
|
|
710
|
+
vix_percentile: vix ? percentileRank(vix) : null,
|
|
711
|
+
constituents,
|
|
712
|
+
equity_risk_premium: null,
|
|
713
|
+
basis_warning: PE_BASIS_DIVERGENCE,
|
|
714
|
+
sources: SOURCE_URLS,
|
|
715
|
+
unimplemented_sources: DAMODARAN_SOURCES,
|
|
716
|
+
unavailable,
|
|
717
|
+
});
|
|
718
|
+
}
|