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
package/.claude/skills/uptrend-analyzer/scripts/calculators/sector_participation_calculator.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Component 2: Sector Participation - Weight: 25%
|
|
4
|
+
|
|
5
|
+
Evaluates how many sectors are participating in the uptrend and the
|
|
6
|
+
uniformity of participation (spread between strongest and weakest).
|
|
7
|
+
|
|
8
|
+
Data Source: Sector Summary CSV
|
|
9
|
+
|
|
10
|
+
Sub-scores:
|
|
11
|
+
- Uptrend Count (60%): How many of 11 sectors are in uptrend
|
|
12
|
+
- Spread (40%): Max-min ratio spread (uniform = healthy, selective = weak)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
from typing import Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
from data_fetcher import build_summary_from_timeseries
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Monty's official dashboard thresholds
|
|
22
|
+
OVERBOUGHT_THRESHOLD = 0.37 # Upper threshold
|
|
23
|
+
OVERSOLD_THRESHOLD = 0.097 # Lower threshold
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def calculate_sector_participation(sector_summary: List[Dict],
|
|
27
|
+
sector_timeseries: Dict[str, List[Dict]]) -> Dict:
|
|
28
|
+
"""
|
|
29
|
+
Calculate sector participation score.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
sector_summary: List of sector summary rows
|
|
33
|
+
sector_timeseries: Dict mapping sector -> timeseries rows; used as
|
|
34
|
+
fallback if sector_summary is unavailable
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Dict with score (0-100), signal, and detail fields
|
|
38
|
+
"""
|
|
39
|
+
if not sector_summary:
|
|
40
|
+
if sector_timeseries:
|
|
41
|
+
sector_summary = build_summary_from_timeseries(sector_timeseries)
|
|
42
|
+
print(" (fallback: built sector summary from timeseries data)",
|
|
43
|
+
file=sys.stderr)
|
|
44
|
+
else:
|
|
45
|
+
return {
|
|
46
|
+
"score": 50,
|
|
47
|
+
"signal": "NO DATA: Sector summary unavailable (neutral default)",
|
|
48
|
+
"data_available": False,
|
|
49
|
+
"uptrend_count": None,
|
|
50
|
+
"total_sectors": None,
|
|
51
|
+
"spread": None,
|
|
52
|
+
"sector_details": [],
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
total_sectors = len(sector_summary)
|
|
56
|
+
if total_sectors == 0:
|
|
57
|
+
return {
|
|
58
|
+
"score": 50,
|
|
59
|
+
"signal": "NO DATA: No sectors in summary",
|
|
60
|
+
"data_available": False,
|
|
61
|
+
"uptrend_count": 0,
|
|
62
|
+
"total_sectors": 0,
|
|
63
|
+
"spread": None,
|
|
64
|
+
"sector_details": [],
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Count sectors in uptrend
|
|
68
|
+
uptrend_count = sum(
|
|
69
|
+
1 for s in sector_summary
|
|
70
|
+
if s.get("Trend", "").lower() == "up"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Get ratios for spread calculation
|
|
74
|
+
ratios = [s["Ratio"] for s in sector_summary
|
|
75
|
+
if s.get("Ratio") is not None]
|
|
76
|
+
|
|
77
|
+
if ratios:
|
|
78
|
+
max_ratio = max(ratios)
|
|
79
|
+
min_ratio = min(ratios)
|
|
80
|
+
spread = max_ratio - min_ratio
|
|
81
|
+
else:
|
|
82
|
+
max_ratio = None
|
|
83
|
+
min_ratio = None
|
|
84
|
+
spread = None
|
|
85
|
+
|
|
86
|
+
# Sub-score 1: Uptrend Count (60%)
|
|
87
|
+
count_score = _score_uptrend_count(uptrend_count, total_sectors)
|
|
88
|
+
|
|
89
|
+
# Sub-score 2: Spread (40%)
|
|
90
|
+
spread_score = _score_spread(spread) if spread is not None else 50
|
|
91
|
+
|
|
92
|
+
# Composite
|
|
93
|
+
raw_score = count_score * 0.60 + spread_score * 0.40
|
|
94
|
+
score = round(min(100, max(0, raw_score)))
|
|
95
|
+
|
|
96
|
+
# Identify overbought/oversold sectors
|
|
97
|
+
overbought = [s for s in sector_summary
|
|
98
|
+
if s.get("Ratio") is not None and s["Ratio"] >= OVERBOUGHT_THRESHOLD]
|
|
99
|
+
oversold = [s for s in sector_summary
|
|
100
|
+
if s.get("Ratio") is not None and s["Ratio"] < OVERSOLD_THRESHOLD]
|
|
101
|
+
|
|
102
|
+
signal = _build_signal(score, uptrend_count, total_sectors, spread)
|
|
103
|
+
|
|
104
|
+
# Build sector details sorted by ratio descending
|
|
105
|
+
sector_details = []
|
|
106
|
+
for s in sorted(sector_summary, key=lambda x: x.get("Ratio") or 0, reverse=True):
|
|
107
|
+
sector_details.append({
|
|
108
|
+
"sector": s.get("Sector", "Unknown"),
|
|
109
|
+
"ratio": s.get("Ratio"),
|
|
110
|
+
"ratio_pct": round(s["Ratio"] * 100, 1) if s.get("Ratio") is not None else None,
|
|
111
|
+
"ma_10": s.get("10MA"),
|
|
112
|
+
"trend": s.get("Trend", ""),
|
|
113
|
+
"slope": s.get("Slope"),
|
|
114
|
+
"status": s.get("Status", ""),
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
"score": score,
|
|
119
|
+
"signal": signal,
|
|
120
|
+
"data_available": True,
|
|
121
|
+
"uptrend_count": uptrend_count,
|
|
122
|
+
"total_sectors": total_sectors,
|
|
123
|
+
"count_score": round(count_score),
|
|
124
|
+
"spread": round(spread, 4) if spread is not None else None,
|
|
125
|
+
"spread_pct": round(spread * 100, 1) if spread is not None else None,
|
|
126
|
+
"spread_score": round(spread_score),
|
|
127
|
+
"max_ratio": max_ratio,
|
|
128
|
+
"min_ratio": min_ratio,
|
|
129
|
+
"overbought_count": len(overbought),
|
|
130
|
+
"overbought_sectors": [s.get("Sector", "") for s in overbought],
|
|
131
|
+
"oversold_count": len(oversold),
|
|
132
|
+
"oversold_sectors": [s.get("Sector", "") for s in oversold],
|
|
133
|
+
"sector_details": sector_details,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _score_uptrend_count(uptrend_count: int, total_sectors: int) -> float:
|
|
138
|
+
"""Score based on number of sectors in uptrend.
|
|
139
|
+
|
|
140
|
+
10-11 sectors -> 100
|
|
141
|
+
8-9 sectors -> 80
|
|
142
|
+
6-7 sectors -> 60
|
|
143
|
+
4-5 sectors -> 40
|
|
144
|
+
2-3 sectors -> 20
|
|
145
|
+
0-1 sectors -> 0
|
|
146
|
+
"""
|
|
147
|
+
if total_sectors == 0:
|
|
148
|
+
return 50
|
|
149
|
+
|
|
150
|
+
if uptrend_count >= 10:
|
|
151
|
+
return 100
|
|
152
|
+
elif uptrend_count >= 8:
|
|
153
|
+
return 80
|
|
154
|
+
elif uptrend_count >= 6:
|
|
155
|
+
return 60
|
|
156
|
+
elif uptrend_count >= 4:
|
|
157
|
+
return 40
|
|
158
|
+
elif uptrend_count >= 2:
|
|
159
|
+
return 20
|
|
160
|
+
else:
|
|
161
|
+
return 0
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _score_spread(spread: float) -> float:
|
|
165
|
+
"""Score based on max-min ratio spread (0-1 scale).
|
|
166
|
+
|
|
167
|
+
< 0.15 -> 100 (uniform participation)
|
|
168
|
+
0.15-0.25 -> 80 (healthy spread)
|
|
169
|
+
0.25-0.35 -> 60 (moderate dispersion)
|
|
170
|
+
0.35-0.45 -> 30 (wide divergence)
|
|
171
|
+
> 0.45 -> 0 (extremely selective)
|
|
172
|
+
"""
|
|
173
|
+
if spread < 0.15:
|
|
174
|
+
return 100
|
|
175
|
+
elif spread < 0.25:
|
|
176
|
+
return 100 - (spread - 0.15) / 0.10 * 20
|
|
177
|
+
elif spread < 0.35:
|
|
178
|
+
return 80 - (spread - 0.25) / 0.10 * 20
|
|
179
|
+
elif spread < 0.45:
|
|
180
|
+
return 60 - (spread - 0.35) / 0.10 * 30
|
|
181
|
+
else:
|
|
182
|
+
return max(0, 30 - (spread - 0.45) / 0.10 * 30)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _build_signal(score: int, uptrend_count: int,
|
|
186
|
+
total_sectors: int, spread: Optional[float]) -> str:
|
|
187
|
+
"""Build human-readable signal."""
|
|
188
|
+
spread_pct = f", spread {round(spread * 100, 1)}%" if spread is not None else ""
|
|
189
|
+
|
|
190
|
+
if score >= 80:
|
|
191
|
+
return (f"BROAD PARTICIPATION: {uptrend_count}/{total_sectors} "
|
|
192
|
+
f"sectors uptrending{spread_pct}")
|
|
193
|
+
elif score >= 60:
|
|
194
|
+
return (f"HEALTHY: {uptrend_count}/{total_sectors} "
|
|
195
|
+
f"sectors uptrending{spread_pct}")
|
|
196
|
+
elif score >= 40:
|
|
197
|
+
return (f"MODERATE: {uptrend_count}/{total_sectors} "
|
|
198
|
+
f"sectors uptrending{spread_pct}")
|
|
199
|
+
elif score >= 20:
|
|
200
|
+
return (f"NARROW: {uptrend_count}/{total_sectors} "
|
|
201
|
+
f"sectors uptrending{spread_pct}")
|
|
202
|
+
else:
|
|
203
|
+
return (f"VERY NARROW: {uptrend_count}/{total_sectors} "
|
|
204
|
+
f"sectors uptrending{spread_pct}")
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Component 3: Sector Rotation - Weight: 15%
|
|
4
|
+
|
|
5
|
+
Evaluates whether cyclical (risk-on) or defensive (risk-off) sectors are
|
|
6
|
+
leading, plus commodity sector dynamics for late-cycle detection.
|
|
7
|
+
|
|
8
|
+
Data Source: Sector Summary CSV
|
|
9
|
+
|
|
10
|
+
Sector Groups:
|
|
11
|
+
Cyclical: Technology, Consumer Cyclical, Communication Services, Financial, Industrials
|
|
12
|
+
Defensive: Utilities, Consumer Defensive, Healthcare, Real Estate
|
|
13
|
+
Commodity: Energy, Basic Materials
|
|
14
|
+
|
|
15
|
+
Primary score: cyclical_avg - defensive_avg difference
|
|
16
|
+
> +0.15 -> 90-100 (strong risk-on)
|
|
17
|
+
+0.05~+0.15 -> 70-89 (healthy cyclical lead)
|
|
18
|
+
-0.05~+0.05 -> 45-69 (balanced)
|
|
19
|
+
-0.15~-0.05 -> 20-44 (defensive tilt)
|
|
20
|
+
< -0.15 -> 0-19 (strong risk-off)
|
|
21
|
+
|
|
22
|
+
Commodity adjustment: if commodity_avg > both groups -> late cycle flag, penalty
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import sys
|
|
26
|
+
from typing import Dict, List, Optional
|
|
27
|
+
|
|
28
|
+
from data_fetcher import build_summary_from_timeseries
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Sector classification
|
|
32
|
+
CYCLICAL_SECTORS = [
|
|
33
|
+
"Technology", "Consumer Cyclical", "Communication Services",
|
|
34
|
+
"Financial", "Industrials",
|
|
35
|
+
]
|
|
36
|
+
DEFENSIVE_SECTORS = [
|
|
37
|
+
"Utilities", "Consumer Defensive", "Healthcare", "Real Estate",
|
|
38
|
+
]
|
|
39
|
+
COMMODITY_SECTORS = [
|
|
40
|
+
"Energy", "Basic Materials",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def calculate_sector_rotation(sector_summary: List[Dict],
|
|
45
|
+
sector_timeseries: Dict[str, List[Dict]]) -> Dict:
|
|
46
|
+
"""
|
|
47
|
+
Calculate sector rotation score.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
sector_summary: List of sector summary rows
|
|
51
|
+
sector_timeseries: Dict mapping sector -> timeseries rows (reserved)
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Dict with score (0-100), signal, and detail fields
|
|
55
|
+
"""
|
|
56
|
+
if not sector_summary:
|
|
57
|
+
if sector_timeseries:
|
|
58
|
+
sector_summary = build_summary_from_timeseries(sector_timeseries)
|
|
59
|
+
print(" (fallback: built sector summary from timeseries data)",
|
|
60
|
+
file=sys.stderr)
|
|
61
|
+
else:
|
|
62
|
+
return {
|
|
63
|
+
"score": 50,
|
|
64
|
+
"signal": "NO DATA: Sector summary unavailable (neutral default)",
|
|
65
|
+
"data_available": False,
|
|
66
|
+
"cyclical_avg": None,
|
|
67
|
+
"defensive_avg": None,
|
|
68
|
+
"commodity_avg": None,
|
|
69
|
+
"difference": None,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Build lookup by sector name
|
|
73
|
+
sector_map = {s["Sector"]: s for s in sector_summary if s.get("Sector")}
|
|
74
|
+
|
|
75
|
+
# Calculate group averages
|
|
76
|
+
cyclical_ratios = _get_group_ratios(sector_map, CYCLICAL_SECTORS)
|
|
77
|
+
defensive_ratios = _get_group_ratios(sector_map, DEFENSIVE_SECTORS)
|
|
78
|
+
commodity_ratios = _get_group_ratios(sector_map, COMMODITY_SECTORS)
|
|
79
|
+
|
|
80
|
+
cyclical_avg = _avg(cyclical_ratios) if cyclical_ratios else None
|
|
81
|
+
defensive_avg = _avg(defensive_ratios) if defensive_ratios else None
|
|
82
|
+
commodity_avg = _avg(commodity_ratios) if commodity_ratios else None
|
|
83
|
+
|
|
84
|
+
if cyclical_avg is None or defensive_avg is None:
|
|
85
|
+
return {
|
|
86
|
+
"score": 50,
|
|
87
|
+
"signal": "INCOMPLETE DATA: Cannot calculate rotation (neutral default)",
|
|
88
|
+
"data_available": False,
|
|
89
|
+
"cyclical_avg": cyclical_avg,
|
|
90
|
+
"defensive_avg": defensive_avg,
|
|
91
|
+
"commodity_avg": commodity_avg,
|
|
92
|
+
"difference": None,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
difference = cyclical_avg - defensive_avg
|
|
96
|
+
|
|
97
|
+
# Map difference to base score
|
|
98
|
+
base_score = _difference_to_score(difference)
|
|
99
|
+
|
|
100
|
+
# Commodity adjustment: late-cycle penalty
|
|
101
|
+
late_cycle_flag = False
|
|
102
|
+
commodity_penalty = 0
|
|
103
|
+
if commodity_avg is not None:
|
|
104
|
+
if commodity_avg > cyclical_avg and commodity_avg > defensive_avg:
|
|
105
|
+
late_cycle_flag = True
|
|
106
|
+
# Stronger penalty if commodity leads by a lot
|
|
107
|
+
excess = commodity_avg - max(cyclical_avg, defensive_avg)
|
|
108
|
+
if excess > 0.10:
|
|
109
|
+
commodity_penalty = -10
|
|
110
|
+
else:
|
|
111
|
+
commodity_penalty = -5
|
|
112
|
+
|
|
113
|
+
score = round(min(100, max(0, base_score + commodity_penalty)))
|
|
114
|
+
|
|
115
|
+
signal = _build_signal(score, difference, late_cycle_flag)
|
|
116
|
+
|
|
117
|
+
# Build group details
|
|
118
|
+
cyclical_details = _build_group_details(sector_map, CYCLICAL_SECTORS)
|
|
119
|
+
defensive_details = _build_group_details(sector_map, DEFENSIVE_SECTORS)
|
|
120
|
+
commodity_details = _build_group_details(sector_map, COMMODITY_SECTORS)
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
"score": score,
|
|
124
|
+
"signal": signal,
|
|
125
|
+
"data_available": True,
|
|
126
|
+
"cyclical_avg": round(cyclical_avg, 4),
|
|
127
|
+
"cyclical_avg_pct": round(cyclical_avg * 100, 1),
|
|
128
|
+
"defensive_avg": round(defensive_avg, 4),
|
|
129
|
+
"defensive_avg_pct": round(defensive_avg * 100, 1),
|
|
130
|
+
"commodity_avg": round(commodity_avg, 4) if commodity_avg is not None else None,
|
|
131
|
+
"commodity_avg_pct": round(commodity_avg * 100, 1) if commodity_avg is not None else None,
|
|
132
|
+
"difference": round(difference, 4),
|
|
133
|
+
"difference_pct": round(difference * 100, 1),
|
|
134
|
+
"late_cycle_flag": late_cycle_flag,
|
|
135
|
+
"commodity_penalty": commodity_penalty,
|
|
136
|
+
"cyclical_details": cyclical_details,
|
|
137
|
+
"defensive_details": defensive_details,
|
|
138
|
+
"commodity_details": commodity_details,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _get_group_ratios(sector_map: Dict[str, Dict],
|
|
143
|
+
sector_names: List[str]) -> List[float]:
|
|
144
|
+
"""Extract ratios for a group of sectors."""
|
|
145
|
+
ratios = []
|
|
146
|
+
for name in sector_names:
|
|
147
|
+
sector = sector_map.get(name)
|
|
148
|
+
if sector and sector.get("Ratio") is not None:
|
|
149
|
+
ratios.append(sector["Ratio"])
|
|
150
|
+
return ratios
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _avg(values: List[float]) -> float:
|
|
154
|
+
"""Simple average."""
|
|
155
|
+
return sum(values) / len(values)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _difference_to_score(diff: float) -> float:
|
|
159
|
+
"""Map cyclical-defensive difference to score.
|
|
160
|
+
|
|
161
|
+
> +0.15 -> 90-100 (strong risk-on)
|
|
162
|
+
+0.05~+0.15 -> 70-89 (healthy cyclical lead)
|
|
163
|
+
-0.05~+0.05 -> 45-69 (balanced)
|
|
164
|
+
-0.15~-0.05 -> 20-44 (defensive tilt)
|
|
165
|
+
< -0.15 -> 0-19 (strong risk-off)
|
|
166
|
+
"""
|
|
167
|
+
if diff > 0.15:
|
|
168
|
+
return min(100, 90 + (diff - 0.15) / 0.10 * 10)
|
|
169
|
+
elif diff > 0.05:
|
|
170
|
+
return 70 + (diff - 0.05) / 0.10 * 19
|
|
171
|
+
elif diff > -0.05:
|
|
172
|
+
return 45 + (diff + 0.05) / 0.10 * 24
|
|
173
|
+
elif diff > -0.15:
|
|
174
|
+
return 20 + (diff + 0.15) / 0.10 * 24
|
|
175
|
+
else:
|
|
176
|
+
return max(0, 19 + (diff + 0.15) / 0.10 * 19)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _build_signal(score: int, difference: float, late_cycle: bool) -> str:
|
|
180
|
+
"""Build human-readable signal."""
|
|
181
|
+
diff_pct = round(difference * 100, 1)
|
|
182
|
+
late_str = " [LATE CYCLE WARNING]" if late_cycle else ""
|
|
183
|
+
|
|
184
|
+
if score >= 90:
|
|
185
|
+
return f"STRONG RISK-ON: Cyclical leads by {diff_pct}pp{late_str}"
|
|
186
|
+
elif score >= 70:
|
|
187
|
+
return f"RISK-ON: Cyclical leads by {diff_pct}pp{late_str}"
|
|
188
|
+
elif score >= 45:
|
|
189
|
+
return f"BALANCED: Cyclical-Defensive gap {diff_pct}pp{late_str}"
|
|
190
|
+
elif score >= 20:
|
|
191
|
+
return f"DEFENSIVE TILT: Defensive leads by {abs(diff_pct)}pp{late_str}"
|
|
192
|
+
else:
|
|
193
|
+
return f"STRONG RISK-OFF: Defensive leads by {abs(diff_pct)}pp{late_str}"
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _build_group_details(sector_map: Dict[str, Dict],
|
|
197
|
+
sector_names: List[str]) -> List[Dict]:
|
|
198
|
+
"""Build detail rows for a sector group."""
|
|
199
|
+
details = []
|
|
200
|
+
for name in sector_names:
|
|
201
|
+
sector = sector_map.get(name)
|
|
202
|
+
if sector:
|
|
203
|
+
details.append({
|
|
204
|
+
"sector": name,
|
|
205
|
+
"ratio": sector.get("Ratio"),
|
|
206
|
+
"ratio_pct": round(sector["Ratio"] * 100, 1) if sector.get("Ratio") is not None else None,
|
|
207
|
+
"trend": sector.get("Trend", ""),
|
|
208
|
+
"slope": sector.get("Slope"),
|
|
209
|
+
})
|
|
210
|
+
else:
|
|
211
|
+
details.append({
|
|
212
|
+
"sector": name,
|
|
213
|
+
"ratio": None,
|
|
214
|
+
"ratio_pct": None,
|
|
215
|
+
"trend": "N/A",
|
|
216
|
+
"slope": None,
|
|
217
|
+
})
|
|
218
|
+
return details
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Uptrend Analyzer - CSV Data Fetcher
|
|
4
|
+
|
|
5
|
+
Downloads and parses Monty's Uptrend Ratio Dashboard CSV data from GitHub.
|
|
6
|
+
No API key required - uses publicly available CSV files.
|
|
7
|
+
|
|
8
|
+
Data Sources:
|
|
9
|
+
- Timeseries: uptrend_ratio_timeseries.csv (all + 11 sectors, 2023/08~present)
|
|
10
|
+
- Sector Summary: sector_summary.csv (latest snapshot)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import csv
|
|
14
|
+
import io
|
|
15
|
+
import sys
|
|
16
|
+
import time
|
|
17
|
+
from typing import Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
import requests
|
|
21
|
+
except ImportError:
|
|
22
|
+
print("ERROR: requests library not found. Install with: pip install requests",
|
|
23
|
+
file=sys.stderr)
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
TIMESERIES_URL = (
|
|
28
|
+
"https://raw.githubusercontent.com/tradermonty/uptrend-dashboard/"
|
|
29
|
+
"main/data/uptrend_ratio_timeseries.csv"
|
|
30
|
+
)
|
|
31
|
+
SECTOR_SUMMARY_URL = (
|
|
32
|
+
"https://raw.githubusercontent.com/tradermonty/uptrend-dashboard/"
|
|
33
|
+
"main/data/sector_summary.csv"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
WORKSHEET_TO_DISPLAY = {
|
|
38
|
+
"sec_basicmaterials": "Basic Materials",
|
|
39
|
+
"sec_communicationservices": "Communication Services",
|
|
40
|
+
"sec_consumercyclical": "Consumer Cyclical",
|
|
41
|
+
"sec_consumerdefensive": "Consumer Defensive",
|
|
42
|
+
"sec_energy": "Energy",
|
|
43
|
+
"sec_financial": "Financial",
|
|
44
|
+
"sec_healthcare": "Healthcare",
|
|
45
|
+
"sec_industrials": "Industrials",
|
|
46
|
+
"sec_realestate": "Real Estate",
|
|
47
|
+
"sec_technology": "Technology",
|
|
48
|
+
"sec_utilities": "Utilities",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Monty's official dashboard thresholds (same as in sector_participation_calculator)
|
|
52
|
+
OVERBOUGHT_THRESHOLD = 0.37
|
|
53
|
+
OVERSOLD_THRESHOLD = 0.097
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build_summary_from_timeseries(sector_timeseries: Dict[str, Dict]) -> List[Dict]:
|
|
57
|
+
"""Build sector_summary-compatible list from timeseries latest rows.
|
|
58
|
+
|
|
59
|
+
Used as fallback when sector_summary.csv is unavailable.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
sector_timeseries: Dict mapping worksheet name -> latest timeseries row
|
|
63
|
+
e.g. {"sec_technology": {"ratio": 0.288, "ma_10": 0.266, ...}}
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
List of dicts matching sector_summary format:
|
|
67
|
+
[{"Sector": "Technology", "Ratio": 0.288, "10MA": 0.266, ...}]
|
|
68
|
+
"""
|
|
69
|
+
rows = []
|
|
70
|
+
for ws_name, row in sector_timeseries.items():
|
|
71
|
+
display_name = WORKSHEET_TO_DISPLAY.get(ws_name, ws_name)
|
|
72
|
+
ratio = row.get("ratio")
|
|
73
|
+
status = ("Overbought" if ratio is not None and ratio > OVERBOUGHT_THRESHOLD else
|
|
74
|
+
"Oversold" if ratio is not None and ratio < OVERSOLD_THRESHOLD else
|
|
75
|
+
"Normal")
|
|
76
|
+
rows.append({
|
|
77
|
+
"Sector": display_name,
|
|
78
|
+
"Ratio": ratio,
|
|
79
|
+
"10MA": row.get("ma_10"),
|
|
80
|
+
"Trend": (row.get("trend") or "").capitalize(),
|
|
81
|
+
"Slope": row.get("slope"),
|
|
82
|
+
"Status": status,
|
|
83
|
+
})
|
|
84
|
+
return rows
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class UptrendDataFetcher:
|
|
88
|
+
"""Client for Monty's Uptrend Ratio Dashboard CSV data"""
|
|
89
|
+
|
|
90
|
+
def __init__(self):
|
|
91
|
+
self.session = requests.Session()
|
|
92
|
+
self._timeseries_cache: Optional[List[Dict]] = None
|
|
93
|
+
self._sector_summary_cache: Optional[List[Dict]] = None
|
|
94
|
+
|
|
95
|
+
def fetch_timeseries(self) -> List[Dict]:
|
|
96
|
+
"""Download and parse the timeseries CSV.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
List of dicts with keys: worksheet, date, count, total, ratio,
|
|
100
|
+
ma_10, slope, trend. Numeric fields cast to float/int.
|
|
101
|
+
"""
|
|
102
|
+
if self._timeseries_cache is not None:
|
|
103
|
+
return self._timeseries_cache
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
response = self.session.get(TIMESERIES_URL, timeout=30)
|
|
107
|
+
response.raise_for_status()
|
|
108
|
+
except requests.exceptions.RequestException as e:
|
|
109
|
+
print(f"WARNING: Failed to fetch timeseries CSV: {e}",
|
|
110
|
+
file=sys.stderr)
|
|
111
|
+
return []
|
|
112
|
+
|
|
113
|
+
rows = []
|
|
114
|
+
reader = csv.DictReader(io.StringIO(response.text))
|
|
115
|
+
for row in reader:
|
|
116
|
+
parsed = _parse_timeseries_row(row)
|
|
117
|
+
if parsed:
|
|
118
|
+
rows.append(parsed)
|
|
119
|
+
|
|
120
|
+
self._timeseries_cache = rows
|
|
121
|
+
return rows
|
|
122
|
+
|
|
123
|
+
def fetch_sector_summary(self) -> List[Dict]:
|
|
124
|
+
"""Download and parse the sector summary CSV.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
List of dicts with keys: Sector, Ratio, 10MA, Trend, Slope, Status.
|
|
128
|
+
Numeric fields cast to float.
|
|
129
|
+
"""
|
|
130
|
+
if self._sector_summary_cache is not None:
|
|
131
|
+
return self._sector_summary_cache
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
response = self.session.get(SECTOR_SUMMARY_URL, timeout=30)
|
|
135
|
+
response.raise_for_status()
|
|
136
|
+
except requests.exceptions.RequestException as e:
|
|
137
|
+
print(f"WARNING: Failed to fetch sector summary CSV: {e}",
|
|
138
|
+
file=sys.stderr)
|
|
139
|
+
return []
|
|
140
|
+
|
|
141
|
+
rows = []
|
|
142
|
+
reader = csv.DictReader(io.StringIO(response.text))
|
|
143
|
+
for row in reader:
|
|
144
|
+
parsed = _parse_sector_summary_row(row)
|
|
145
|
+
if parsed:
|
|
146
|
+
rows.append(parsed)
|
|
147
|
+
|
|
148
|
+
self._sector_summary_cache = rows
|
|
149
|
+
return rows
|
|
150
|
+
|
|
151
|
+
def get_all_timeseries(self) -> List[Dict]:
|
|
152
|
+
"""Get timeseries filtered to worksheet=='all', sorted by date ascending."""
|
|
153
|
+
data = self.fetch_timeseries()
|
|
154
|
+
filtered = [r for r in data if r["worksheet"] == "all"]
|
|
155
|
+
filtered.sort(key=lambda r: r["date"])
|
|
156
|
+
return filtered
|
|
157
|
+
|
|
158
|
+
def get_sector_timeseries(self, sector: str) -> List[Dict]:
|
|
159
|
+
"""Get timeseries for a specific sector worksheet, sorted by date ascending."""
|
|
160
|
+
data = self.fetch_timeseries()
|
|
161
|
+
filtered = [r for r in data if r["worksheet"] == sector]
|
|
162
|
+
filtered.sort(key=lambda r: r["date"])
|
|
163
|
+
return filtered
|
|
164
|
+
|
|
165
|
+
def get_latest_all(self) -> Optional[Dict]:
|
|
166
|
+
"""Get the most recent 'all' row."""
|
|
167
|
+
ts = self.get_all_timeseries()
|
|
168
|
+
return ts[-1] if ts else None
|
|
169
|
+
|
|
170
|
+
def get_all_sector_latest(self) -> Dict[str, Dict]:
|
|
171
|
+
"""Get latest row for each sector (excluding 'all').
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Dict mapping sector name -> latest timeseries row
|
|
175
|
+
"""
|
|
176
|
+
data = self.fetch_timeseries()
|
|
177
|
+
sectors: Dict[str, Dict] = {}
|
|
178
|
+
for row in data:
|
|
179
|
+
ws = row["worksheet"]
|
|
180
|
+
if ws == "all":
|
|
181
|
+
continue
|
|
182
|
+
if ws not in sectors or row["date"] > sectors[ws]["date"]:
|
|
183
|
+
sectors[ws] = row
|
|
184
|
+
return sectors
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _parse_timeseries_row(row: Dict) -> Optional[Dict]:
|
|
188
|
+
"""Parse a timeseries CSV row, casting numeric fields."""
|
|
189
|
+
try:
|
|
190
|
+
return {
|
|
191
|
+
"worksheet": row.get("worksheet", "").strip(),
|
|
192
|
+
"date": row.get("date", "").strip(),
|
|
193
|
+
"count": _safe_int(row.get("count")),
|
|
194
|
+
"total": _safe_int(row.get("total")),
|
|
195
|
+
"ratio": _safe_float(row.get("ratio")),
|
|
196
|
+
"ma_10": _safe_float(row.get("ma_10")),
|
|
197
|
+
"slope": _safe_float(row.get("slope")),
|
|
198
|
+
"trend": row.get("trend", "").strip(),
|
|
199
|
+
}
|
|
200
|
+
except (ValueError, TypeError):
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _parse_sector_summary_row(row: Dict) -> Optional[Dict]:
|
|
205
|
+
"""Parse a sector summary CSV row, casting numeric fields."""
|
|
206
|
+
try:
|
|
207
|
+
return {
|
|
208
|
+
"Sector": row.get("Sector", "").strip(),
|
|
209
|
+
"Ratio": _safe_float(row.get("Ratio")),
|
|
210
|
+
"10MA": _safe_float(row.get("10MA")),
|
|
211
|
+
"Trend": row.get("Trend", "").strip(),
|
|
212
|
+
"Slope": _safe_float(row.get("Slope")),
|
|
213
|
+
"Status": row.get("Status", "").strip(),
|
|
214
|
+
}
|
|
215
|
+
except (ValueError, TypeError):
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _safe_float(value) -> Optional[float]:
|
|
220
|
+
"""Convert to float, return None if empty or invalid."""
|
|
221
|
+
if value is None or str(value).strip() == "":
|
|
222
|
+
return None
|
|
223
|
+
try:
|
|
224
|
+
return float(value)
|
|
225
|
+
except (ValueError, TypeError):
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _safe_int(value) -> Optional[int]:
|
|
230
|
+
"""Convert to int, return None if empty or invalid."""
|
|
231
|
+
if value is None or str(value).strip() == "":
|
|
232
|
+
return None
|
|
233
|
+
try:
|
|
234
|
+
return int(float(value))
|
|
235
|
+
except (ValueError, TypeError):
|
|
236
|
+
return None
|