quantwise 1.2.0 → 1.2.2
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/skills/README.md +80 -0
- package/.claude/skills/backtest-expert/SKILL.md +206 -0
- package/.claude/skills/backtest-expert/references/failed_tests.md +236 -0
- package/.claude/skills/backtest-expert/references/methodology.md +227 -0
- package/.claude/skills/breadth-chart-analyst/SKILL.md +583 -0
- package/.claude/skills/breadth-chart-analyst/assets/SP500_Breadth_Index_200MA_8MA.jpeg +0 -0
- package/.claude/skills/breadth-chart-analyst/assets/US_Stock_Market_Uptrend_Ratio.jpeg +0 -0
- package/.claude/skills/breadth-chart-analyst/assets/breadth_analysis_template.md +558 -0
- package/.claude/skills/breadth-chart-analyst/references/breadth_chart_methodology.md +590 -0
- package/.claude/skills/canslim-screener/SKILL.md +599 -0
- package/.claude/skills/canslim-screener/references/canslim_methodology.md +606 -0
- package/.claude/skills/canslim-screener/references/fmp_api_endpoints.md +707 -0
- package/.claude/skills/canslim-screener/references/interpretation_guide.md +516 -0
- package/.claude/skills/canslim-screener/references/scoring_system.md +597 -0
- package/.claude/skills/canslim-screener/scripts/calculators/earnings_calculator.py +343 -0
- package/.claude/skills/canslim-screener/scripts/calculators/growth_calculator.py +334 -0
- package/.claude/skills/canslim-screener/scripts/calculators/institutional_calculator.py +347 -0
- package/.claude/skills/canslim-screener/scripts/calculators/leadership_calculator.py +380 -0
- package/.claude/skills/canslim-screener/scripts/calculators/market_calculator.py +244 -0
- package/.claude/skills/canslim-screener/scripts/calculators/new_highs_calculator.py +194 -0
- package/.claude/skills/canslim-screener/scripts/calculators/supply_demand_calculator.py +221 -0
- package/.claude/skills/canslim-screener/scripts/finviz_stock_client.py +227 -0
- package/.claude/skills/canslim-screener/scripts/fmp_client.py +393 -0
- package/.claude/skills/canslim-screener/scripts/report_generator.py +405 -0
- package/.claude/skills/canslim-screener/scripts/scorer.py +625 -0
- package/.claude/skills/canslim-screener/scripts/screen_canslim.py +361 -0
- package/.claude/skills/canslim-screener/scripts/test_institutional_endpoint.py +109 -0
- package/.claude/skills/chart/SKILL.md +20 -0
- package/.claude/skills/dividend-growth-pullback-screener/SKILL.md +322 -0
- package/.claude/skills/dividend-growth-pullback-screener/references/dividend_growth_compounding.md +400 -0
- package/.claude/skills/dividend-growth-pullback-screener/references/fmp_api_guide.md +642 -0
- package/.claude/skills/dividend-growth-pullback-screener/references/rsi_oversold_strategy.md +333 -0
- package/.claude/skills/dividend-growth-pullback-screener/scripts/screen_dividend_growth_rsi.py +1155 -0
- package/.claude/skills/earnings-calendar/SKILL.md +721 -0
- package/.claude/skills/earnings-calendar/assets/earnings_report_template.md +102 -0
- package/.claude/skills/earnings-calendar/references/fmp_api_guide.md +590 -0
- package/.claude/skills/earnings-calendar/scripts/fetch_earnings_fmp.py +443 -0
- package/.claude/skills/earnings-calendar/scripts/generate_report.py +366 -0
- package/.claude/skills/economic-calendar-fetcher/SKILL.md +365 -0
- package/.claude/skills/economic-calendar-fetcher/references/fmp_api_documentation.md +345 -0
- package/.claude/skills/economic-calendar-fetcher/scripts/get_economic_calendar.py +267 -0
- package/.claude/skills/ftd-detector/SKILL.md +147 -0
- package/.claude/skills/ftd-detector/references/ftd_methodology.md +188 -0
- package/.claude/skills/ftd-detector/references/post_ftd_guide.md +185 -0
- package/.claude/skills/ftd-detector/scripts/fmp_client.py +158 -0
- package/.claude/skills/ftd-detector/scripts/ftd_detector.py +280 -0
- package/.claude/skills/ftd-detector/scripts/post_ftd_monitor.py +404 -0
- package/.claude/skills/ftd-detector/scripts/rally_tracker.py +508 -0
- package/.claude/skills/ftd-detector/scripts/report_generator.py +341 -0
- package/.claude/skills/ftd-detector/scripts/tests/conftest.py +9 -0
- package/.claude/skills/ftd-detector/scripts/tests/helpers.py +107 -0
- package/.claude/skills/ftd-detector/scripts/tests/test_post_ftd_monitor.py +311 -0
- package/.claude/skills/ftd-detector/scripts/tests/test_rally_tracker.py +302 -0
- package/.claude/skills/institutional-flow-tracker/README.md +362 -0
- package/.claude/skills/institutional-flow-tracker/SKILL.md +357 -0
- package/.claude/skills/institutional-flow-tracker/references/13f_filings_guide.md +383 -0
- package/.claude/skills/institutional-flow-tracker/references/institutional_investor_types.md +580 -0
- package/.claude/skills/institutional-flow-tracker/references/interpretation_framework.md +573 -0
- package/.claude/skills/institutional-flow-tracker/scripts/analyze_single_stock.py +457 -0
- package/.claude/skills/institutional-flow-tracker/scripts/track_institution_portfolio.py +108 -0
- package/.claude/skills/institutional-flow-tracker/scripts/track_institutional_flow.py +450 -0
- package/.claude/skills/macro-regime-detector/SKILL.md +86 -0
- package/.claude/skills/macro-regime-detector/references/historical_regimes.md +124 -0
- package/.claude/skills/macro-regime-detector/references/indicator_interpretation_guide.md +144 -0
- package/.claude/skills/macro-regime-detector/references/regime_detection_methodology.md +138 -0
- package/.claude/skills/macro-regime-detector/scripts/calculators/__init__.py +1 -0
- package/.claude/skills/macro-regime-detector/scripts/calculators/concentration_calculator.py +165 -0
- package/.claude/skills/macro-regime-detector/scripts/calculators/credit_conditions_calculator.py +124 -0
- package/.claude/skills/macro-regime-detector/scripts/calculators/equity_bond_calculator.py +198 -0
- package/.claude/skills/macro-regime-detector/scripts/calculators/sector_rotation_calculator.py +123 -0
- package/.claude/skills/macro-regime-detector/scripts/calculators/size_factor_calculator.py +131 -0
- package/.claude/skills/macro-regime-detector/scripts/calculators/utils.py +347 -0
- package/.claude/skills/macro-regime-detector/scripts/calculators/yield_curve_calculator.py +279 -0
- package/.claude/skills/macro-regime-detector/scripts/fmp_client.py +134 -0
- package/.claude/skills/macro-regime-detector/scripts/macro_regime_detector.py +278 -0
- package/.claude/skills/macro-regime-detector/scripts/report_generator.py +327 -0
- package/.claude/skills/macro-regime-detector/scripts/scorer.py +574 -0
- package/.claude/skills/macro-regime-detector/scripts/tests/conftest.py +9 -0
- package/.claude/skills/macro-regime-detector/scripts/tests/test_concentration.py +78 -0
- package/.claude/skills/macro-regime-detector/scripts/tests/test_credit_conditions.py +59 -0
- package/.claude/skills/macro-regime-detector/scripts/tests/test_equity_bond.py +74 -0
- package/.claude/skills/macro-regime-detector/scripts/tests/test_helpers.py +90 -0
- package/.claude/skills/macro-regime-detector/scripts/tests/test_scorer.py +439 -0
- package/.claude/skills/macro-regime-detector/scripts/tests/test_sector_rotation.py +78 -0
- package/.claude/skills/macro-regime-detector/scripts/tests/test_size_factor.py +59 -0
- package/.claude/skills/macro-regime-detector/scripts/tests/test_utils.py +126 -0
- package/.claude/skills/macro-regime-detector/scripts/tests/test_yield_curve.py +64 -0
- package/.claude/skills/market-breadth-analyzer/SKILL.md +121 -0
- package/.claude/skills/market-breadth-analyzer/references/breadth_analysis_methodology.md +168 -0
- package/.claude/skills/market-breadth-analyzer/scripts/calculators/__init__.py +1 -0
- package/.claude/skills/market-breadth-analyzer/scripts/calculators/bearish_signal_calculator.py +150 -0
- package/.claude/skills/market-breadth-analyzer/scripts/calculators/cycle_calculator.py +168 -0
- package/.claude/skills/market-breadth-analyzer/scripts/calculators/divergence_calculator.py +119 -0
- package/.claude/skills/market-breadth-analyzer/scripts/calculators/historical_context_calculator.py +120 -0
- package/.claude/skills/market-breadth-analyzer/scripts/calculators/ma_crossover_calculator.py +115 -0
- package/.claude/skills/market-breadth-analyzer/scripts/calculators/trend_level_calculator.py +103 -0
- package/.claude/skills/market-breadth-analyzer/scripts/csv_client.py +225 -0
- package/.claude/skills/market-breadth-analyzer/scripts/market_breadth_analyzer.py +307 -0
- package/.claude/skills/market-breadth-analyzer/scripts/report_generator.py +330 -0
- package/.claude/skills/market-breadth-analyzer/scripts/scorer.py +271 -0
- package/.claude/skills/market-environment-analysis/SKILL.md +139 -0
- package/.claude/skills/market-environment-analysis/references/analysis_patterns.md +124 -0
- package/.claude/skills/market-environment-analysis/references/indicators.md +99 -0
- package/.claude/skills/market-environment-analysis/scripts/market_utils.py +127 -0
- package/.claude/skills/market-news-analyst/SKILL.md +714 -0
- package/.claude/skills/market-news-analyst/references/corporate_news_impact.md +446 -0
- package/.claude/skills/market-news-analyst/references/geopolitical_commodity_correlations.md +499 -0
- package/.claude/skills/market-news-analyst/references/market_event_patterns.md +393 -0
- package/.claude/skills/market-news-analyst/references/trusted_news_sources.md +510 -0
- package/.claude/skills/market-top-detector/SKILL.md +159 -0
- package/.claude/skills/market-top-detector/references/distribution_day_guide.md +100 -0
- package/.claude/skills/market-top-detector/references/historical_tops.md +142 -0
- package/.claude/skills/market-top-detector/references/market_top_methodology.md +167 -0
- package/.claude/skills/market-top-detector/scripts/calculators/__init__.py +17 -0
- package/.claude/skills/market-top-detector/scripts/calculators/breadth_calculator.py +116 -0
- package/.claude/skills/market-top-detector/scripts/calculators/defensive_rotation_calculator.py +127 -0
- package/.claude/skills/market-top-detector/scripts/calculators/distribution_day_calculator.py +161 -0
- package/.claude/skills/market-top-detector/scripts/calculators/index_technical_calculator.py +254 -0
- package/.claude/skills/market-top-detector/scripts/calculators/leading_stock_calculator.py +198 -0
- package/.claude/skills/market-top-detector/scripts/calculators/sentiment_calculator.py +213 -0
- package/.claude/skills/market-top-detector/scripts/fmp_client.py +158 -0
- package/.claude/skills/market-top-detector/scripts/market_top_detector.py +349 -0
- package/.claude/skills/market-top-detector/scripts/report_generator.py +314 -0
- package/.claude/skills/market-top-detector/scripts/scorer.py +473 -0
- package/.claude/skills/market-top-detector/scripts/tests/conftest.py +9 -0
- package/.claude/skills/market-top-detector/scripts/tests/helpers.py +49 -0
- package/.claude/skills/market-top-detector/scripts/tests/test_breadth.py +62 -0
- package/.claude/skills/market-top-detector/scripts/tests/test_defensive_rotation.py +56 -0
- package/.claude/skills/market-top-detector/scripts/tests/test_distribution_day.py +92 -0
- package/.claude/skills/market-top-detector/scripts/tests/test_index_technical.py +73 -0
- package/.claude/skills/market-top-detector/scripts/tests/test_leading_stock.py +57 -0
- package/.claude/skills/market-top-detector/scripts/tests/test_scorer.py +180 -0
- package/.claude/skills/market-top-detector/scripts/tests/test_sentiment.py +64 -0
- package/.claude/skills/options-strategy-advisor/README.md +469 -0
- package/.claude/skills/options-strategy-advisor/SKILL.md +959 -0
- package/.claude/skills/options-strategy-advisor/scripts/black_scholes.py +495 -0
- package/.claude/skills/pair-trade-screener/README.md +389 -0
- package/.claude/skills/pair-trade-screener/SKILL.md +622 -0
- package/.claude/skills/pair-trade-screener/references/cointegration_guide.md +745 -0
- package/.claude/skills/pair-trade-screener/references/methodology.md +853 -0
- package/.claude/skills/pair-trade-screener/scripts/analyze_spread.py +394 -0
- package/.claude/skills/pair-trade-screener/scripts/find_pairs.py +535 -0
- package/.claude/skills/portfolio-manager/README.md +394 -0
- package/.claude/skills/portfolio-manager/SKILL.md +750 -0
- package/.claude/skills/portfolio-manager/references/alpaca-mcp-setup.md +367 -0
- package/.claude/skills/portfolio-manager/references/asset-allocation.md +502 -0
- package/.claude/skills/portfolio-manager/references/diversification-principles.md +553 -0
- package/.claude/skills/portfolio-manager/references/portfolio-risk-metrics.md +603 -0
- package/.claude/skills/portfolio-manager/references/position-evaluation.md +477 -0
- package/.claude/skills/portfolio-manager/references/rebalancing-strategies.md +715 -0
- package/.claude/skills/portfolio-manager/references/risk-profile-questionnaire.md +608 -0
- package/.claude/skills/portfolio-manager/references/target-allocations.md +558 -0
- package/.claude/skills/portfolio-manager/scripts/test_alpaca_connection.py +286 -0
- package/.claude/skills/scenario-analyzer/SKILL.md +317 -0
- package/.claude/skills/scenario-analyzer/references/headline_event_patterns.md +264 -0
- package/.claude/skills/scenario-analyzer/references/scenario_playbooks.md +320 -0
- package/.claude/skills/scenario-analyzer/references/sector_sensitivity_matrix.md +217 -0
- package/.claude/skills/sector-analyst/SKILL.md +206 -0
- package/.claude/skills/sector-analyst/assets/industory_performance_1.jpeg +0 -0
- package/.claude/skills/sector-analyst/assets/industory_performance_2.jpeg +0 -0
- package/.claude/skills/sector-analyst/assets/sector_performance.jpeg +0 -0
- package/.claude/skills/sector-analyst/references/sector_rotation.md +170 -0
- package/.claude/skills/stanley-druckenmiller-investment/SKILL.md +84 -0
- package/.claude/skills/stanley-druckenmiller-investment/references/case-studies.md +148 -0
- package/.claude/skills/stanley-druckenmiller-investment/references/investment-philosophy.md +80 -0
- package/.claude/skills/stanley-druckenmiller-investment/references/market-analysis-guide.md +146 -0
- package/.claude/skills/stock/NOTION_SETUP.md +33 -0
- package/.claude/skills/stock/SKILL.md +38 -0
- package/.claude/skills/technical-analyst/SKILL.md +238 -0
- package/.claude/skills/technical-analyst/assets/analysis_template.md +183 -0
- package/.claude/skills/technical-analyst/references/technical_analysis_framework.md +282 -0
- package/.claude/skills/theme-detector/SKILL.md +320 -0
- package/.claude/skills/theme-detector/assets/report_template.md +155 -0
- package/.claude/skills/theme-detector/references/cross_sector_themes.md +252 -0
- package/.claude/skills/theme-detector/references/finviz_industry_codes.md +403 -0
- package/.claude/skills/theme-detector/references/thematic_etf_catalog.md +333 -0
- package/.claude/skills/theme-detector/references/theme_detection_methodology.md +430 -0
- package/.claude/skills/theme-detector/scripts/calculators/__init__.py +1 -0
- package/.claude/skills/theme-detector/scripts/calculators/heat_calculator.py +123 -0
- package/.claude/skills/theme-detector/scripts/calculators/industry_ranker.py +98 -0
- package/.claude/skills/theme-detector/scripts/calculators/lifecycle_calculator.py +172 -0
- package/.claude/skills/theme-detector/scripts/calculators/theme_classifier.py +195 -0
- package/.claude/skills/theme-detector/scripts/calculators/theme_discoverer.py +280 -0
- package/.claude/skills/theme-detector/scripts/config_loader.py +142 -0
- package/.claude/skills/theme-detector/scripts/default_theme_config.py +254 -0
- package/.claude/skills/theme-detector/scripts/etf_scanner.py +609 -0
- package/.claude/skills/theme-detector/scripts/finviz_performance_client.py +131 -0
- package/.claude/skills/theme-detector/scripts/report_generator.py +490 -0
- package/.claude/skills/theme-detector/scripts/representative_stock_selector.py +673 -0
- package/.claude/skills/theme-detector/scripts/scorer.py +87 -0
- package/.claude/skills/theme-detector/scripts/tests/README.md +21 -0
- package/.claude/skills/theme-detector/scripts/tests/conftest.py +9 -0
- package/.claude/skills/theme-detector/scripts/tests/test_config_loader.py +239 -0
- package/.claude/skills/theme-detector/scripts/tests/test_etf_scanner.py +810 -0
- package/.claude/skills/theme-detector/scripts/tests/test_heat_calculator.py +245 -0
- package/.claude/skills/theme-detector/scripts/tests/test_industry_ranker.py +256 -0
- package/.claude/skills/theme-detector/scripts/tests/test_lifecycle_calculator.py +301 -0
- package/.claude/skills/theme-detector/scripts/tests/test_report_generator.py +624 -0
- package/.claude/skills/theme-detector/scripts/tests/test_representative_stock_selector.py +898 -0
- package/.claude/skills/theme-detector/scripts/tests/test_scorer.py +185 -0
- package/.claude/skills/theme-detector/scripts/tests/test_theme_classifier.py +534 -0
- package/.claude/skills/theme-detector/scripts/tests/test_theme_detector_e2e.py +467 -0
- package/.claude/skills/theme-detector/scripts/tests/test_theme_discoverer.py +458 -0
- package/.claude/skills/theme-detector/scripts/tests/test_uptrend_client.py +76 -0
- package/.claude/skills/theme-detector/scripts/theme_detector.py +815 -0
- package/.claude/skills/theme-detector/scripts/themes.yaml +168 -0
- package/.claude/skills/theme-detector/scripts/uptrend_client.py +241 -0
- package/.claude/skills/uptrend-analyzer/SKILL.md +108 -0
- package/.claude/skills/uptrend-analyzer/references/uptrend_methodology.md +215 -0
- package/.claude/skills/uptrend-analyzer/scripts/calculators/__init__.py +1 -0
- package/.claude/skills/uptrend-analyzer/scripts/calculators/historical_context_calculator.py +122 -0
- package/.claude/skills/uptrend-analyzer/scripts/calculators/market_breadth_calculator.py +145 -0
- package/.claude/skills/uptrend-analyzer/scripts/calculators/momentum_calculator.py +183 -0
- package/.claude/skills/uptrend-analyzer/scripts/calculators/sector_participation_calculator.py +204 -0
- package/.claude/skills/uptrend-analyzer/scripts/calculators/sector_rotation_calculator.py +218 -0
- package/.claude/skills/uptrend-analyzer/scripts/data_fetcher.py +236 -0
- package/.claude/skills/uptrend-analyzer/scripts/report_generator.py +329 -0
- package/.claude/skills/uptrend-analyzer/scripts/scorer.py +276 -0
- package/.claude/skills/uptrend-analyzer/scripts/uptrend_analyzer.py +219 -0
- package/.claude/skills/us-market-bubble-detector/CHANGELOG.md +118 -0
- package/.claude/skills/us-market-bubble-detector/SKILL.md +545 -0
- package/.claude/skills/us-market-bubble-detector/references/bubble_framework.md +335 -0
- package/.claude/skills/us-market-bubble-detector/references/historical_cases.md +327 -0
- package/.claude/skills/us-market-bubble-detector/references/implementation_guide.md +473 -0
- package/.claude/skills/us-market-bubble-detector/references/quick_reference.md +354 -0
- package/.claude/skills/us-market-bubble-detector/references/quick_reference_en.md +342 -0
- package/.claude/skills/us-market-bubble-detector/scripts/bubble_scorer.py +309 -0
- package/.claude/skills/us-stock-analysis/SKILL.md +294 -0
- package/.claude/skills/us-stock-analysis/references/financial-metrics.md +172 -0
- package/.claude/skills/us-stock-analysis/references/fundamental-analysis.md +129 -0
- package/.claude/skills/us-stock-analysis/references/report-template.md +207 -0
- package/.claude/skills/us-stock-analysis/references/technical-analysis.md +93 -0
- package/.claude/skills/value-dividend-screener/SKILL.md +562 -0
- package/.claude/skills/value-dividend-screener/references/fmp_api_guide.md +348 -0
- package/.claude/skills/value-dividend-screener/references/screening_methodology.md +315 -0
- package/.claude/skills/value-dividend-screener/scripts/screen_dividend_stocks.py +1138 -0
- package/.claude/skills/vcp-screener/SKILL.md +79 -0
- package/.claude/skills/vcp-screener/references/fmp_api_endpoints.md +45 -0
- package/.claude/skills/vcp-screener/references/scoring_system.md +154 -0
- package/.claude/skills/vcp-screener/references/vcp_methodology.md +124 -0
- package/.claude/skills/vcp-screener/scripts/calculators/__init__.py +1 -0
- package/.claude/skills/vcp-screener/scripts/calculators/pivot_proximity_calculator.py +139 -0
- package/.claude/skills/vcp-screener/scripts/calculators/relative_strength_calculator.py +161 -0
- package/.claude/skills/vcp-screener/scripts/calculators/trend_template_calculator.py +228 -0
- package/.claude/skills/vcp-screener/scripts/calculators/vcp_pattern_calculator.py +322 -0
- package/.claude/skills/vcp-screener/scripts/calculators/volume_pattern_calculator.py +121 -0
- package/.claude/skills/vcp-screener/scripts/fmp_client.py +162 -0
- package/.claude/skills/vcp-screener/scripts/report_generator.py +317 -0
- package/.claude/skills/vcp-screener/scripts/scorer.py +155 -0
- package/.claude/skills/vcp-screener/scripts/screen_vcp.py +536 -0
- package/.claude/skills/vcp-screener/scripts/tests/__init__.py +0 -0
- package/.claude/skills/vcp-screener/scripts/tests/conftest.py +9 -0
- package/.claude/skills/vcp-screener/scripts/tests/test_vcp_screener.py +834 -0
- package/.claude/skills/weekly-trade-strategy/.claude/agents/druckenmiller-strategy-planner.md +300 -0
- package/.claude/skills/weekly-trade-strategy/.claude/agents/market-news-analyzer.md +239 -0
- package/.claude/skills/weekly-trade-strategy/.claude/agents/technical-market-analyst.md +187 -0
- package/.claude/skills/weekly-trade-strategy/.claude/agents/us-market-analyst.md +218 -0
- package/.claude/skills/weekly-trade-strategy/.claude/agents/weekly-trade-blog-writer.md +318 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/breadth-chart-analyst/SKILL.md +662 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/breadth-chart-analyst/assets/SP500_Breadth_Index_200MA_8MA.jpeg +0 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/breadth-chart-analyst/assets/US_Stock_Market_Uptrend_Ratio.jpeg +0 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/breadth-chart-analyst/assets/breadth_analysis_template.md +558 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/breadth-chart-analyst/references/breadth_chart_methodology.md +590 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/earnings-calendar/SKILL.md +721 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/earnings-calendar/assets/earnings_report_template.md +102 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/earnings-calendar/earnings_calendar_2025-11-02.md +447 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/earnings-calendar/references/fmp_api_guide.md +590 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/earnings-calendar/scripts/fetch_earnings_fmp.py +443 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/earnings-calendar/scripts/generate_report.py +366 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/economic-calendar-fetcher/SKILL.md +365 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/economic-calendar-fetcher/references/fmp_api_documentation.md +345 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/economic-calendar-fetcher/scripts/get_economic_calendar.py +267 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/market-environment-analysis/SKILL.md +139 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/market-environment-analysis/references/analysis_patterns.md +124 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/market-environment-analysis/references/indicators.md +99 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/market-environment-analysis/scripts/market_utils.py +127 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/market-news-analyst/SKILL.md +714 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/market-news-analyst/references/corporate_news_impact.md +446 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/market-news-analyst/references/geopolitical_commodity_correlations.md +499 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/market-news-analyst/references/market_event_patterns.md +393 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/market-news-analyst/references/trusted_news_sources.md +510 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/sector-analyst/SKILL.md +206 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/sector-analyst/assets/industory_performance_1.jpeg +0 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/sector-analyst/assets/industory_performance_2.jpeg +0 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/sector-analyst/assets/sector_performance.jpeg +0 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/sector-analyst/references/sector_rotation.md +170 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/stanley-druckenmiller-investment/SKILL.md +84 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/stanley-druckenmiller-investment/references/case-studies.md +148 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/stanley-druckenmiller-investment/references/investment-philosophy.md +80 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/stanley-druckenmiller-investment/references/market-analysis-guide.md +146 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/technical-analyst/SKILL.md +238 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/technical-analyst/assets/analysis_template.md +183 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/technical-analyst/references/technical_analysis_framework.md +282 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/CHANGELOG.md +118 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/SKILL.md +545 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/references/bubble_framework.md +335 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/references/historical_cases.md +327 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/references/implementation_guide.md +473 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/references/quick_reference.md +354 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/references/quick_reference_en.md +342 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/scripts/bubble_scorer.py +309 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/us-stock-analysis/SKILL.md +294 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/us-stock-analysis/references/financial-metrics.md +172 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/us-stock-analysis/references/fundamental-analysis.md +129 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/us-stock-analysis/references/report-template.md +207 -0
- package/.claude/skills/weekly-trade-strategy/.claude/skills/us-stock-analysis/references/technical-analysis.md +93 -0
- package/.claude/skills/weekly-trade-strategy/CLAUDE.md +454 -0
- package/.claude/skills/weekly-trade-strategy/README.md +287 -0
- package/.claude/skills/weekly-trade-strategy/blogs/.gitkeep +0 -0
- package/.claude/skills/weekly-trade-strategy/charts/.gitkeep +0 -0
- package/.claude/skills/weekly-trade-strategy/earnings_data.json +10054 -0
- package/.claude/skills/weekly-trade-strategy/skills/breadth-chart-analyst/SKILL.md +662 -0
- package/.claude/skills/weekly-trade-strategy/skills/breadth-chart-analyst/assets/SP500_Breadth_Index_200MA_8MA.jpeg +0 -0
- package/.claude/skills/weekly-trade-strategy/skills/breadth-chart-analyst/assets/US_Stock_Market_Uptrend_Ratio.jpeg +0 -0
- package/.claude/skills/weekly-trade-strategy/skills/breadth-chart-analyst/assets/breadth_analysis_template.md +558 -0
- package/.claude/skills/weekly-trade-strategy/skills/breadth-chart-analyst/references/breadth_chart_methodology.md +590 -0
- package/.claude/skills/weekly-trade-strategy/skills/earnings-calendar/SKILL.md +721 -0
- package/.claude/skills/weekly-trade-strategy/skills/earnings-calendar/assets/earnings_report_template.md +102 -0
- package/.claude/skills/weekly-trade-strategy/skills/earnings-calendar/earnings_calendar_2025-11-02.md +447 -0
- package/.claude/skills/weekly-trade-strategy/skills/earnings-calendar/references/fmp_api_guide.md +590 -0
- package/.claude/skills/weekly-trade-strategy/skills/earnings-calendar/scripts/fetch_earnings_fmp.py +443 -0
- package/.claude/skills/weekly-trade-strategy/skills/earnings-calendar/scripts/generate_report.py +366 -0
- package/.claude/skills/weekly-trade-strategy/skills/economic-calendar-fetcher/SKILL.md +365 -0
- package/.claude/skills/weekly-trade-strategy/skills/economic-calendar-fetcher/references/fmp_api_documentation.md +345 -0
- package/.claude/skills/weekly-trade-strategy/skills/economic-calendar-fetcher/scripts/get_economic_calendar.py +267 -0
- package/.claude/skills/weekly-trade-strategy/skills/market-environment-analysis/SKILL.md +139 -0
- package/.claude/skills/weekly-trade-strategy/skills/market-environment-analysis/references/analysis_patterns.md +124 -0
- package/.claude/skills/weekly-trade-strategy/skills/market-environment-analysis/references/indicators.md +99 -0
- package/.claude/skills/weekly-trade-strategy/skills/market-environment-analysis/scripts/market_utils.py +127 -0
- package/.claude/skills/weekly-trade-strategy/skills/market-news-analyst/SKILL.md +714 -0
- package/.claude/skills/weekly-trade-strategy/skills/market-news-analyst/references/corporate_news_impact.md +446 -0
- package/.claude/skills/weekly-trade-strategy/skills/market-news-analyst/references/geopolitical_commodity_correlations.md +499 -0
- package/.claude/skills/weekly-trade-strategy/skills/market-news-analyst/references/market_event_patterns.md +393 -0
- package/.claude/skills/weekly-trade-strategy/skills/market-news-analyst/references/trusted_news_sources.md +510 -0
- package/.claude/skills/weekly-trade-strategy/skills/sector-analyst/SKILL.md +206 -0
- package/.claude/skills/weekly-trade-strategy/skills/sector-analyst/assets/industory_performance_1.jpeg +0 -0
- package/.claude/skills/weekly-trade-strategy/skills/sector-analyst/assets/industory_performance_2.jpeg +0 -0
- package/.claude/skills/weekly-trade-strategy/skills/sector-analyst/assets/sector_performance.jpeg +0 -0
- package/.claude/skills/weekly-trade-strategy/skills/sector-analyst/references/sector_rotation.md +170 -0
- package/.claude/skills/weekly-trade-strategy/skills/stanley-druckenmiller-investment/SKILL.md +84 -0
- package/.claude/skills/weekly-trade-strategy/skills/stanley-druckenmiller-investment/references/case-studies.md +148 -0
- package/.claude/skills/weekly-trade-strategy/skills/stanley-druckenmiller-investment/references/investment-philosophy.md +80 -0
- package/.claude/skills/weekly-trade-strategy/skills/stanley-druckenmiller-investment/references/market-analysis-guide.md +146 -0
- package/.claude/skills/weekly-trade-strategy/skills/technical-analyst/SKILL.md +238 -0
- package/.claude/skills/weekly-trade-strategy/skills/technical-analyst/assets/analysis_template.md +183 -0
- package/.claude/skills/weekly-trade-strategy/skills/technical-analyst/references/technical_analysis_framework.md +282 -0
- package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/CHANGELOG.md +118 -0
- package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/SKILL.md +545 -0
- package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/references/bubble_framework.md +335 -0
- package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/references/historical_cases.md +327 -0
- package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/references/implementation_guide.md +473 -0
- package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/references/quick_reference.md +354 -0
- package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/references/quick_reference_en.md +342 -0
- package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/scripts/bubble_scorer.py +309 -0
- package/.claude/skills/weekly-trade-strategy/skills/us-stock-analysis/SKILL.md +294 -0
- package/.claude/skills/weekly-trade-strategy/skills/us-stock-analysis/references/financial-metrics.md +172 -0
- package/.claude/skills/weekly-trade-strategy/skills/us-stock-analysis/references/fundamental-analysis.md +129 -0
- package/.claude/skills/weekly-trade-strategy/skills/us-stock-analysis/references/report-template.md +207 -0
- package/.claude/skills/weekly-trade-strategy/skills/us-stock-analysis/references/technical-analysis.md +93 -0
- package/.mcp.json +3 -0
- package/cli.mjs +16 -16
- package/package.json +4 -2
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Macro Regime Detector - Composite Scoring & Regime Classification
|
|
4
|
+
|
|
5
|
+
Combines 6 component transition signals into:
|
|
6
|
+
1. Weighted composite transition score (0-100)
|
|
7
|
+
2. Regime classification (5 regimes)
|
|
8
|
+
3. Transition probability assessment
|
|
9
|
+
|
|
10
|
+
Component Weights:
|
|
11
|
+
1. Market Concentration (RSP/SPY): 25%
|
|
12
|
+
2. Yield Curve (10Y-2Y): 20%
|
|
13
|
+
3. Credit Conditions (HYG/LQD): 15%
|
|
14
|
+
4. Size Factor (IWM/SPY): 15%
|
|
15
|
+
5. Equity-Bond (SPY/TLT+corr): 15%
|
|
16
|
+
6. Sector Rotation (XLY/XLP): 10%
|
|
17
|
+
Total: 100%
|
|
18
|
+
|
|
19
|
+
5 Regimes:
|
|
20
|
+
- Concentration: RSP/SPY↓, IWM/SPY↓, credit stable
|
|
21
|
+
- Broadening: RSP/SPY↑, IWM/SPY↑, credit stable/easing
|
|
22
|
+
- Contraction: Credit tight, XLY/XLP↓, SPY/TLT↓
|
|
23
|
+
- Inflationary: Stock-Bond positive corr, SPY/TLT↓
|
|
24
|
+
- Transitional: 3+ components signaling, unclear pattern
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from typing import Dict, List, Optional
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
COMPONENT_WEIGHTS = {
|
|
31
|
+
"concentration": 0.25,
|
|
32
|
+
"yield_curve": 0.20,
|
|
33
|
+
"credit_conditions": 0.15,
|
|
34
|
+
"size_factor": 0.15,
|
|
35
|
+
"equity_bond": 0.15,
|
|
36
|
+
"sector_rotation": 0.10,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
COMPONENT_LABELS = {
|
|
40
|
+
"concentration": "Market Concentration (RSP/SPY)",
|
|
41
|
+
"yield_curve": "Yield Curve (10Y-2Y)",
|
|
42
|
+
"credit_conditions": "Credit Conditions (HYG/LQD)",
|
|
43
|
+
"size_factor": "Size Factor (IWM/SPY)",
|
|
44
|
+
"equity_bond": "Equity-Bond (SPY/TLT)",
|
|
45
|
+
"sector_rotation": "Sector Rotation (XLY/XLP)",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def calculate_composite_score(component_scores: Dict[str, float],
|
|
50
|
+
data_availability: Optional[Dict[str, bool]] = None) -> Dict:
|
|
51
|
+
"""
|
|
52
|
+
Calculate weighted composite transition signal score.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
component_scores: Dict with keys matching COMPONENT_WEIGHTS, each 0-100
|
|
56
|
+
data_availability: Optional dict mapping component key -> bool
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Dict with composite_score, zone, guidance, component breakdown, data_quality
|
|
60
|
+
"""
|
|
61
|
+
if data_availability is None:
|
|
62
|
+
data_availability = {}
|
|
63
|
+
|
|
64
|
+
composite = 0.0
|
|
65
|
+
for key, weight in COMPONENT_WEIGHTS.items():
|
|
66
|
+
score = component_scores.get(key, 0)
|
|
67
|
+
composite += score * weight
|
|
68
|
+
|
|
69
|
+
composite = round(composite, 1)
|
|
70
|
+
|
|
71
|
+
# Identify strongest and weakest signals
|
|
72
|
+
valid_scores = {k: v for k, v in component_scores.items()
|
|
73
|
+
if k in COMPONENT_WEIGHTS}
|
|
74
|
+
|
|
75
|
+
if valid_scores:
|
|
76
|
+
strongest_signal = max(valid_scores, key=valid_scores.get)
|
|
77
|
+
weakest_signal = min(valid_scores, key=valid_scores.get)
|
|
78
|
+
else:
|
|
79
|
+
strongest_signal = "N/A"
|
|
80
|
+
weakest_signal = "N/A"
|
|
81
|
+
|
|
82
|
+
# Zone interpretation
|
|
83
|
+
zone_info = _interpret_zone(composite)
|
|
84
|
+
|
|
85
|
+
# Data quality
|
|
86
|
+
available_count = sum(
|
|
87
|
+
1 for k in COMPONENT_WEIGHTS
|
|
88
|
+
if data_availability.get(k, True)
|
|
89
|
+
)
|
|
90
|
+
total_components = len(COMPONENT_WEIGHTS)
|
|
91
|
+
missing_components = [
|
|
92
|
+
COMPONENT_LABELS[k] for k in COMPONENT_WEIGHTS
|
|
93
|
+
if not data_availability.get(k, True)
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
if available_count == total_components:
|
|
97
|
+
quality_label = f"Complete ({available_count}/{total_components} components)"
|
|
98
|
+
elif available_count >= total_components - 2:
|
|
99
|
+
quality_label = (f"Partial ({available_count}/{total_components} components)"
|
|
100
|
+
" - interpret with caution")
|
|
101
|
+
else:
|
|
102
|
+
quality_label = (f"Limited ({available_count}/{total_components} components)"
|
|
103
|
+
" - low confidence")
|
|
104
|
+
|
|
105
|
+
# Count components with significant signals
|
|
106
|
+
signaling_count = sum(1 for v in valid_scores.values() if v >= 40)
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
"composite_score": composite,
|
|
110
|
+
"zone": zone_info["zone"],
|
|
111
|
+
"zone_color": zone_info["color"],
|
|
112
|
+
"guidance": zone_info["guidance"],
|
|
113
|
+
"actions": zone_info["actions"],
|
|
114
|
+
"signaling_components": signaling_count,
|
|
115
|
+
"strongest_signal": {
|
|
116
|
+
"component": strongest_signal,
|
|
117
|
+
"label": COMPONENT_LABELS.get(strongest_signal, strongest_signal),
|
|
118
|
+
"score": valid_scores.get(strongest_signal, 0),
|
|
119
|
+
},
|
|
120
|
+
"weakest_signal": {
|
|
121
|
+
"component": weakest_signal,
|
|
122
|
+
"label": COMPONENT_LABELS.get(weakest_signal, weakest_signal),
|
|
123
|
+
"score": valid_scores.get(weakest_signal, 0),
|
|
124
|
+
},
|
|
125
|
+
"data_quality": {
|
|
126
|
+
"available_count": available_count,
|
|
127
|
+
"total_components": total_components,
|
|
128
|
+
"label": quality_label,
|
|
129
|
+
"missing_components": missing_components,
|
|
130
|
+
},
|
|
131
|
+
"component_scores": {
|
|
132
|
+
k: {
|
|
133
|
+
"score": component_scores.get(k, 0),
|
|
134
|
+
"weight": w,
|
|
135
|
+
"weighted_contribution": round(component_scores.get(k, 0) * w, 1),
|
|
136
|
+
"label": COMPONENT_LABELS[k],
|
|
137
|
+
}
|
|
138
|
+
for k, w in COMPONENT_WEIGHTS.items()
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def classify_regime(component_results: Dict[str, Dict]) -> Dict:
|
|
144
|
+
"""
|
|
145
|
+
Classify current macro regime based on component directions and signals.
|
|
146
|
+
|
|
147
|
+
Only components with data_available=True contribute to regime scoring.
|
|
148
|
+
Confidence is capped when data availability is limited.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
component_results: Dict of component key -> full result dict from each calculator
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Dict with current_regime, confidence, evidence, transition_probability
|
|
155
|
+
"""
|
|
156
|
+
# Extract directions and scores, treating unavailable data as neutral
|
|
157
|
+
conc = component_results.get("concentration", {})
|
|
158
|
+
yc = component_results.get("yield_curve", {})
|
|
159
|
+
credit = component_results.get("credit_conditions", {})
|
|
160
|
+
size = component_results.get("size_factor", {})
|
|
161
|
+
eb = component_results.get("equity_bond", {})
|
|
162
|
+
sector = component_results.get("sector_rotation", {})
|
|
163
|
+
|
|
164
|
+
# Count available components for confidence adjustment
|
|
165
|
+
available_count = sum(
|
|
166
|
+
1 for comp in [conc, yc, credit, size, eb, sector]
|
|
167
|
+
if comp.get("data_available", False)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Only use direction from components with available data;
|
|
171
|
+
# unavailable components default to "unknown" (neutral, no scoring)
|
|
172
|
+
def _dir(comp: Dict) -> str:
|
|
173
|
+
if not comp.get("data_available", False):
|
|
174
|
+
return "unknown"
|
|
175
|
+
return comp.get("direction", "unknown")
|
|
176
|
+
|
|
177
|
+
conc_dir = _dir(conc)
|
|
178
|
+
credit_dir = _dir(credit)
|
|
179
|
+
size_dir = _dir(size)
|
|
180
|
+
eb_dir = _dir(eb)
|
|
181
|
+
sector_dir = _dir(sector)
|
|
182
|
+
corr_regime = eb.get("correlation_regime", "unknown") if eb.get("data_available", False) else "unknown"
|
|
183
|
+
|
|
184
|
+
# Score each regime hypothesis
|
|
185
|
+
regime_scores = {
|
|
186
|
+
"concentration": _score_concentration_regime(conc_dir, size_dir, credit_dir),
|
|
187
|
+
"broadening": _score_broadening_regime(conc_dir, size_dir, credit_dir, sector_dir),
|
|
188
|
+
"contraction": _score_contraction_regime(credit_dir, sector_dir, eb_dir),
|
|
189
|
+
"inflationary": _score_inflationary_regime(corr_regime, eb_dir),
|
|
190
|
+
"transitional": 0, # Computed below
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# Count signaling components
|
|
194
|
+
all_scores = [
|
|
195
|
+
conc.get("score", 0), yc.get("score", 0), credit.get("score", 0),
|
|
196
|
+
size.get("score", 0), eb.get("score", 0), sector.get("score", 0),
|
|
197
|
+
]
|
|
198
|
+
signaling = sum(1 for s in all_scores if s >= 40)
|
|
199
|
+
|
|
200
|
+
# Find best matching regime (excluding transitional for ranking)
|
|
201
|
+
scored = {k: v for k, v in regime_scores.items() if k != "transitional"}
|
|
202
|
+
sorted_regimes = sorted(scored.items(), key=lambda x: x[1], reverse=True)
|
|
203
|
+
|
|
204
|
+
best_regime = sorted_regimes[0][0]
|
|
205
|
+
best_score = sorted_regimes[0][1]
|
|
206
|
+
|
|
207
|
+
# Tiebreak detection: top 2 regimes within 1 point
|
|
208
|
+
is_tied = (len(sorted_regimes) >= 2
|
|
209
|
+
and best_score > 0
|
|
210
|
+
and (best_score - sorted_regimes[1][1]) <= 1)
|
|
211
|
+
tied_regimes = ([sorted_regimes[0][0], sorted_regimes[1][0]]
|
|
212
|
+
if is_tied else None)
|
|
213
|
+
|
|
214
|
+
# Quick composite for tiebreak resolution
|
|
215
|
+
quick_composite = sum(
|
|
216
|
+
comp.get("score", 0) * COMPONENT_WEIGHTS[k]
|
|
217
|
+
for k, comp in component_results.items()
|
|
218
|
+
if k in COMPONENT_WEIGHTS
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Tiebreak + low composite → transitional
|
|
222
|
+
if is_tied and quick_composite < 50:
|
|
223
|
+
best_regime = "transitional"
|
|
224
|
+
regime_scores["transitional"] = best_score
|
|
225
|
+
|
|
226
|
+
# Fallback: multiple signals but no clear regime pattern
|
|
227
|
+
elif signaling >= 3 and best_score < 3:
|
|
228
|
+
best_regime = "transitional"
|
|
229
|
+
best_score = signaling
|
|
230
|
+
|
|
231
|
+
# Confidence level (capped by data availability)
|
|
232
|
+
if best_score >= 4:
|
|
233
|
+
confidence = "high"
|
|
234
|
+
elif best_score >= 3:
|
|
235
|
+
confidence = "moderate"
|
|
236
|
+
elif best_score >= 2:
|
|
237
|
+
confidence = "low"
|
|
238
|
+
else:
|
|
239
|
+
confidence = "very_low"
|
|
240
|
+
|
|
241
|
+
# Cap confidence when data is limited
|
|
242
|
+
if available_count <= 3:
|
|
243
|
+
# With 3 or fewer components, never report above "very_low"
|
|
244
|
+
confidence = "very_low"
|
|
245
|
+
elif available_count <= 4:
|
|
246
|
+
# With 4 components, cap at "low"
|
|
247
|
+
if confidence in ("high", "moderate"):
|
|
248
|
+
confidence = "low"
|
|
249
|
+
|
|
250
|
+
# Transition probability
|
|
251
|
+
transition_prob = _calculate_transition_probability(
|
|
252
|
+
signaling, all_scores, regime_scores, best_regime, sorted_regimes
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Evidence summary
|
|
256
|
+
evidence = _build_evidence(component_results)
|
|
257
|
+
|
|
258
|
+
# Regime description
|
|
259
|
+
regime_info = REGIME_DESCRIPTIONS.get(best_regime, {})
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
"current_regime": best_regime,
|
|
263
|
+
"regime_label": regime_info.get("label", best_regime),
|
|
264
|
+
"regime_description": regime_info.get("description", ""),
|
|
265
|
+
"confidence": confidence,
|
|
266
|
+
"regime_scores": regime_scores,
|
|
267
|
+
"signaling_components": signaling,
|
|
268
|
+
"transition_probability": transition_prob,
|
|
269
|
+
"portfolio_posture": regime_info.get("portfolio_posture", ""),
|
|
270
|
+
"evidence": evidence,
|
|
271
|
+
"tied_regimes": tied_regimes,
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
REGIME_DESCRIPTIONS = {
|
|
276
|
+
"concentration": {
|
|
277
|
+
"label": "Concentration",
|
|
278
|
+
"description": "Market leadership concentrated in mega-cap stocks. RSP/SPY declining, "
|
|
279
|
+
"small-caps underperforming. Credit conditions stable.",
|
|
280
|
+
"portfolio_posture": "Focus on mega-cap tech/growth leaders. "
|
|
281
|
+
"Underweight small-caps and value.",
|
|
282
|
+
},
|
|
283
|
+
"broadening": {
|
|
284
|
+
"label": "Broadening",
|
|
285
|
+
"description": "Market participation expanding. RSP/SPY rising, small-caps catching up. "
|
|
286
|
+
"Credit easing or stable.",
|
|
287
|
+
"portfolio_posture": "Add small-cap/mid-cap exposure. Equal-weight strategies. "
|
|
288
|
+
"Value and cyclical rotation.",
|
|
289
|
+
},
|
|
290
|
+
"contraction": {
|
|
291
|
+
"label": "Contraction",
|
|
292
|
+
"description": "Credit tightening, defensive rotation, equity-bond shift to risk-off. "
|
|
293
|
+
"Classic late-cycle deterioration.",
|
|
294
|
+
"portfolio_posture": "Raise cash. Increase Treasury allocation. "
|
|
295
|
+
"Defensive sectors (Staples, Utilities, Healthcare).",
|
|
296
|
+
},
|
|
297
|
+
"inflationary": {
|
|
298
|
+
"label": "Inflationary",
|
|
299
|
+
"description": "Positive stock-bond correlation regime. Both equities and bonds under "
|
|
300
|
+
"pressure. Traditional hedging breaks down.",
|
|
301
|
+
"portfolio_posture": "Real assets, commodities, energy. Short-duration bonds. "
|
|
302
|
+
"TIPS. Reduce long-duration exposure.",
|
|
303
|
+
},
|
|
304
|
+
"transitional": {
|
|
305
|
+
"label": "Transitional",
|
|
306
|
+
"description": "Multiple components signaling change but no clear regime pattern. "
|
|
307
|
+
"Market in flux between regimes.",
|
|
308
|
+
"portfolio_posture": "Increase diversification. Gradual repositioning. "
|
|
309
|
+
"Avoid concentrated bets. Monitor weekly for regime clarity.",
|
|
310
|
+
},
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _score_concentration_regime(conc_dir: str, size_dir: str, credit_dir: str) -> int:
|
|
315
|
+
"""Score evidence for Concentration regime.
|
|
316
|
+
|
|
317
|
+
Only scores when direction is known. 'unknown' (missing data) is neutral.
|
|
318
|
+
"""
|
|
319
|
+
score = 0
|
|
320
|
+
if conc_dir == "concentrating":
|
|
321
|
+
score += 2
|
|
322
|
+
if size_dir == "large_cap_leading":
|
|
323
|
+
score += 2
|
|
324
|
+
if credit_dir in ("stable", "easing"):
|
|
325
|
+
score += 1
|
|
326
|
+
return score
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _score_broadening_regime(conc_dir: str, size_dir: str,
|
|
330
|
+
credit_dir: str, sector_dir: str) -> int:
|
|
331
|
+
"""Score evidence for Broadening regime."""
|
|
332
|
+
score = 0
|
|
333
|
+
if conc_dir == "broadening":
|
|
334
|
+
score += 2
|
|
335
|
+
if size_dir == "small_cap_leading":
|
|
336
|
+
score += 2
|
|
337
|
+
if credit_dir in ("stable", "easing"):
|
|
338
|
+
score += 1
|
|
339
|
+
if sector_dir == "risk_on":
|
|
340
|
+
score += 1
|
|
341
|
+
return score
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _score_contraction_regime(credit_dir: str, sector_dir: str, eb_dir: str) -> int:
|
|
345
|
+
"""Score evidence for Contraction regime."""
|
|
346
|
+
score = 0
|
|
347
|
+
if credit_dir == "tightening":
|
|
348
|
+
score += 2
|
|
349
|
+
if sector_dir == "risk_off":
|
|
350
|
+
score += 2
|
|
351
|
+
if eb_dir == "risk_off":
|
|
352
|
+
score += 1
|
|
353
|
+
return score
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _score_inflationary_regime(corr_regime: str, eb_dir: str) -> int:
|
|
357
|
+
"""Score evidence for Inflationary regime."""
|
|
358
|
+
score = 0
|
|
359
|
+
if corr_regime == "positive":
|
|
360
|
+
score += 3
|
|
361
|
+
elif corr_regime == "near_zero":
|
|
362
|
+
score += 1
|
|
363
|
+
if eb_dir == "risk_off":
|
|
364
|
+
score += 1
|
|
365
|
+
return score
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _calculate_transition_probability(signaling: int, all_scores: List[float],
|
|
369
|
+
regime_scores: Dict[str, int],
|
|
370
|
+
current_regime: str,
|
|
371
|
+
sorted_regimes: Optional[List] = None) -> Dict:
|
|
372
|
+
"""
|
|
373
|
+
Calculate probability that a regime transition is underway.
|
|
374
|
+
|
|
375
|
+
Returns dict with probability level, supporting metrics, and transition direction.
|
|
376
|
+
"""
|
|
377
|
+
avg_score = sum(all_scores) / len(all_scores) if all_scores else 0
|
|
378
|
+
|
|
379
|
+
# High probability: many components signaling + high average score
|
|
380
|
+
if signaling >= 4 and avg_score >= 50:
|
|
381
|
+
prob_level = "high"
|
|
382
|
+
prob_pct = "70-90%"
|
|
383
|
+
elif signaling >= 3 and avg_score >= 40:
|
|
384
|
+
prob_level = "moderate"
|
|
385
|
+
prob_pct = "40-60%"
|
|
386
|
+
elif signaling >= 2 and avg_score >= 30:
|
|
387
|
+
prob_level = "low"
|
|
388
|
+
prob_pct = "20-40%"
|
|
389
|
+
else:
|
|
390
|
+
prob_level = "minimal"
|
|
391
|
+
prob_pct = "<20%"
|
|
392
|
+
|
|
393
|
+
# Check if second-best regime is close (ambiguity)
|
|
394
|
+
if sorted_regimes is None:
|
|
395
|
+
scored = {k: v for k, v in regime_scores.items() if k != "transitional"}
|
|
396
|
+
sorted_regimes = sorted(scored.items(), key=lambda x: x[1], reverse=True)
|
|
397
|
+
|
|
398
|
+
ambiguity = False
|
|
399
|
+
if len(sorted_regimes) >= 2:
|
|
400
|
+
best_val = sorted_regimes[0][1]
|
|
401
|
+
second_val = sorted_regimes[1][1]
|
|
402
|
+
if best_val > 0 and second_val >= best_val - 1:
|
|
403
|
+
ambiguity = True
|
|
404
|
+
|
|
405
|
+
# Transition direction
|
|
406
|
+
from_regime = None
|
|
407
|
+
to_regime = None
|
|
408
|
+
if prob_level in ("high", "moderate") and sorted_regimes:
|
|
409
|
+
top = sorted_regimes[0][0]
|
|
410
|
+
if current_regime == "transitional" and len(sorted_regimes) >= 2:
|
|
411
|
+
to_regime = top
|
|
412
|
+
from_regime = (sorted_regimes[1][0]
|
|
413
|
+
if sorted_regimes[1][1] > 0 else None)
|
|
414
|
+
elif top != current_regime:
|
|
415
|
+
from_regime = current_regime
|
|
416
|
+
to_regime = top
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
"level": prob_level,
|
|
420
|
+
"probability_range": prob_pct,
|
|
421
|
+
"signaling_count": signaling,
|
|
422
|
+
"avg_component_score": round(avg_score, 1),
|
|
423
|
+
"ambiguous": ambiguity,
|
|
424
|
+
"from_regime": from_regime,
|
|
425
|
+
"to_regime": to_regime,
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
REGIME_CONSISTENCY = {
|
|
430
|
+
"broadening": {
|
|
431
|
+
"concentration": "broadening",
|
|
432
|
+
"size_factor": "small_cap_leading",
|
|
433
|
+
"credit_conditions": ["stable", "easing"],
|
|
434
|
+
"sector_rotation": "risk_on",
|
|
435
|
+
"equity_bond": "risk_on",
|
|
436
|
+
},
|
|
437
|
+
"concentration": {
|
|
438
|
+
"concentration": "concentrating",
|
|
439
|
+
"size_factor": "large_cap_leading",
|
|
440
|
+
"credit_conditions": ["stable", "easing"],
|
|
441
|
+
},
|
|
442
|
+
"contraction": {
|
|
443
|
+
"credit_conditions": "tightening",
|
|
444
|
+
"sector_rotation": "risk_off",
|
|
445
|
+
"equity_bond": "risk_off",
|
|
446
|
+
},
|
|
447
|
+
"inflationary": {
|
|
448
|
+
"equity_bond": "risk_off",
|
|
449
|
+
},
|
|
450
|
+
"transitional": {},
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def check_regime_consistency(current_regime: str,
|
|
455
|
+
component_results: Dict[str, Dict]) -> Dict[str, str]:
|
|
456
|
+
"""
|
|
457
|
+
Check whether each component's direction is consistent with the classified regime.
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
Dict mapping component key -> "consistent" | "contradicting" | "neutral"
|
|
461
|
+
"""
|
|
462
|
+
expected = REGIME_CONSISTENCY.get(current_regime, {})
|
|
463
|
+
result = {}
|
|
464
|
+
|
|
465
|
+
for key in COMPONENT_WEIGHTS:
|
|
466
|
+
comp = component_results.get(key, {})
|
|
467
|
+
direction = comp.get("direction", "unknown")
|
|
468
|
+
|
|
469
|
+
if key not in expected:
|
|
470
|
+
result[key] = "neutral"
|
|
471
|
+
continue
|
|
472
|
+
|
|
473
|
+
exp_dir = expected[key]
|
|
474
|
+
if isinstance(exp_dir, list):
|
|
475
|
+
if direction in exp_dir:
|
|
476
|
+
result[key] = "consistent"
|
|
477
|
+
elif direction == "unknown":
|
|
478
|
+
result[key] = "neutral"
|
|
479
|
+
else:
|
|
480
|
+
result[key] = "contradicting"
|
|
481
|
+
else:
|
|
482
|
+
if direction == exp_dir:
|
|
483
|
+
result[key] = "consistent"
|
|
484
|
+
elif direction == "unknown":
|
|
485
|
+
result[key] = "neutral"
|
|
486
|
+
else:
|
|
487
|
+
result[key] = "contradicting"
|
|
488
|
+
|
|
489
|
+
return result
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def _build_evidence(component_results: Dict[str, Dict]) -> List[Dict]:
|
|
493
|
+
"""Build evidence list from component results."""
|
|
494
|
+
evidence = []
|
|
495
|
+
for key in COMPONENT_WEIGHTS:
|
|
496
|
+
comp = component_results.get(key, {})
|
|
497
|
+
if not comp:
|
|
498
|
+
continue
|
|
499
|
+
score = comp.get("score", 0)
|
|
500
|
+
if score >= 40:
|
|
501
|
+
evidence.append({
|
|
502
|
+
"component": COMPONENT_LABELS.get(key, key),
|
|
503
|
+
"score": score,
|
|
504
|
+
"signal": comp.get("signal", ""),
|
|
505
|
+
"direction": comp.get("direction", "unknown"),
|
|
506
|
+
})
|
|
507
|
+
# Sort by score descending
|
|
508
|
+
evidence.sort(key=lambda x: x["score"], reverse=True)
|
|
509
|
+
return evidence
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def _interpret_zone(composite: float) -> Dict:
|
|
513
|
+
"""Map composite score to transition signal zone."""
|
|
514
|
+
if composite <= 20:
|
|
515
|
+
return {
|
|
516
|
+
"zone": "Stable (No Transition)",
|
|
517
|
+
"color": "green",
|
|
518
|
+
"guidance": "All indicators stable. Current regime well-established.",
|
|
519
|
+
"actions": [
|
|
520
|
+
"Maintain current portfolio posture",
|
|
521
|
+
"Standard rebalancing schedule",
|
|
522
|
+
"Monitor monthly for early signals",
|
|
523
|
+
],
|
|
524
|
+
}
|
|
525
|
+
elif composite <= 40:
|
|
526
|
+
return {
|
|
527
|
+
"zone": "Early Signal (Monitoring)",
|
|
528
|
+
"color": "yellow",
|
|
529
|
+
"guidance": "Minor shifts detected. Worth monitoring but not actionable yet.",
|
|
530
|
+
"actions": [
|
|
531
|
+
"Increase monitoring frequency to bi-weekly",
|
|
532
|
+
"Review portfolio for regime sensitivity",
|
|
533
|
+
"Identify potential adjustment triggers",
|
|
534
|
+
"No position changes needed yet",
|
|
535
|
+
],
|
|
536
|
+
}
|
|
537
|
+
elif composite <= 60:
|
|
538
|
+
return {
|
|
539
|
+
"zone": "Transition Zone (Preparing)",
|
|
540
|
+
"color": "orange",
|
|
541
|
+
"guidance": "Multiple indicators in transition. Begin planning portfolio adjustment.",
|
|
542
|
+
"actions": [
|
|
543
|
+
"Develop regime-change response plan",
|
|
544
|
+
"Begin gradual diversification shifts",
|
|
545
|
+
"Reduce concentrated bets",
|
|
546
|
+
"Identify new regime beneficiaries",
|
|
547
|
+
"Weekly monitoring",
|
|
548
|
+
],
|
|
549
|
+
}
|
|
550
|
+
elif composite <= 80:
|
|
551
|
+
return {
|
|
552
|
+
"zone": "Active Transition (Repositioning)",
|
|
553
|
+
"color": "red",
|
|
554
|
+
"guidance": "Clear regime transition underway. Execute repositioning plan.",
|
|
555
|
+
"actions": [
|
|
556
|
+
"Execute planned portfolio adjustments",
|
|
557
|
+
"Rotate toward new regime beneficiaries",
|
|
558
|
+
"Reduce exposure to old regime leaders",
|
|
559
|
+
"Increase hedging if contracting/inflationary",
|
|
560
|
+
"Daily monitoring of confirmation signals",
|
|
561
|
+
],
|
|
562
|
+
}
|
|
563
|
+
else:
|
|
564
|
+
return {
|
|
565
|
+
"zone": "Confirmed Transition (Completing)",
|
|
566
|
+
"color": "critical",
|
|
567
|
+
"guidance": "Strong confirmed transition. Complete repositioning.",
|
|
568
|
+
"actions": [
|
|
569
|
+
"Finalize portfolio repositioning",
|
|
570
|
+
"Full allocation to new regime posture",
|
|
571
|
+
"Monitor for transition exhaustion signals",
|
|
572
|
+
"Prepare for new regime stability",
|
|
573
|
+
],
|
|
574
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Shared fixtures for Macro Regime Detector tests"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
# Add scripts directory to path so calculators can be imported
|
|
7
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
8
|
+
# Add tests directory to path so test helpers can be imported
|
|
9
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Tests for Market Concentration Calculator (RSP/SPY)"""
|
|
2
|
+
|
|
3
|
+
from calculators.concentration_calculator import calculate_concentration
|
|
4
|
+
from test_helpers import make_monthly_history
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestCalculateConcentration:
|
|
8
|
+
def test_insufficient_data_empty(self):
|
|
9
|
+
result = calculate_concentration([], [])
|
|
10
|
+
assert result["score"] == 0
|
|
11
|
+
assert result["data_available"] is False
|
|
12
|
+
|
|
13
|
+
def test_insufficient_data_too_short(self):
|
|
14
|
+
rsp = make_monthly_history([100] * 6, start_year=2025)
|
|
15
|
+
spy = make_monthly_history([100] * 6, start_year=2025)
|
|
16
|
+
result = calculate_concentration(rsp, spy)
|
|
17
|
+
assert result["data_available"] is False
|
|
18
|
+
|
|
19
|
+
def test_stable_ratio_low_score(self):
|
|
20
|
+
# Flat ratio = no transition signal
|
|
21
|
+
rsp = make_monthly_history([100] * 24, start_year=2024)
|
|
22
|
+
spy = make_monthly_history([100] * 24, start_year=2024)
|
|
23
|
+
result = calculate_concentration(rsp, spy)
|
|
24
|
+
assert result["data_available"] is True
|
|
25
|
+
assert result["score"] <= 30 # Small noise from daily variation is expected
|
|
26
|
+
assert result["current_ratio"] is not None
|
|
27
|
+
|
|
28
|
+
def test_rising_rsp_spy_broadening(self):
|
|
29
|
+
# RSP rising faster than SPY = broadening
|
|
30
|
+
# Create a clear uptrend in RSP relative to SPY
|
|
31
|
+
rsp_closes = [100 + i * 2 for i in range(24)] # Rises from 100 to 146
|
|
32
|
+
spy_closes = [100 + i * 0.5 for i in range(24)] # Rises from 100 to 111.5
|
|
33
|
+
rsp = make_monthly_history(rsp_closes, start_year=2024)
|
|
34
|
+
spy = make_monthly_history(spy_closes, start_year=2024)
|
|
35
|
+
result = calculate_concentration(rsp, spy)
|
|
36
|
+
assert result["data_available"] is True
|
|
37
|
+
assert result["monthly_points"] >= 12
|
|
38
|
+
|
|
39
|
+
def test_declining_rsp_spy_concentrating(self):
|
|
40
|
+
# SPY rising faster than RSP = concentrating
|
|
41
|
+
rsp_closes = [100 + i * 0.5 for i in range(24)]
|
|
42
|
+
spy_closes = [100 + i * 2 for i in range(24)]
|
|
43
|
+
rsp = make_monthly_history(rsp_closes, start_year=2024)
|
|
44
|
+
spy = make_monthly_history(spy_closes, start_year=2024)
|
|
45
|
+
result = calculate_concentration(rsp, spy)
|
|
46
|
+
assert result["data_available"] is True
|
|
47
|
+
|
|
48
|
+
def test_crossover_detected(self):
|
|
49
|
+
# Create a ratio that crosses over (death_cross then reverses)
|
|
50
|
+
# First 12 months: RSP/SPY declining, next 12 months: recovering
|
|
51
|
+
rsp_closes = [120 - i for i in range(12)] + [108 + i * 2 for i in range(12)]
|
|
52
|
+
spy_closes = [100] * 24
|
|
53
|
+
rsp = make_monthly_history(rsp_closes, start_year=2024)
|
|
54
|
+
spy = make_monthly_history(spy_closes, start_year=2024)
|
|
55
|
+
result = calculate_concentration(rsp, spy)
|
|
56
|
+
assert result["data_available"] is True
|
|
57
|
+
assert result["crossover"]["type"] in ("golden_cross", "death_cross", "converging", "none")
|
|
58
|
+
|
|
59
|
+
def test_output_structure(self):
|
|
60
|
+
rsp = make_monthly_history([100 + i for i in range(24)], start_year=2024)
|
|
61
|
+
spy = make_monthly_history([100] * 24, start_year=2024)
|
|
62
|
+
result = calculate_concentration(rsp, spy)
|
|
63
|
+
|
|
64
|
+
# Verify required keys
|
|
65
|
+
assert "score" in result
|
|
66
|
+
assert "signal" in result
|
|
67
|
+
assert "data_available" in result
|
|
68
|
+
assert "direction" in result
|
|
69
|
+
assert "current_ratio" in result
|
|
70
|
+
assert "sma_6m" in result
|
|
71
|
+
assert "sma_12m" in result
|
|
72
|
+
assert "roc_3m" in result
|
|
73
|
+
assert "roc_12m" in result
|
|
74
|
+
assert "percentile" in result
|
|
75
|
+
assert "crossover" in result
|
|
76
|
+
assert "monthly_points" in result
|
|
77
|
+
|
|
78
|
+
assert 0 <= result["score"] <= 100
|