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,347 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Shared utility functions for macro regime calculators.
|
|
4
|
+
|
|
5
|
+
Provides monthly downsampling, ratio calculation, moving averages,
|
|
6
|
+
crossover detection, momentum computation, and transition scoring.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Dict, List, Optional, Tuple
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def downsample_to_monthly(daily_history: List[Dict]) -> List[Dict]:
|
|
14
|
+
"""
|
|
15
|
+
Downsample daily OHLCV to monthly (last business day of each month).
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
daily_history: Daily bars, most recent first.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
List of monthly bars (most recent first), each with 'date', 'close'.
|
|
22
|
+
"""
|
|
23
|
+
if not daily_history:
|
|
24
|
+
return []
|
|
25
|
+
|
|
26
|
+
# Group by year-month, pick the most recent bar per month
|
|
27
|
+
monthly = {}
|
|
28
|
+
for bar in daily_history:
|
|
29
|
+
date_str = bar.get("date", "")
|
|
30
|
+
close = bar.get("adjClose", bar.get("close", 0))
|
|
31
|
+
if not date_str or close == 0:
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
# Extract year-month key
|
|
35
|
+
ym = date_str[:7] # "YYYY-MM"
|
|
36
|
+
if ym not in monthly:
|
|
37
|
+
monthly[ym] = {"date": date_str, "close": close}
|
|
38
|
+
else:
|
|
39
|
+
# daily_history is most recent first, so first occurrence is the latest in that month
|
|
40
|
+
# Keep the first (most recent) bar for each month
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
# Sort by date descending (most recent first)
|
|
44
|
+
result = sorted(monthly.values(), key=lambda x: x["date"], reverse=True)
|
|
45
|
+
return result
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def calculate_ratio(numerator_monthly: List[Dict],
|
|
49
|
+
denominator_monthly: List[Dict]) -> List[Dict]:
|
|
50
|
+
"""
|
|
51
|
+
Calculate ratio of two monthly series aligned by date.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
numerator_monthly: Monthly bars for numerator (most recent first)
|
|
55
|
+
denominator_monthly: Monthly bars for denominator (most recent first)
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
List of {'date': str, 'value': float} (most recent first)
|
|
59
|
+
"""
|
|
60
|
+
# Build lookup by year-month
|
|
61
|
+
denom_lookup = {}
|
|
62
|
+
for bar in denominator_monthly:
|
|
63
|
+
ym = bar["date"][:7]
|
|
64
|
+
denom_lookup[ym] = bar["close"]
|
|
65
|
+
|
|
66
|
+
result = []
|
|
67
|
+
for bar in numerator_monthly:
|
|
68
|
+
ym = bar["date"][:7]
|
|
69
|
+
if ym in denom_lookup and denom_lookup[ym] != 0:
|
|
70
|
+
ratio = bar["close"] / denom_lookup[ym]
|
|
71
|
+
result.append({"date": bar["date"], "value": ratio})
|
|
72
|
+
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def compute_sma(values: List[float], period: int) -> Optional[float]:
|
|
77
|
+
"""
|
|
78
|
+
Compute Simple Moving Average from a list of values (most recent first).
|
|
79
|
+
|
|
80
|
+
Returns SMA of the most recent `period` values, or None if insufficient data.
|
|
81
|
+
"""
|
|
82
|
+
if len(values) < period:
|
|
83
|
+
return None
|
|
84
|
+
return sum(values[:period]) / period
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def detect_crossover(values: List[float], short_period: int = 6,
|
|
88
|
+
long_period: int = 12) -> Dict:
|
|
89
|
+
"""
|
|
90
|
+
Detect SMA crossover between short and long periods.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
values: Series of values (most recent first)
|
|
94
|
+
short_period: Short-term SMA period (default 6)
|
|
95
|
+
long_period: Long-term SMA period (default 12)
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Dict with 'type' ('golden_cross', 'death_cross', 'converging', 'none'),
|
|
99
|
+
'bars_ago' (how many months ago the crossover occurred),
|
|
100
|
+
'gap_pct' (current gap between short and long SMA as %)
|
|
101
|
+
"""
|
|
102
|
+
if len(values) < long_period + 3:
|
|
103
|
+
return {"type": "none", "bars_ago": None, "gap_pct": None}
|
|
104
|
+
|
|
105
|
+
# Compute SMAs at each point
|
|
106
|
+
max_lookback = min(len(values), long_period + 12) # Check up to 12 months back
|
|
107
|
+
sma_pairs = []
|
|
108
|
+
for offset in range(max_lookback - long_period + 1):
|
|
109
|
+
subset = values[offset:]
|
|
110
|
+
short_sma = compute_sma(subset, short_period)
|
|
111
|
+
long_sma = compute_sma(subset, long_period)
|
|
112
|
+
if short_sma is not None and long_sma is not None:
|
|
113
|
+
sma_pairs.append((short_sma, long_sma))
|
|
114
|
+
|
|
115
|
+
if len(sma_pairs) < 2:
|
|
116
|
+
return {"type": "none", "bars_ago": None, "gap_pct": None}
|
|
117
|
+
|
|
118
|
+
# Current gap
|
|
119
|
+
current_short, current_long = sma_pairs[0]
|
|
120
|
+
if current_long != 0:
|
|
121
|
+
gap_pct = (current_short - current_long) / current_long * 100
|
|
122
|
+
else:
|
|
123
|
+
gap_pct = 0
|
|
124
|
+
|
|
125
|
+
# Look for crossover
|
|
126
|
+
for i in range(1, len(sma_pairs)):
|
|
127
|
+
prev_short, prev_long = sma_pairs[i]
|
|
128
|
+
curr_short, curr_long = sma_pairs[i - 1]
|
|
129
|
+
|
|
130
|
+
# Golden cross: short crosses above long
|
|
131
|
+
if prev_short <= prev_long and curr_short > curr_long:
|
|
132
|
+
return {
|
|
133
|
+
"type": "golden_cross",
|
|
134
|
+
"bars_ago": i - 1,
|
|
135
|
+
"gap_pct": round(gap_pct, 3),
|
|
136
|
+
}
|
|
137
|
+
# Death cross: short crosses below long
|
|
138
|
+
if prev_short >= prev_long and curr_short < curr_long:
|
|
139
|
+
return {
|
|
140
|
+
"type": "death_cross",
|
|
141
|
+
"bars_ago": i - 1,
|
|
142
|
+
"gap_pct": round(gap_pct, 3),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# No crossover found, check if converging
|
|
146
|
+
if abs(gap_pct) < 1.0:
|
|
147
|
+
return {
|
|
148
|
+
"type": "converging",
|
|
149
|
+
"bars_ago": None,
|
|
150
|
+
"gap_pct": round(gap_pct, 3),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
"type": "none",
|
|
155
|
+
"bars_ago": None,
|
|
156
|
+
"gap_pct": round(gap_pct, 3),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def compute_roc(values: List[float], period: int) -> Optional[float]:
|
|
161
|
+
"""
|
|
162
|
+
Compute Rate of Change (%) over `period` data points.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
values: Series (most recent first)
|
|
166
|
+
period: Number of periods back to compare
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
ROC as percentage, or None if insufficient data
|
|
170
|
+
"""
|
|
171
|
+
if len(values) <= period:
|
|
172
|
+
return None
|
|
173
|
+
current = values[0]
|
|
174
|
+
past = values[period]
|
|
175
|
+
if past == 0:
|
|
176
|
+
return None
|
|
177
|
+
return (current - past) / past * 100
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def compute_percentile(values: List[float], current: float) -> Optional[float]:
|
|
181
|
+
"""
|
|
182
|
+
Compute percentile rank of current value within the series.
|
|
183
|
+
|
|
184
|
+
Returns percentile (0-100), or None if insufficient data.
|
|
185
|
+
"""
|
|
186
|
+
if not values:
|
|
187
|
+
return None
|
|
188
|
+
below = sum(1 for v in values if v < current)
|
|
189
|
+
return below / len(values) * 100
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def compute_rolling_correlation(series_a: List[float], series_b: List[float],
|
|
193
|
+
window: int) -> Optional[float]:
|
|
194
|
+
"""
|
|
195
|
+
Compute rolling Pearson correlation between two series over a window.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
series_a: First series (most recent first)
|
|
199
|
+
series_b: Second series (most recent first)
|
|
200
|
+
window: Number of data points for correlation window
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Correlation coefficient (-1 to 1), or None if insufficient data
|
|
204
|
+
"""
|
|
205
|
+
if len(series_a) < window or len(series_b) < window:
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
a = series_a[:window]
|
|
209
|
+
b = series_b[:window]
|
|
210
|
+
|
|
211
|
+
n = window
|
|
212
|
+
mean_a = sum(a) / n
|
|
213
|
+
mean_b = sum(b) / n
|
|
214
|
+
|
|
215
|
+
cov = sum((a[i] - mean_a) * (b[i] - mean_b) for i in range(n)) / n
|
|
216
|
+
std_a = (sum((x - mean_a) ** 2 for x in a) / n) ** 0.5
|
|
217
|
+
std_b = (sum((x - mean_b) ** 2 for x in b) / n) ** 0.5
|
|
218
|
+
|
|
219
|
+
if std_a == 0 or std_b == 0:
|
|
220
|
+
return 0.0
|
|
221
|
+
|
|
222
|
+
return cov / (std_a * std_b)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
STALE_CROSSOVER_MONTHS = 3
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def determine_direction(crossover: Dict, roc_3m: Optional[float],
|
|
229
|
+
positive_label: str, negative_label: str,
|
|
230
|
+
neutral_label: str = "neutral") -> Tuple[str, str]:
|
|
231
|
+
"""
|
|
232
|
+
Determine direction from crossover and momentum, accounting for stale crossovers.
|
|
233
|
+
|
|
234
|
+
When a crossover is old (>= STALE_CROSSOVER_MONTHS) and momentum contradicts it,
|
|
235
|
+
momentum takes priority ("reversing"). Recent crossovers always win.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
crossover: Dict with 'type' and 'bars_ago'
|
|
239
|
+
roc_3m: 3-month rate of change (%)
|
|
240
|
+
positive_label: Label for positive direction (e.g., "risk_on")
|
|
241
|
+
negative_label: Label for negative direction (e.g., "risk_off")
|
|
242
|
+
neutral_label: Label when no signal (default "neutral")
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Tuple of (direction, momentum_qualifier)
|
|
246
|
+
momentum_qualifier: "confirmed" | "fading" | "reversing" | "N/A"
|
|
247
|
+
"""
|
|
248
|
+
cross_type = crossover.get("type", "none")
|
|
249
|
+
bars_ago = crossover.get("bars_ago")
|
|
250
|
+
is_stale = bars_ago is not None and bars_ago >= STALE_CROSSOVER_MONTHS
|
|
251
|
+
|
|
252
|
+
# Crossover direction
|
|
253
|
+
cross_dir = (positive_label if cross_type == "golden_cross"
|
|
254
|
+
else negative_label if cross_type == "death_cross"
|
|
255
|
+
else None)
|
|
256
|
+
|
|
257
|
+
# Momentum direction
|
|
258
|
+
mom_dir = (positive_label if roc_3m is not None and roc_3m > 0
|
|
259
|
+
else negative_label if roc_3m is not None and roc_3m < 0
|
|
260
|
+
else None)
|
|
261
|
+
|
|
262
|
+
if cross_dir:
|
|
263
|
+
if is_stale and mom_dir and mom_dir != cross_dir:
|
|
264
|
+
return mom_dir, "reversing"
|
|
265
|
+
qualifier = ("confirmed" if mom_dir == cross_dir
|
|
266
|
+
else "fading" if mom_dir and mom_dir != cross_dir
|
|
267
|
+
else "N/A")
|
|
268
|
+
return cross_dir, qualifier
|
|
269
|
+
elif mom_dir:
|
|
270
|
+
return mom_dir, "N/A"
|
|
271
|
+
else:
|
|
272
|
+
return neutral_label, "N/A"
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def score_transition_signal(crossover: Dict,
|
|
276
|
+
roc_short: Optional[float],
|
|
277
|
+
roc_long: Optional[float],
|
|
278
|
+
sma_short: Optional[float],
|
|
279
|
+
sma_long: Optional[float]) -> int:
|
|
280
|
+
"""
|
|
281
|
+
Score transition signal strength (0-100) from crossover, momentum, and MA data.
|
|
282
|
+
|
|
283
|
+
Scoring layers:
|
|
284
|
+
1. MA Crossover (0-40 base): Recent crossover = high, converging = moderate
|
|
285
|
+
2. Momentum Shift (0-30): Short ROC reversal against long trend = early warning
|
|
286
|
+
3. Confirmation (0-30): Multiple signals aligning = strong confirmation
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
int score 0-100
|
|
290
|
+
"""
|
|
291
|
+
score = 0
|
|
292
|
+
|
|
293
|
+
# Layer 1: Crossover detection (0-40)
|
|
294
|
+
cross_type = crossover.get("type", "none")
|
|
295
|
+
bars_ago = crossover.get("bars_ago")
|
|
296
|
+
gap_pct = crossover.get("gap_pct", 0) or 0
|
|
297
|
+
|
|
298
|
+
if cross_type in ("golden_cross", "death_cross"):
|
|
299
|
+
if bars_ago is not None and bars_ago <= 2:
|
|
300
|
+
score += 40 # Very recent crossover
|
|
301
|
+
elif bars_ago is not None and bars_ago <= 5:
|
|
302
|
+
score += 30 # Recent crossover
|
|
303
|
+
else:
|
|
304
|
+
score += 20 # Older crossover
|
|
305
|
+
elif cross_type == "converging":
|
|
306
|
+
# MAs getting close, crossover possible
|
|
307
|
+
closeness = max(0, 1.0 - abs(gap_pct)) * 25
|
|
308
|
+
score += int(closeness)
|
|
309
|
+
# "none" = stable, no points
|
|
310
|
+
|
|
311
|
+
# Layer 2: Momentum shift (0-30)
|
|
312
|
+
if roc_short is not None and roc_long is not None:
|
|
313
|
+
# Reversal: short-term momentum opposite to long-term trend
|
|
314
|
+
if roc_long < 0 and roc_short > 0:
|
|
315
|
+
# Declining long-term but short-term bouncing = early reversal
|
|
316
|
+
strength = min(abs(roc_short), 5.0) / 5.0 * 30
|
|
317
|
+
score += int(strength)
|
|
318
|
+
elif roc_long > 0 and roc_short < 0:
|
|
319
|
+
# Rising long-term but short-term declining = early reversal
|
|
320
|
+
strength = min(abs(roc_short), 5.0) / 5.0 * 30
|
|
321
|
+
score += int(strength)
|
|
322
|
+
elif abs(roc_short) > 3.0:
|
|
323
|
+
# Strong short-term momentum in same direction = acceleration
|
|
324
|
+
score += 10
|
|
325
|
+
|
|
326
|
+
# Layer 3: Confirmation / alignment (0-30)
|
|
327
|
+
signals_aligned = 0
|
|
328
|
+
|
|
329
|
+
# Signal 1: Crossover present
|
|
330
|
+
if cross_type in ("golden_cross", "death_cross"):
|
|
331
|
+
signals_aligned += 1
|
|
332
|
+
|
|
333
|
+
# Signal 2: Short ROC confirms direction
|
|
334
|
+
if cross_type == "golden_cross" and roc_short is not None and roc_short > 0:
|
|
335
|
+
signals_aligned += 1
|
|
336
|
+
elif cross_type == "death_cross" and roc_short is not None and roc_short < 0:
|
|
337
|
+
signals_aligned += 1
|
|
338
|
+
|
|
339
|
+
# Signal 3: SMA gap widening (momentum)
|
|
340
|
+
if sma_short is not None and sma_long is not None and sma_long != 0:
|
|
341
|
+
current_gap = abs(sma_short - sma_long) / sma_long * 100
|
|
342
|
+
if current_gap > 0.5:
|
|
343
|
+
signals_aligned += 1
|
|
344
|
+
|
|
345
|
+
score += signals_aligned * 10
|
|
346
|
+
|
|
347
|
+
return min(100, max(0, score))
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Component 2: Yield Curve (Weight: 20%)
|
|
4
|
+
|
|
5
|
+
Analyzes 10Y-2Y Treasury spread to detect interest rate cycle transitions.
|
|
6
|
+
|
|
7
|
+
Primary data: Treasury API (10Y-2Y spread directly)
|
|
8
|
+
Fallback: SHY/TLT ratio as proxy for yield curve shape
|
|
9
|
+
|
|
10
|
+
Transition signals:
|
|
11
|
+
- Inversion → Normalization: Often precedes recession end, risk-on shift
|
|
12
|
+
- Steepening from flat: Economic recovery signal
|
|
13
|
+
- Flattening from steep: Late-cycle tightening signal
|
|
14
|
+
- Deep inversion: Recession warning
|
|
15
|
+
|
|
16
|
+
Scoring (0-100 = Transition Signal Strength):
|
|
17
|
+
0-20: Stable yield curve regime
|
|
18
|
+
20-40: Minor changes in spread
|
|
19
|
+
40-60: Transition zone (curve shape changing)
|
|
20
|
+
60-80: Clear transition (inversion/normalization crossover)
|
|
21
|
+
80-100: Strong confirmed transition
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from typing import Dict, List, Optional
|
|
25
|
+
from .utils import (
|
|
26
|
+
downsample_to_monthly,
|
|
27
|
+
calculate_ratio,
|
|
28
|
+
compute_sma,
|
|
29
|
+
detect_crossover,
|
|
30
|
+
compute_roc,
|
|
31
|
+
compute_percentile,
|
|
32
|
+
score_transition_signal,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def calculate_yield_curve(treasury_rates: Optional[List[Dict]] = None,
|
|
37
|
+
shy_history: Optional[List[Dict]] = None,
|
|
38
|
+
tlt_history: Optional[List[Dict]] = None) -> Dict:
|
|
39
|
+
"""
|
|
40
|
+
Calculate yield curve transition signal.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
treasury_rates: Treasury rate data from FMP stable API (most recent first)
|
|
44
|
+
shy_history: SHY daily OHLCV (fallback, most recent first)
|
|
45
|
+
tlt_history: TLT daily OHLCV (fallback, most recent first)
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Dict with score, signal, spread details, curve state
|
|
49
|
+
"""
|
|
50
|
+
# Try Treasury API first
|
|
51
|
+
if treasury_rates:
|
|
52
|
+
result = _analyze_treasury_spread(treasury_rates)
|
|
53
|
+
if result is not None:
|
|
54
|
+
return result
|
|
55
|
+
|
|
56
|
+
# Fallback to SHY/TLT ratio
|
|
57
|
+
if shy_history and tlt_history:
|
|
58
|
+
return _analyze_shy_tlt_proxy(shy_history, tlt_history)
|
|
59
|
+
|
|
60
|
+
return _insufficient_data("No treasury rates or SHY/TLT data available")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _analyze_treasury_spread(treasury_rates: List[Dict]) -> Optional[Dict]:
|
|
64
|
+
"""Analyze 10Y-2Y spread from Treasury API data."""
|
|
65
|
+
# Extract 10Y-2Y spread series
|
|
66
|
+
spread_monthly = {}
|
|
67
|
+
for entry in treasury_rates:
|
|
68
|
+
date_str = entry.get("date", "")
|
|
69
|
+
year10 = entry.get("year10")
|
|
70
|
+
year2 = entry.get("year2")
|
|
71
|
+
|
|
72
|
+
if not date_str or year10 is None or year2 is None:
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
y10 = float(year10)
|
|
77
|
+
y2 = float(year2)
|
|
78
|
+
except (ValueError, TypeError):
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
ym = date_str[:7]
|
|
82
|
+
if ym not in spread_monthly:
|
|
83
|
+
spread_monthly[ym] = {
|
|
84
|
+
"date": date_str,
|
|
85
|
+
"spread": y10 - y2,
|
|
86
|
+
"year10": y10,
|
|
87
|
+
"year2": y2,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if len(spread_monthly) < 12:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
# Sort most recent first
|
|
94
|
+
spread_series = sorted(spread_monthly.values(), key=lambda x: x["date"], reverse=True)
|
|
95
|
+
spread_values = [s["spread"] for s in spread_series]
|
|
96
|
+
|
|
97
|
+
current_spread = spread_values[0]
|
|
98
|
+
current_date = spread_series[0]["date"]
|
|
99
|
+
current_10y = spread_series[0]["year10"]
|
|
100
|
+
current_2y = spread_series[0]["year2"]
|
|
101
|
+
|
|
102
|
+
# Compute SMAs
|
|
103
|
+
sma_6m = compute_sma(spread_values, 6)
|
|
104
|
+
sma_12m = compute_sma(spread_values, 12)
|
|
105
|
+
|
|
106
|
+
# Crossover detection
|
|
107
|
+
crossover = detect_crossover(spread_values, short_period=6, long_period=12)
|
|
108
|
+
|
|
109
|
+
# Momentum
|
|
110
|
+
roc_3m = compute_roc(spread_values, 3)
|
|
111
|
+
roc_12m = compute_roc(spread_values, 12)
|
|
112
|
+
|
|
113
|
+
# Percentile
|
|
114
|
+
percentile = compute_percentile(spread_values, current_spread)
|
|
115
|
+
|
|
116
|
+
# Score
|
|
117
|
+
score = score_transition_signal(
|
|
118
|
+
crossover=crossover,
|
|
119
|
+
roc_short=roc_3m,
|
|
120
|
+
roc_long=roc_12m,
|
|
121
|
+
sma_short=sma_6m,
|
|
122
|
+
sma_long=sma_12m,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Curve state
|
|
126
|
+
curve_state = _classify_curve_state(current_spread, sma_6m, roc_3m)
|
|
127
|
+
|
|
128
|
+
# Direction of transition
|
|
129
|
+
if roc_3m is not None and roc_3m > 0:
|
|
130
|
+
direction = "steepening"
|
|
131
|
+
elif roc_3m is not None and roc_3m < 0:
|
|
132
|
+
direction = "flattening"
|
|
133
|
+
else:
|
|
134
|
+
direction = "stable"
|
|
135
|
+
|
|
136
|
+
signal = _describe_signal(score, curve_state, current_spread, direction)
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
"score": score,
|
|
140
|
+
"signal": signal,
|
|
141
|
+
"data_available": True,
|
|
142
|
+
"data_source": "treasury_api",
|
|
143
|
+
"direction": direction,
|
|
144
|
+
"curve_state": curve_state,
|
|
145
|
+
"current_spread": round(current_spread, 3),
|
|
146
|
+
"current_10y": current_10y,
|
|
147
|
+
"current_2y": current_2y,
|
|
148
|
+
"current_date": current_date,
|
|
149
|
+
"sma_6m": round(sma_6m, 3) if sma_6m is not None else None,
|
|
150
|
+
"sma_12m": round(sma_12m, 3) if sma_12m is not None else None,
|
|
151
|
+
"roc_3m": round(roc_3m, 2) if roc_3m is not None else None,
|
|
152
|
+
"roc_12m": round(roc_12m, 2) if roc_12m is not None else None,
|
|
153
|
+
"percentile": round(percentile, 1) if percentile is not None else None,
|
|
154
|
+
"crossover": crossover,
|
|
155
|
+
"monthly_points": len(spread_series),
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _analyze_shy_tlt_proxy(shy_history: List[Dict],
|
|
160
|
+
tlt_history: List[Dict]) -> Dict:
|
|
161
|
+
"""Fallback: Use SHY/TLT ratio as yield curve proxy."""
|
|
162
|
+
shy_monthly = downsample_to_monthly(shy_history)
|
|
163
|
+
tlt_monthly = downsample_to_monthly(tlt_history)
|
|
164
|
+
|
|
165
|
+
if len(shy_monthly) < 12 or len(tlt_monthly) < 12:
|
|
166
|
+
return _insufficient_data("Insufficient SHY/TLT monthly data")
|
|
167
|
+
|
|
168
|
+
# SHY/TLT ratio: rising = curve flattening/inverting, falling = steepening
|
|
169
|
+
ratio_series = calculate_ratio(shy_monthly, tlt_monthly)
|
|
170
|
+
if len(ratio_series) < 12:
|
|
171
|
+
return _insufficient_data("Insufficient SHY/TLT ratio data")
|
|
172
|
+
|
|
173
|
+
ratio_values = [r["value"] for r in ratio_series]
|
|
174
|
+
current_ratio = ratio_values[0]
|
|
175
|
+
current_date = ratio_series[0]["date"]
|
|
176
|
+
|
|
177
|
+
sma_6m = compute_sma(ratio_values, 6)
|
|
178
|
+
sma_12m = compute_sma(ratio_values, 12)
|
|
179
|
+
crossover = detect_crossover(ratio_values, short_period=6, long_period=12)
|
|
180
|
+
roc_3m = compute_roc(ratio_values, 3)
|
|
181
|
+
roc_12m = compute_roc(ratio_values, 12)
|
|
182
|
+
percentile = compute_percentile(ratio_values, current_ratio)
|
|
183
|
+
|
|
184
|
+
score = score_transition_signal(
|
|
185
|
+
crossover=crossover,
|
|
186
|
+
roc_short=roc_3m,
|
|
187
|
+
roc_long=roc_12m,
|
|
188
|
+
sma_short=sma_6m,
|
|
189
|
+
sma_long=sma_12m,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# For SHY/TLT: rising ratio = flattening, falling = steepening
|
|
193
|
+
if roc_3m is not None and roc_3m < 0:
|
|
194
|
+
direction = "steepening"
|
|
195
|
+
elif roc_3m is not None and roc_3m > 0:
|
|
196
|
+
direction = "flattening"
|
|
197
|
+
else:
|
|
198
|
+
direction = "stable"
|
|
199
|
+
|
|
200
|
+
signal = f"PROXY (SHY/TLT): {direction} signal, score={score}"
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
"score": score,
|
|
204
|
+
"signal": signal,
|
|
205
|
+
"data_available": True,
|
|
206
|
+
"data_source": "shy_tlt_proxy",
|
|
207
|
+
"direction": direction,
|
|
208
|
+
"curve_state": "proxy_only",
|
|
209
|
+
"current_spread": None,
|
|
210
|
+
"current_10y": None,
|
|
211
|
+
"current_2y": None,
|
|
212
|
+
"current_date": current_date,
|
|
213
|
+
"proxy_ratio": round(current_ratio, 4),
|
|
214
|
+
"sma_6m": round(sma_6m, 4) if sma_6m is not None else None,
|
|
215
|
+
"sma_12m": round(sma_12m, 4) if sma_12m is not None else None,
|
|
216
|
+
"roc_3m": round(roc_3m, 2) if roc_3m is not None else None,
|
|
217
|
+
"roc_12m": round(roc_12m, 2) if roc_12m is not None else None,
|
|
218
|
+
"percentile": round(percentile, 1) if percentile is not None else None,
|
|
219
|
+
"crossover": crossover,
|
|
220
|
+
"monthly_points": len(ratio_series),
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _classify_curve_state(spread: float, sma_6m: Optional[float],
|
|
225
|
+
roc_3m: Optional[float]) -> str:
|
|
226
|
+
"""Classify current yield curve state."""
|
|
227
|
+
if spread < -0.5:
|
|
228
|
+
return "deeply_inverted"
|
|
229
|
+
elif spread < 0:
|
|
230
|
+
return "inverted"
|
|
231
|
+
elif spread < 0.5:
|
|
232
|
+
if roc_3m is not None and roc_3m > 0:
|
|
233
|
+
return "normalizing"
|
|
234
|
+
return "flat"
|
|
235
|
+
elif spread < 1.5:
|
|
236
|
+
return "normal"
|
|
237
|
+
else:
|
|
238
|
+
return "steep"
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _describe_signal(score: int, state: str, spread: float, direction: str) -> str:
|
|
242
|
+
state_labels = {
|
|
243
|
+
"deeply_inverted": "Deeply Inverted",
|
|
244
|
+
"inverted": "Inverted",
|
|
245
|
+
"normalizing": "Normalizing",
|
|
246
|
+
"flat": "Flat",
|
|
247
|
+
"normal": "Normal",
|
|
248
|
+
"steep": "Steep",
|
|
249
|
+
}
|
|
250
|
+
state_label = state_labels.get(state, state)
|
|
251
|
+
|
|
252
|
+
if score >= 60:
|
|
253
|
+
return f"TRANSITION: Yield curve {direction} ({state_label}, spread={spread:+.3f}%)"
|
|
254
|
+
elif score >= 40:
|
|
255
|
+
return f"SHIFTING: Yield curve {direction} ({state_label}, spread={spread:+.3f}%)"
|
|
256
|
+
else:
|
|
257
|
+
return f"STABLE: Yield curve {state_label} (spread={spread:+.3f}%)"
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _insufficient_data(reason: str) -> Dict:
|
|
261
|
+
return {
|
|
262
|
+
"score": 0,
|
|
263
|
+
"signal": f"INSUFFICIENT DATA: {reason}",
|
|
264
|
+
"data_available": False,
|
|
265
|
+
"data_source": "none",
|
|
266
|
+
"direction": "unknown",
|
|
267
|
+
"curve_state": "unknown",
|
|
268
|
+
"current_spread": None,
|
|
269
|
+
"current_10y": None,
|
|
270
|
+
"current_2y": None,
|
|
271
|
+
"current_date": None,
|
|
272
|
+
"sma_6m": None,
|
|
273
|
+
"sma_12m": None,
|
|
274
|
+
"roc_3m": None,
|
|
275
|
+
"roc_12m": None,
|
|
276
|
+
"percentile": None,
|
|
277
|
+
"crossover": {"type": "none", "bars_ago": None},
|
|
278
|
+
"monthly_points": 0,
|
|
279
|
+
}
|