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,215 @@
|
|
|
1
|
+
# Uptrend Analyzer Methodology
|
|
2
|
+
|
|
3
|
+
## Data Source: Monty's Uptrend Ratio Dashboard
|
|
4
|
+
|
|
5
|
+
Monty's Uptrend Ratio Dashboard tracks approximately 2,800 US stocks across 11 GICS sectors. For each stock, it determines whether the stock is in an "uptrend" based on the criteria below. The dashboard publishes daily CSV data on GitHub.
|
|
6
|
+
|
|
7
|
+
**GitHub Repository:** `tradermonty/uptrend-dashboard`
|
|
8
|
+
**Live Dashboard:** https://uptrend-dashboard.streamlit.app/
|
|
9
|
+
|
|
10
|
+
### Uptrend Definition (Finviz Elite Screener)
|
|
11
|
+
|
|
12
|
+
A stock is classified as "uptrend" when it meets **all** of the following conditions:
|
|
13
|
+
|
|
14
|
+
| Condition | Description |
|
|
15
|
+
|-----------|-------------|
|
|
16
|
+
| Price > $10 | Penny stocks excluded |
|
|
17
|
+
| Avg Volume > 100K | Sufficient liquidity |
|
|
18
|
+
| Market Cap > $50M | Micro-cap and above |
|
|
19
|
+
| Price > SMA20 | Short-term uptrend |
|
|
20
|
+
| Price > SMA200 | Long-term uptrend |
|
|
21
|
+
| SMA50 > SMA200 | Golden cross (bullish structure) |
|
|
22
|
+
| 52W High/Low > 30% above Low | Recovering from bottom |
|
|
23
|
+
| 4-Week Performance: Up | Recent momentum positive |
|
|
24
|
+
|
|
25
|
+
The **uptrend ratio** = (stocks meeting all conditions) / (stocks meeting base filters: price, volume, market cap).
|
|
26
|
+
|
|
27
|
+
### CSV Files
|
|
28
|
+
|
|
29
|
+
| File | Description | Update Frequency |
|
|
30
|
+
|------|-------------|------------------|
|
|
31
|
+
| `uptrend_ratio_timeseries.csv` | Daily ratios for "all" + 11 sectors | Daily |
|
|
32
|
+
| `sector_summary.csv` | Latest snapshot of all sectors | Daily |
|
|
33
|
+
|
|
34
|
+
**Data availability:**
|
|
35
|
+
- "all" (full market): Since 2023-08-11
|
|
36
|
+
- Sector-level data: Since 2024-07-21
|
|
37
|
+
|
|
38
|
+
### Timeseries Columns
|
|
39
|
+
|
|
40
|
+
| Column | Type | Description |
|
|
41
|
+
|--------|------|-------------|
|
|
42
|
+
| worksheet | string | "all" or sector slug (e.g., "sec_technology") |
|
|
43
|
+
| date | string | YYYY-MM-DD format |
|
|
44
|
+
| count | int | Number of stocks in uptrend |
|
|
45
|
+
| total | int | Total stocks tracked |
|
|
46
|
+
| ratio | float | count/total (0-1 scale, raw decimal) |
|
|
47
|
+
| ma_10 | float | 10-day simple moving average of ratio |
|
|
48
|
+
| slope | float | 1-day difference of ma_10 (`ma_10.diff()`) |
|
|
49
|
+
| trend | string | "up" (slope > 0) or "down" (slope <= 0) |
|
|
50
|
+
|
|
51
|
+
### Sector Summary Columns
|
|
52
|
+
|
|
53
|
+
| Column | Type | Description |
|
|
54
|
+
|--------|------|-------------|
|
|
55
|
+
| Sector | string | Display name (e.g., "Technology") |
|
|
56
|
+
| Ratio | float | Current uptrend ratio (0-1) |
|
|
57
|
+
| 10MA | float | 10-day MA of ratio |
|
|
58
|
+
| Trend | string | "Up" or "Down" |
|
|
59
|
+
| Slope | float | 1-day difference of MA |
|
|
60
|
+
| Status | string | "Overbought", "Oversold", or "Normal" |
|
|
61
|
+
|
|
62
|
+
### Indicator Calculations (from source code)
|
|
63
|
+
|
|
64
|
+
All indicators are computed on-the-fly from raw count/total data:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
ratio = count / total
|
|
68
|
+
ma_10 = ratio.rolling(10).mean() # 10-day simple MA
|
|
69
|
+
slope = ma_10.diff() # 1-day change of MA
|
|
70
|
+
trend = "up" if slope > 0 else "down"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Peak/Trough Detection:** The dashboard uses `scipy.signal.find_peaks` with parameters `distance=20, prominence=0.015` to identify local tops and bottoms in the 10MA series.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Official Dashboard Thresholds
|
|
78
|
+
|
|
79
|
+
These thresholds are defined in `src/constants.py` of the source repository:
|
|
80
|
+
|
|
81
|
+
| Threshold | Value | Meaning |
|
|
82
|
+
|-----------|-------|---------|
|
|
83
|
+
| **Upper (Overbought)** | **37%** | Ratio above this = overbought conditions |
|
|
84
|
+
| **Lower (Oversold)** | **9.7%** | Ratio below this = oversold / crisis |
|
|
85
|
+
| MA Period | 10 | Simple moving average window |
|
|
86
|
+
|
|
87
|
+
### Status Determination
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
ratio > 0.37 -> "Overbought"
|
|
91
|
+
ratio < 0.097 -> "Oversold"
|
|
92
|
+
otherwise -> "Normal"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Practical Interpretation
|
|
96
|
+
|
|
97
|
+
| Ratio | Interpretation | Market Environment |
|
|
98
|
+
|-------|---------------|-------------------|
|
|
99
|
+
| 50%+ | Strong breadth | Broad bull market, most stocks participating |
|
|
100
|
+
| 37-50% | Overbought / Healthy | Above upper threshold, strong but extended |
|
|
101
|
+
| 25-37% | Normal / Recovering | Between thresholds, typical trading range |
|
|
102
|
+
| 9.7-25% | Weak | Below normal, breadth deteriorating |
|
|
103
|
+
| < 9.7% | Oversold / Crisis | Below lower threshold, extreme selling |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 5-Component Scoring System
|
|
108
|
+
|
|
109
|
+
### Component 1: Market Breadth (Overall) - Weight: 30%
|
|
110
|
+
|
|
111
|
+
**Rationale:** The overall uptrend ratio is the single most important measure of market health. A high ratio means broad participation; a low ratio means a narrow, fragile market.
|
|
112
|
+
|
|
113
|
+
**Scoring Bands (aligned with dashboard thresholds):**
|
|
114
|
+
|
|
115
|
+
| Ratio | Score Range | Signal |
|
|
116
|
+
|-------|-------------|--------|
|
|
117
|
+
| >= 50% | 90-100 | Strong Bull |
|
|
118
|
+
| 37-50% | 70-89 | Bullish (above overbought threshold) |
|
|
119
|
+
| 25-37% | 40-69 | Neutral/Recovering |
|
|
120
|
+
| 9.7-25% | 10-39 | Weak (between thresholds) |
|
|
121
|
+
| < 9.7% | 0-9 | Crisis (below oversold threshold) |
|
|
122
|
+
|
|
123
|
+
**Trend Adjustment:** +5 when trend="up" and slope>0, -5 when trend="down" and slope<0.
|
|
124
|
+
|
|
125
|
+
### Component 2: Sector Participation - Weight: 25%
|
|
126
|
+
|
|
127
|
+
**Rationale:** A healthy market has most sectors participating. When only 2-3 sectors lead, the market is fragile and vulnerable to sector rotation shocks.
|
|
128
|
+
|
|
129
|
+
**Sub-scores:**
|
|
130
|
+
- **Uptrend Count (60%):** Number of sectors in uptrend mapped to 0-100
|
|
131
|
+
- **Spread (40%):** Max-min ratio spread. Narrow spread = uniform participation (good). Wide spread = selective market (risky).
|
|
132
|
+
|
|
133
|
+
**Overbought/Oversold classification uses dashboard thresholds:** >37% = Overbought, <9.7% = Oversold.
|
|
134
|
+
|
|
135
|
+
### Component 3: Sector Rotation - Weight: 15%
|
|
136
|
+
|
|
137
|
+
**Rationale:** In a healthy bull market, cyclical sectors (Technology, Consumer Cyclical) lead defensive sectors (Utilities, Consumer Defensive). When defensives lead, it signals risk-off behavior.
|
|
138
|
+
|
|
139
|
+
**Sector Classification:**
|
|
140
|
+
|
|
141
|
+
| Group | Sectors |
|
|
142
|
+
|-------|---------|
|
|
143
|
+
| Cyclical | Technology, Consumer Cyclical, Communication Services, Financial, Industrials |
|
|
144
|
+
| Defensive | Utilities, Consumer Defensive, Healthcare, Real Estate |
|
|
145
|
+
| Commodity | Energy, Basic Materials |
|
|
146
|
+
|
|
147
|
+
**Scoring:** Based on cyclical_avg - defensive_avg difference.
|
|
148
|
+
- Cyclical lead > +15pp = Strong risk-on (90-100)
|
|
149
|
+
- Balanced within +/-5pp = Neutral (45-69)
|
|
150
|
+
- Defensive lead > +15pp = Strong risk-off (0-19)
|
|
151
|
+
|
|
152
|
+
**Commodity Adjustment:** When commodity sectors outperform both cyclical and defensive groups, it may signal late-cycle dynamics. A penalty of -5 to -10 is applied.
|
|
153
|
+
|
|
154
|
+
### Component 4: Momentum - Weight: 20%
|
|
155
|
+
|
|
156
|
+
**Rationale:** The direction and rate of change in breadth matters as much as the level. Improving breadth (positive slope, accelerating) suggests the environment is getting better; deteriorating breadth suggests caution.
|
|
157
|
+
|
|
158
|
+
**Sub-scores:**
|
|
159
|
+
- **Slope Score (50%):** Current slope mapped to 0-100 (typical range: -0.02 to +0.02)
|
|
160
|
+
- **Acceleration (30%):** Recent 5-point slope average vs prior 5-point average
|
|
161
|
+
- **Sector Slope Breadth (20%):** Count of sectors with positive slope
|
|
162
|
+
|
|
163
|
+
### Component 5: Historical Context - Weight: 10%
|
|
164
|
+
|
|
165
|
+
**Rationale:** Knowing where the current ratio falls in historical distribution provides perspective. A ratio that seems "low" might be historically average, or vice versa.
|
|
166
|
+
|
|
167
|
+
**Scoring:** Percentile rank of current ratio in the full historical distribution (Aug 2023 to present).
|
|
168
|
+
|
|
169
|
+
**Note:** The "all" dataset starts from 2023-08-11 (~650+ data points), while sector data starts from 2024-07-21 (~370+ data points each).
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Scoring Zones and Exposure Guidance
|
|
174
|
+
|
|
175
|
+
| Score | Zone | Exposure | Description |
|
|
176
|
+
|-------|------|----------|-------------|
|
|
177
|
+
| 80-100 | Strong Bull | Full (100%) | Broad participation, strong momentum. Ideal for aggressive positioning. |
|
|
178
|
+
| 60-79 | Bull | Normal (80-100%) | Healthy breadth. Standard position management. |
|
|
179
|
+
| 40-59 | Neutral | Reduced (60-80%) | Mixed signals. Participate selectively. |
|
|
180
|
+
| 20-39 | Cautious | Defensive (30-60%) | Weak breadth. Prioritize capital preservation. |
|
|
181
|
+
| 0-19 | Bear | Preservation (0-30%) | Severe deterioration. Maximum defense. |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Weight Rationale
|
|
186
|
+
|
|
187
|
+
| Component | Weight | Rationale |
|
|
188
|
+
|-----------|--------|-----------|
|
|
189
|
+
| Market Breadth | 30% | Most direct measure of market health |
|
|
190
|
+
| Sector Participation | 25% | Breadth of sector-level participation is critical for sustainability |
|
|
191
|
+
| Momentum | 20% | Direction matters as much as level |
|
|
192
|
+
| Sector Rotation | 15% | Rotation signals provide important risk-on/off context |
|
|
193
|
+
| Historical Context | 10% | Provides perspective but less actionable than real-time signals |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Limitations
|
|
198
|
+
|
|
199
|
+
1. **Data History:** "all" data starts from Aug 2023; sector data from Jul 2024. Long-term percentile analysis is limited.
|
|
200
|
+
2. **Single Source:** Relies entirely on Monty's dashboard (Finviz Elite data); no cross-validation with other breadth measures.
|
|
201
|
+
3. **No Volume Data:** Uptrend ratio is price-based only; no volume confirmation.
|
|
202
|
+
4. **Lagging Indicator:** 10-day moving average and slope introduce inherent lag.
|
|
203
|
+
5. **US Only:** Covers US stocks only; no international market breadth.
|
|
204
|
+
6. **Sector Classification:** Uses fixed GICS sectors which may not capture all rotation dynamics.
|
|
205
|
+
7. **Finviz Dependency:** Upstream data depends on Finviz Elite availability and screener accuracy.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Complementary Analysis
|
|
210
|
+
|
|
211
|
+
This skill works best when combined with:
|
|
212
|
+
- **Market Top Detector:** For distribution day and leadership deterioration signals
|
|
213
|
+
- **Technical Analyst:** For index-level chart confirmation
|
|
214
|
+
- **Sector Analyst:** For detailed sector rotation analysis
|
|
215
|
+
- **Market News Analyst:** For fundamental catalyst context
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Uptrend Analyzer - Calculator modules
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Component 5: Historical Context - Weight: 10%
|
|
4
|
+
|
|
5
|
+
Evaluates the current uptrend ratio relative to its full historical
|
|
6
|
+
distribution (2023/08~present) using percentile rank.
|
|
7
|
+
|
|
8
|
+
Data Source: Timeseries "all" worksheet
|
|
9
|
+
|
|
10
|
+
Score = percentile rank of current ratio in historical distribution
|
|
11
|
+
Additional context: min, max, median, 30d avg, 90d avg
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def calculate_historical_context(all_timeseries: List[Dict]) -> Dict:
|
|
18
|
+
"""
|
|
19
|
+
Calculate historical context score via percentile rank.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
all_timeseries: Full "all" timeseries sorted by date ascending
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Dict with score (0-100), signal, and context fields
|
|
26
|
+
"""
|
|
27
|
+
if not all_timeseries:
|
|
28
|
+
return {
|
|
29
|
+
"score": 50,
|
|
30
|
+
"signal": "NO DATA: Historical timeseries unavailable (neutral default)",
|
|
31
|
+
"data_available": False,
|
|
32
|
+
"percentile": None,
|
|
33
|
+
"current_ratio": None,
|
|
34
|
+
"historical_min": None,
|
|
35
|
+
"historical_max": None,
|
|
36
|
+
"historical_median": None,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Extract all valid ratios
|
|
40
|
+
ratios = [r["ratio"] for r in all_timeseries if r.get("ratio") is not None]
|
|
41
|
+
|
|
42
|
+
if len(ratios) < 2:
|
|
43
|
+
return {
|
|
44
|
+
"score": 50,
|
|
45
|
+
"signal": "INSUFFICIENT DATA: Need at least 2 data points",
|
|
46
|
+
"data_available": False,
|
|
47
|
+
"percentile": None,
|
|
48
|
+
"current_ratio": None,
|
|
49
|
+
"data_points": len(ratios),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
current_ratio = ratios[-1]
|
|
53
|
+
|
|
54
|
+
# Calculate percentile rank
|
|
55
|
+
below_count = sum(1 for r in ratios if r < current_ratio)
|
|
56
|
+
equal_count = sum(1 for r in ratios if r == current_ratio)
|
|
57
|
+
percentile = (below_count + equal_count * 0.5) / len(ratios) * 100
|
|
58
|
+
percentile = round(percentile, 1)
|
|
59
|
+
|
|
60
|
+
# Score = percentile rank (direct mapping)
|
|
61
|
+
score = round(min(100, max(0, percentile)))
|
|
62
|
+
|
|
63
|
+
# Historical statistics
|
|
64
|
+
sorted_ratios = sorted(ratios)
|
|
65
|
+
hist_min = sorted_ratios[0]
|
|
66
|
+
hist_max = sorted_ratios[-1]
|
|
67
|
+
n = len(sorted_ratios)
|
|
68
|
+
if n % 2 == 0:
|
|
69
|
+
hist_median = (sorted_ratios[n // 2 - 1] + sorted_ratios[n // 2]) / 2
|
|
70
|
+
else:
|
|
71
|
+
hist_median = sorted_ratios[n // 2]
|
|
72
|
+
|
|
73
|
+
# Recent averages
|
|
74
|
+
avg_30d = _avg_last_n(ratios, 30)
|
|
75
|
+
avg_90d = _avg_last_n(ratios, 90)
|
|
76
|
+
|
|
77
|
+
signal = _build_signal(score, percentile, current_ratio)
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
"score": score,
|
|
81
|
+
"signal": signal,
|
|
82
|
+
"data_available": True,
|
|
83
|
+
"percentile": percentile,
|
|
84
|
+
"current_ratio": current_ratio,
|
|
85
|
+
"current_ratio_pct": round(current_ratio * 100, 1),
|
|
86
|
+
"historical_min": round(hist_min, 4),
|
|
87
|
+
"historical_min_pct": round(hist_min * 100, 1),
|
|
88
|
+
"historical_max": round(hist_max, 4),
|
|
89
|
+
"historical_max_pct": round(hist_max * 100, 1),
|
|
90
|
+
"historical_median": round(hist_median, 4),
|
|
91
|
+
"historical_median_pct": round(hist_median * 100, 1),
|
|
92
|
+
"avg_30d": round(avg_30d, 4) if avg_30d is not None else None,
|
|
93
|
+
"avg_30d_pct": round(avg_30d * 100, 1) if avg_30d is not None else None,
|
|
94
|
+
"avg_90d": round(avg_90d, 4) if avg_90d is not None else None,
|
|
95
|
+
"avg_90d_pct": round(avg_90d * 100, 1) if avg_90d is not None else None,
|
|
96
|
+
"data_points": len(ratios),
|
|
97
|
+
"date_range": f"{all_timeseries[0].get('date', '?')} to {all_timeseries[-1].get('date', '?')}",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _avg_last_n(values: List[float], n: int) -> Optional[float]:
|
|
102
|
+
"""Average of the last n values, or all if fewer than n."""
|
|
103
|
+
if not values:
|
|
104
|
+
return None
|
|
105
|
+
subset = values[-n:]
|
|
106
|
+
return sum(subset) / len(subset)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _build_signal(score: int, percentile: float, current_ratio: float) -> str:
|
|
110
|
+
"""Build human-readable signal."""
|
|
111
|
+
ratio_pct = round(current_ratio * 100, 1)
|
|
112
|
+
|
|
113
|
+
if score >= 80:
|
|
114
|
+
return f"ABOVE AVERAGE: {ratio_pct}% at {percentile}th percentile historically"
|
|
115
|
+
elif score >= 60:
|
|
116
|
+
return f"SLIGHTLY ABOVE: {ratio_pct}% at {percentile}th percentile historically"
|
|
117
|
+
elif score >= 40:
|
|
118
|
+
return f"NEAR MEDIAN: {ratio_pct}% at {percentile}th percentile historically"
|
|
119
|
+
elif score >= 20:
|
|
120
|
+
return f"BELOW AVERAGE: {ratio_pct}% at {percentile}th percentile historically"
|
|
121
|
+
else:
|
|
122
|
+
return f"HISTORICALLY LOW: {ratio_pct}% at {percentile}th percentile historically"
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Component 1: Market Breadth (Overall) - Weight: 30%
|
|
4
|
+
|
|
5
|
+
Evaluates the overall market uptrend ratio level and trend direction.
|
|
6
|
+
Primary signal: ratio level mapped to scoring bands with trend adjustment.
|
|
7
|
+
|
|
8
|
+
Data Source: Timeseries "all" worksheet
|
|
9
|
+
|
|
10
|
+
Thresholds aligned with Monty's dashboard:
|
|
11
|
+
- Overbought: 37% (upper threshold)
|
|
12
|
+
- Oversold: 9.7% (lower threshold)
|
|
13
|
+
|
|
14
|
+
Scoring Bands (linear interpolation within each band):
|
|
15
|
+
>= 50% -> 90-100 (Strong Bull)
|
|
16
|
+
37-50% -> 70-89 (Bullish / above overbought threshold)
|
|
17
|
+
25-37% -> 40-69 (Neutral/Recovering)
|
|
18
|
+
9.7-25% -> 10-39 (Weak / between thresholds)
|
|
19
|
+
< 9.7% -> 0-9 (Crisis / below oversold threshold)
|
|
20
|
+
|
|
21
|
+
Trend adjustment: trend="up" & slope>0 -> +5, trend="down" & slope<0 -> -5
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from typing import Dict, List, Optional
|
|
25
|
+
|
|
26
|
+
# Monty's official dashboard thresholds
|
|
27
|
+
UPPER_THRESHOLD = 0.37 # Overbought
|
|
28
|
+
LOWER_THRESHOLD = 0.097 # Oversold
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def calculate_market_breadth(latest_all: Optional[Dict],
|
|
32
|
+
all_timeseries: List[Dict]) -> Dict:
|
|
33
|
+
"""
|
|
34
|
+
Calculate market breadth score from overall uptrend ratio.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
latest_all: Most recent "all" timeseries row
|
|
38
|
+
all_timeseries: Full "all" timeseries sorted by date ascending
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Dict with score (0-100), signal, and detail fields
|
|
42
|
+
"""
|
|
43
|
+
if not latest_all or latest_all.get("ratio") is None:
|
|
44
|
+
return {
|
|
45
|
+
"score": 50,
|
|
46
|
+
"signal": "NO DATA: Overall uptrend ratio unavailable (neutral default)",
|
|
47
|
+
"data_available": False,
|
|
48
|
+
"ratio": None,
|
|
49
|
+
"ma_10": None,
|
|
50
|
+
"trend": None,
|
|
51
|
+
"slope": None,
|
|
52
|
+
"distance_from_upper": None,
|
|
53
|
+
"distance_from_lower": None,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
ratio = latest_all["ratio"]
|
|
57
|
+
ma_10 = latest_all.get("ma_10")
|
|
58
|
+
trend = latest_all.get("trend", "")
|
|
59
|
+
slope = latest_all.get("slope")
|
|
60
|
+
|
|
61
|
+
# Map ratio to base score via linear interpolation
|
|
62
|
+
base_score = _ratio_to_score(ratio)
|
|
63
|
+
|
|
64
|
+
# Trend adjustment
|
|
65
|
+
trend_adj = 0
|
|
66
|
+
if trend.lower() == "up" and slope is not None and slope > 0:
|
|
67
|
+
trend_adj = 5
|
|
68
|
+
elif trend.lower() == "down" and slope is not None and slope < 0:
|
|
69
|
+
trend_adj = -5
|
|
70
|
+
|
|
71
|
+
score = round(min(100, max(0, base_score + trend_adj)))
|
|
72
|
+
|
|
73
|
+
# Signal label
|
|
74
|
+
signal = _score_to_signal(score, ratio, trend)
|
|
75
|
+
|
|
76
|
+
# Key distances from Monty's official thresholds
|
|
77
|
+
distance_from_upper = round(ratio - UPPER_THRESHOLD, 4) if ratio is not None else None
|
|
78
|
+
distance_from_lower = round(ratio - LOWER_THRESHOLD, 4) if ratio is not None else None
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
"score": score,
|
|
82
|
+
"signal": signal,
|
|
83
|
+
"data_available": True,
|
|
84
|
+
"ratio": ratio,
|
|
85
|
+
"ratio_pct": round(ratio * 100, 1) if ratio is not None else None,
|
|
86
|
+
"ma_10": ma_10,
|
|
87
|
+
"ma_10_pct": round(ma_10 * 100, 1) if ma_10 is not None else None,
|
|
88
|
+
"trend": trend,
|
|
89
|
+
"slope": slope,
|
|
90
|
+
"trend_adjustment": trend_adj,
|
|
91
|
+
"distance_from_upper": distance_from_upper,
|
|
92
|
+
"distance_from_lower": distance_from_lower,
|
|
93
|
+
"upper_threshold": UPPER_THRESHOLD,
|
|
94
|
+
"lower_threshold": LOWER_THRESHOLD,
|
|
95
|
+
"date": latest_all.get("date", "N/A"),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _ratio_to_score(ratio: float) -> float:
|
|
100
|
+
"""Map uptrend ratio (0-1 scale) to base score (0-100).
|
|
101
|
+
|
|
102
|
+
Aligned with Monty's dashboard thresholds:
|
|
103
|
+
Upper (Overbought) = 0.37, Lower (Oversold) = 0.097
|
|
104
|
+
|
|
105
|
+
Scoring bands with linear interpolation:
|
|
106
|
+
>= 0.50 -> 90-100 (Strong Bull)
|
|
107
|
+
0.37-0.50 -> 70-89 (Bullish, above overbought)
|
|
108
|
+
0.25-0.37 -> 40-69 (Neutral/Recovering)
|
|
109
|
+
0.097-0.25 -> 10-39 (Weak, between thresholds)
|
|
110
|
+
< 0.097 -> 0-9 (Crisis, below oversold)
|
|
111
|
+
"""
|
|
112
|
+
if ratio >= 0.50:
|
|
113
|
+
# 0.50 -> 90, 0.60+ -> 100
|
|
114
|
+
return min(100, 90 + (ratio - 0.50) / 0.10 * 10)
|
|
115
|
+
elif ratio >= UPPER_THRESHOLD:
|
|
116
|
+
# 0.37 -> 70, 0.50 -> 89
|
|
117
|
+
return 70 + (ratio - UPPER_THRESHOLD) / (0.50 - UPPER_THRESHOLD) * 19
|
|
118
|
+
elif ratio >= 0.25:
|
|
119
|
+
# 0.25 -> 40, 0.37 -> 69
|
|
120
|
+
return 40 + (ratio - 0.25) / (UPPER_THRESHOLD - 0.25) * 29
|
|
121
|
+
elif ratio >= LOWER_THRESHOLD:
|
|
122
|
+
# 0.097 -> 10, 0.25 -> 39
|
|
123
|
+
return 10 + (ratio - LOWER_THRESHOLD) / (0.25 - LOWER_THRESHOLD) * 29
|
|
124
|
+
else:
|
|
125
|
+
# 0 -> 0, 0.097 -> 9
|
|
126
|
+
return max(0, ratio / LOWER_THRESHOLD * 9)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _score_to_signal(score: int, ratio: float, trend: str) -> str:
|
|
130
|
+
"""Map score to human-readable signal."""
|
|
131
|
+
ratio_pct = round(ratio * 100, 1)
|
|
132
|
+
trend_label = f", trend {trend}" if trend else ""
|
|
133
|
+
|
|
134
|
+
if score >= 90:
|
|
135
|
+
return f"STRONG BULL: {ratio_pct}% uptrend ratio{trend_label}"
|
|
136
|
+
elif score >= 70:
|
|
137
|
+
return f"BULLISH: {ratio_pct}% uptrend ratio{trend_label}"
|
|
138
|
+
elif score >= 45:
|
|
139
|
+
return f"NEUTRAL: {ratio_pct}% uptrend ratio{trend_label}"
|
|
140
|
+
elif score >= 30:
|
|
141
|
+
return f"WEAK: {ratio_pct}% uptrend ratio{trend_label}"
|
|
142
|
+
elif score >= 10:
|
|
143
|
+
return f"VERY WEAK: {ratio_pct}% uptrend ratio{trend_label}"
|
|
144
|
+
else:
|
|
145
|
+
return f"CRISIS: {ratio_pct}% uptrend ratio{trend_label}"
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Component 4: Momentum - Weight: 20%
|
|
4
|
+
|
|
5
|
+
Evaluates the rate of change (slope) and acceleration of the overall
|
|
6
|
+
uptrend ratio, plus sector-level slope breadth.
|
|
7
|
+
|
|
8
|
+
Data Source: Timeseries "all" + Sector Summary
|
|
9
|
+
|
|
10
|
+
Sub-scores:
|
|
11
|
+
- Slope Score (50%): Current slope mapped to 0-100 (typical range: -0.02~+0.02)
|
|
12
|
+
- Acceleration (30%): Recent 5-point slope avg vs prior 5-point
|
|
13
|
+
- Sector Slope Breadth (20%): Count of sectors with positive slope
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def calculate_momentum(all_timeseries: List[Dict],
|
|
20
|
+
sector_summary: List[Dict]) -> Dict:
|
|
21
|
+
"""
|
|
22
|
+
Calculate momentum score from slope, acceleration, and sector breadth.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
all_timeseries: Full "all" timeseries sorted by date ascending
|
|
26
|
+
sector_summary: Latest sector summary rows
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Dict with score (0-100), signal, and detail fields
|
|
30
|
+
"""
|
|
31
|
+
if not all_timeseries:
|
|
32
|
+
return {
|
|
33
|
+
"score": 50,
|
|
34
|
+
"signal": "NO DATA: Timeseries unavailable (neutral default)",
|
|
35
|
+
"data_available": False,
|
|
36
|
+
"slope": None,
|
|
37
|
+
"acceleration": None,
|
|
38
|
+
"sector_slope_breadth": None,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
latest = all_timeseries[-1]
|
|
42
|
+
current_slope = latest.get("slope")
|
|
43
|
+
|
|
44
|
+
# Sub-score 1: Slope Score (50%)
|
|
45
|
+
slope_score = _score_slope(current_slope) if current_slope is not None else 50
|
|
46
|
+
|
|
47
|
+
# Sub-score 2: Acceleration (30%)
|
|
48
|
+
accel_score, accel_value, accel_label = _score_acceleration(all_timeseries)
|
|
49
|
+
|
|
50
|
+
# Sub-score 3: Sector Slope Breadth (20%)
|
|
51
|
+
breadth_score, positive_slope_count, total_sectors = _score_sector_slope_breadth(
|
|
52
|
+
sector_summary
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Composite
|
|
56
|
+
raw_score = slope_score * 0.50 + accel_score * 0.30 + breadth_score * 0.20
|
|
57
|
+
score = round(min(100, max(0, raw_score)))
|
|
58
|
+
|
|
59
|
+
signal = _build_signal(score, current_slope, accel_label)
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
"score": score,
|
|
63
|
+
"signal": signal,
|
|
64
|
+
"data_available": True,
|
|
65
|
+
"slope": current_slope,
|
|
66
|
+
"slope_score": round(slope_score),
|
|
67
|
+
"acceleration": accel_value,
|
|
68
|
+
"acceleration_label": accel_label,
|
|
69
|
+
"acceleration_score": round(accel_score),
|
|
70
|
+
"sector_positive_slope_count": positive_slope_count,
|
|
71
|
+
"sector_total": total_sectors,
|
|
72
|
+
"sector_slope_breadth_score": round(breadth_score),
|
|
73
|
+
"date": latest.get("date", "N/A"),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _score_slope(slope: float) -> float:
|
|
78
|
+
"""Map current slope to 0-100 score.
|
|
79
|
+
|
|
80
|
+
Typical slope range: -0.02 to +0.02
|
|
81
|
+
Extreme range: -0.03 to +0.03
|
|
82
|
+
|
|
83
|
+
>= +0.02 -> 95-100 (strong bullish momentum)
|
|
84
|
+
+0.01~+0.02 -> 75-94
|
|
85
|
+
0~+0.01 -> 55-74 (mild positive)
|
|
86
|
+
-0.01~0 -> 35-54 (mild negative)
|
|
87
|
+
-0.02~-0.01 -> 10-34
|
|
88
|
+
< -0.02 -> 0-9 (strong bearish momentum)
|
|
89
|
+
"""
|
|
90
|
+
if slope >= 0.02:
|
|
91
|
+
return min(100, 95 + (slope - 0.02) / 0.01 * 5)
|
|
92
|
+
elif slope >= 0.01:
|
|
93
|
+
return 75 + (slope - 0.01) / 0.01 * 19
|
|
94
|
+
elif slope >= 0:
|
|
95
|
+
return 55 + slope / 0.01 * 19
|
|
96
|
+
elif slope >= -0.01:
|
|
97
|
+
return 35 + (slope + 0.01) / 0.01 * 19
|
|
98
|
+
elif slope >= -0.02:
|
|
99
|
+
return 10 + (slope + 0.02) / 0.01 * 24
|
|
100
|
+
else:
|
|
101
|
+
return max(0, 9 + (slope + 0.02) / 0.01 * 9)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _score_acceleration(timeseries: List[Dict]) -> tuple:
|
|
105
|
+
"""Calculate acceleration: recent vs prior slope average.
|
|
106
|
+
|
|
107
|
+
Returns: (score, acceleration_value, label)
|
|
108
|
+
"""
|
|
109
|
+
if len(timeseries) < 10:
|
|
110
|
+
return 50, None, "insufficient_data"
|
|
111
|
+
|
|
112
|
+
# Get slopes for recent 10 data points
|
|
113
|
+
recent_slopes = []
|
|
114
|
+
for row in timeseries[-10:]:
|
|
115
|
+
s = row.get("slope")
|
|
116
|
+
if s is not None:
|
|
117
|
+
recent_slopes.append(s)
|
|
118
|
+
|
|
119
|
+
if len(recent_slopes) < 10:
|
|
120
|
+
return 50, None, "insufficient_data"
|
|
121
|
+
|
|
122
|
+
recent_5_avg = sum(recent_slopes[-5:]) / 5
|
|
123
|
+
prior_5_avg = sum(recent_slopes[:5]) / 5
|
|
124
|
+
|
|
125
|
+
acceleration = recent_5_avg - prior_5_avg
|
|
126
|
+
|
|
127
|
+
if acceleration > 0.005:
|
|
128
|
+
label = "strong_accelerating"
|
|
129
|
+
score = 90
|
|
130
|
+
elif acceleration > 0.001:
|
|
131
|
+
label = "accelerating"
|
|
132
|
+
score = 75
|
|
133
|
+
elif acceleration > -0.001:
|
|
134
|
+
label = "steady"
|
|
135
|
+
score = 50
|
|
136
|
+
elif acceleration > -0.005:
|
|
137
|
+
label = "decelerating"
|
|
138
|
+
score = 25
|
|
139
|
+
else:
|
|
140
|
+
label = "strong_decelerating"
|
|
141
|
+
score = 10
|
|
142
|
+
|
|
143
|
+
return score, round(acceleration, 6), label
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _score_sector_slope_breadth(sector_summary: List[Dict]) -> tuple:
|
|
147
|
+
"""Score based on count of sectors with positive slope.
|
|
148
|
+
|
|
149
|
+
Returns: (score, positive_count, total_count)
|
|
150
|
+
"""
|
|
151
|
+
if not sector_summary:
|
|
152
|
+
return 50, 0, 0
|
|
153
|
+
|
|
154
|
+
total = len(sector_summary)
|
|
155
|
+
positive = sum(
|
|
156
|
+
1 for s in sector_summary
|
|
157
|
+
if s.get("Slope") is not None and s["Slope"] > 0
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if total == 0:
|
|
161
|
+
return 50, 0, 0
|
|
162
|
+
|
|
163
|
+
# Linear mapping: 0 sectors -> 0, all sectors -> 100
|
|
164
|
+
score = (positive / total) * 100
|
|
165
|
+
|
|
166
|
+
return score, positive, total
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _build_signal(score: int, slope: Optional[float], accel_label: str) -> str:
|
|
170
|
+
"""Build human-readable signal."""
|
|
171
|
+
slope_str = f"slope={slope:.4f}" if slope is not None else "slope=N/A"
|
|
172
|
+
accel_str = accel_label.replace("_", " ")
|
|
173
|
+
|
|
174
|
+
if score >= 80:
|
|
175
|
+
return f"STRONG MOMENTUM: {slope_str}, {accel_str}"
|
|
176
|
+
elif score >= 60:
|
|
177
|
+
return f"POSITIVE MOMENTUM: {slope_str}, {accel_str}"
|
|
178
|
+
elif score >= 40:
|
|
179
|
+
return f"NEUTRAL MOMENTUM: {slope_str}, {accel_str}"
|
|
180
|
+
elif score >= 20:
|
|
181
|
+
return f"WEAK MOMENTUM: {slope_str}, {accel_str}"
|
|
182
|
+
else:
|
|
183
|
+
return f"NEGATIVE MOMENTUM: {slope_str}, {accel_str}"
|