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,343 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
C Component - Current Quarterly Earnings Calculator
|
|
4
|
+
|
|
5
|
+
Calculates CANSLIM 'C' component score based on quarterly EPS and revenue growth.
|
|
6
|
+
|
|
7
|
+
O'Neil's Rule: "Look for companies whose current quarterly earnings per share
|
|
8
|
+
are up at least 18-20% compared to the same quarter the prior year."
|
|
9
|
+
|
|
10
|
+
Scoring:
|
|
11
|
+
- 100 points: EPS +50%+ YoY AND Revenue +25%+ YoY (explosive growth)
|
|
12
|
+
- 80 points: EPS +30-49% AND Revenue +15%+ (strong growth)
|
|
13
|
+
- 60 points: EPS +18-29% AND Revenue +10%+ (meets CANSLIM minimum)
|
|
14
|
+
- 40 points: EPS +10-17% (below threshold)
|
|
15
|
+
- 0 points: EPS <10% or negative
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Dict, List, Optional
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def calculate_quarterly_growth(income_statements: List[Dict]) -> Dict:
|
|
22
|
+
"""
|
|
23
|
+
Calculate quarterly EPS and revenue growth (year-over-year)
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
income_statements: List of quarterly income statements from FMP API
|
|
27
|
+
(most recent quarter first, needs at least 5 quarters)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Dict with:
|
|
31
|
+
- score: 0-100 points
|
|
32
|
+
- latest_qtr_eps_growth: YoY EPS growth percentage
|
|
33
|
+
- latest_qtr_revenue_growth: YoY revenue growth percentage
|
|
34
|
+
- latest_eps: Most recent quarterly EPS
|
|
35
|
+
- year_ago_eps: EPS from same quarter last year
|
|
36
|
+
- latest_revenue: Most recent quarterly revenue
|
|
37
|
+
- year_ago_revenue: Revenue from same quarter last year
|
|
38
|
+
- interpretation: Human-readable interpretation
|
|
39
|
+
- error: Error message if calculation failed
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
>>> income_stmts = client.get_income_statement("NVDA", period="quarter", limit=8)
|
|
43
|
+
>>> result = calculate_quarterly_growth(income_stmts)
|
|
44
|
+
>>> print(f"C Score: {result['score']}, EPS Growth: {result['latest_qtr_eps_growth']:.1f}%")
|
|
45
|
+
"""
|
|
46
|
+
# Validate input
|
|
47
|
+
if not income_statements or len(income_statements) < 5:
|
|
48
|
+
return {
|
|
49
|
+
"score": 0,
|
|
50
|
+
"error": "Insufficient quarterly data (need at least 5 quarters for YoY comparison)",
|
|
51
|
+
"latest_qtr_eps_growth": None,
|
|
52
|
+
"latest_qtr_revenue_growth": None,
|
|
53
|
+
"latest_eps": None,
|
|
54
|
+
"year_ago_eps": None,
|
|
55
|
+
"latest_revenue": None,
|
|
56
|
+
"year_ago_revenue": None,
|
|
57
|
+
"interpretation": "Data unavailable"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Extract most recent quarter (index 0) and year-ago quarter (index 4)
|
|
61
|
+
latest = income_statements[0]
|
|
62
|
+
year_ago = income_statements[4]
|
|
63
|
+
|
|
64
|
+
# Extract EPS (try multiple field names for compatibility)
|
|
65
|
+
latest_eps = latest.get("eps") or latest.get("epsdiluted") or latest.get("netIncomePerShare")
|
|
66
|
+
year_ago_eps = year_ago.get("eps") or year_ago.get("epsdiluted") or year_ago.get("netIncomePerShare")
|
|
67
|
+
|
|
68
|
+
# Extract revenue
|
|
69
|
+
latest_revenue = latest.get("revenue")
|
|
70
|
+
year_ago_revenue = year_ago.get("revenue")
|
|
71
|
+
|
|
72
|
+
# Validate extracted data
|
|
73
|
+
if latest_eps is None or year_ago_eps is None:
|
|
74
|
+
return {
|
|
75
|
+
"score": 0,
|
|
76
|
+
"error": "EPS data missing or invalid",
|
|
77
|
+
"latest_qtr_eps_growth": None,
|
|
78
|
+
"latest_qtr_revenue_growth": None,
|
|
79
|
+
"latest_eps": latest_eps,
|
|
80
|
+
"year_ago_eps": year_ago_eps,
|
|
81
|
+
"latest_revenue": latest_revenue,
|
|
82
|
+
"year_ago_revenue": year_ago_revenue,
|
|
83
|
+
"interpretation": "EPS data unavailable"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if latest_revenue is None or year_ago_revenue is None or year_ago_revenue == 0:
|
|
87
|
+
return {
|
|
88
|
+
"score": 0,
|
|
89
|
+
"error": "Revenue data missing or invalid",
|
|
90
|
+
"latest_qtr_eps_growth": None,
|
|
91
|
+
"latest_qtr_revenue_growth": None,
|
|
92
|
+
"latest_eps": latest_eps,
|
|
93
|
+
"year_ago_eps": year_ago_eps,
|
|
94
|
+
"latest_revenue": latest_revenue,
|
|
95
|
+
"year_ago_revenue": year_ago_revenue,
|
|
96
|
+
"interpretation": "Revenue data unavailable"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Calculate year-over-year growth
|
|
100
|
+
# Use abs() for denominator to handle negative EPS (turnaround situations)
|
|
101
|
+
if year_ago_eps == 0:
|
|
102
|
+
# Handle zero/negative EPS edge case
|
|
103
|
+
if latest_eps > 0 and year_ago_eps <= 0:
|
|
104
|
+
# Turnaround situation (negative to positive)
|
|
105
|
+
eps_growth = 999.9 # Cap at very high growth
|
|
106
|
+
else:
|
|
107
|
+
eps_growth = 0
|
|
108
|
+
else:
|
|
109
|
+
eps_growth = ((latest_eps - year_ago_eps) / abs(year_ago_eps)) * 100
|
|
110
|
+
|
|
111
|
+
revenue_growth = ((latest_revenue - year_ago_revenue) / year_ago_revenue) * 100
|
|
112
|
+
|
|
113
|
+
# Calculate score
|
|
114
|
+
score = score_current_earnings(eps_growth, revenue_growth)
|
|
115
|
+
|
|
116
|
+
# Generate interpretation
|
|
117
|
+
interpretation = interpret_earnings_score(score, eps_growth, revenue_growth)
|
|
118
|
+
|
|
119
|
+
# Quality check: flag if revenue growth significantly lags EPS growth
|
|
120
|
+
quality_warning = None
|
|
121
|
+
if revenue_growth < (eps_growth * 0.5) and eps_growth > 20:
|
|
122
|
+
quality_warning = ("Revenue growth significantly lags EPS growth - "
|
|
123
|
+
"investigate earnings quality (potential buyback-driven)")
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
"score": score,
|
|
127
|
+
"latest_qtr_eps_growth": round(eps_growth, 1),
|
|
128
|
+
"latest_qtr_revenue_growth": round(revenue_growth, 1),
|
|
129
|
+
"latest_eps": latest_eps,
|
|
130
|
+
"year_ago_eps": year_ago_eps,
|
|
131
|
+
"latest_revenue": latest_revenue,
|
|
132
|
+
"year_ago_revenue": year_ago_revenue,
|
|
133
|
+
"latest_qtr_date": latest.get("date"),
|
|
134
|
+
"year_ago_qtr_date": year_ago.get("date"),
|
|
135
|
+
"interpretation": interpretation,
|
|
136
|
+
"quality_warning": quality_warning,
|
|
137
|
+
"error": None
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def score_current_earnings(eps_growth: float, revenue_growth: float) -> int:
|
|
142
|
+
"""
|
|
143
|
+
Score C component based on quarterly EPS and revenue growth
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
eps_growth: Year-over-year EPS growth percentage
|
|
147
|
+
revenue_growth: Year-over-year revenue growth percentage
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Score (0-100)
|
|
151
|
+
|
|
152
|
+
Scoring Logic (from scoring_system.md):
|
|
153
|
+
- 100: EPS >=50% AND Revenue >=25% (explosive)
|
|
154
|
+
- 80: EPS 30-49% AND Revenue >=15% (strong)
|
|
155
|
+
- 60: EPS 18-29% AND Revenue >=10% (meets CANSLIM minimum)
|
|
156
|
+
- 40: EPS 10-17% (below threshold)
|
|
157
|
+
- 0: EPS <10% (weak/negative)
|
|
158
|
+
"""
|
|
159
|
+
# Exceptional growth
|
|
160
|
+
if eps_growth >= 50 and revenue_growth >= 25:
|
|
161
|
+
return 100
|
|
162
|
+
|
|
163
|
+
# Strong growth
|
|
164
|
+
if eps_growth >= 30 and revenue_growth >= 15:
|
|
165
|
+
return 80
|
|
166
|
+
|
|
167
|
+
# Meets CANSLIM minimum (18%+ EPS growth)
|
|
168
|
+
if eps_growth >= 18 and revenue_growth >= 10:
|
|
169
|
+
return 60
|
|
170
|
+
|
|
171
|
+
# Below threshold but positive
|
|
172
|
+
if eps_growth >= 10:
|
|
173
|
+
return 40
|
|
174
|
+
|
|
175
|
+
# Weak or negative growth
|
|
176
|
+
return 0
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def interpret_earnings_score(score: int, eps_growth: float, revenue_growth: float) -> str:
|
|
180
|
+
"""
|
|
181
|
+
Generate human-readable interpretation of C component score
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
score: Component score (0-100)
|
|
185
|
+
eps_growth: YoY EPS growth percentage
|
|
186
|
+
revenue_growth: YoY revenue growth percentage
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Interpretation string
|
|
190
|
+
"""
|
|
191
|
+
if score >= 90:
|
|
192
|
+
return (f"Exceptional - Explosive earnings acceleration "
|
|
193
|
+
f"(EPS +{eps_growth:.1f}%, Revenue +{revenue_growth:.1f}%)")
|
|
194
|
+
|
|
195
|
+
elif score >= 70:
|
|
196
|
+
return (f"Strong - Well above CANSLIM threshold "
|
|
197
|
+
f"(EPS +{eps_growth:.1f}%, Revenue +{revenue_growth:.1f}%)")
|
|
198
|
+
|
|
199
|
+
elif score >= 50:
|
|
200
|
+
return (f"Acceptable - Meets CANSLIM minimum 18% threshold "
|
|
201
|
+
f"(EPS +{eps_growth:.1f}%, Revenue +{revenue_growth:.1f}%)")
|
|
202
|
+
|
|
203
|
+
elif score >= 30:
|
|
204
|
+
return (f"Below threshold - Insufficient growth "
|
|
205
|
+
f"(EPS +{eps_growth:.1f}%, Revenue +{revenue_growth:.1f}%)")
|
|
206
|
+
|
|
207
|
+
else:
|
|
208
|
+
return (f"Weak - Does not meet CANSLIM criteria "
|
|
209
|
+
f"(EPS {eps_growth:+.1f}%, Revenue {revenue_growth:+.1f}%)")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def detect_earnings_acceleration(income_statements: List[Dict]) -> Dict:
|
|
213
|
+
"""
|
|
214
|
+
Detect if earnings are accelerating or decelerating (trend analysis)
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
income_statements: List of quarterly income statements (recent first)
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Dict with:
|
|
221
|
+
- trend: "accelerating", "stable", or "decelerating"
|
|
222
|
+
- recent_growth: Most recent quarter YoY growth
|
|
223
|
+
- prior_growth: Prior quarter YoY growth
|
|
224
|
+
- interpretation: Trend description
|
|
225
|
+
|
|
226
|
+
Note:
|
|
227
|
+
Earnings acceleration (recent > prior) is bullish signal per O'Neil.
|
|
228
|
+
Deceleration is early warning of potential weakness.
|
|
229
|
+
"""
|
|
230
|
+
if len(income_statements) < 6:
|
|
231
|
+
return {
|
|
232
|
+
"trend": "unknown",
|
|
233
|
+
"recent_growth": None,
|
|
234
|
+
"prior_growth": None,
|
|
235
|
+
"interpretation": "Insufficient data for trend analysis"
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# Most recent quarter vs year-ago
|
|
239
|
+
recent_eps = income_statements[0].get("eps", 0)
|
|
240
|
+
recent_year_ago_eps = income_statements[4].get("eps", 0.01)
|
|
241
|
+
recent_growth = ((recent_eps - recent_year_ago_eps) / abs(recent_year_ago_eps)) * 100 if recent_year_ago_eps else 0
|
|
242
|
+
|
|
243
|
+
# Prior quarter vs its year-ago
|
|
244
|
+
prior_eps = income_statements[1].get("eps", 0)
|
|
245
|
+
prior_year_ago_eps = income_statements[5].get("eps", 0.01)
|
|
246
|
+
prior_growth = ((prior_eps - prior_year_ago_eps) / abs(prior_year_ago_eps)) * 100 if prior_year_ago_eps else 0
|
|
247
|
+
|
|
248
|
+
# Determine trend
|
|
249
|
+
if recent_growth > prior_growth + 5: # 5% threshold for significance
|
|
250
|
+
trend = "accelerating"
|
|
251
|
+
interpretation = f"Earnings accelerating ({recent_growth:.1f}% vs {prior_growth:.1f}% prior quarter)"
|
|
252
|
+
elif recent_growth < prior_growth - 5:
|
|
253
|
+
trend = "decelerating"
|
|
254
|
+
interpretation = f"Earnings decelerating ({recent_growth:.1f}% vs {prior_growth:.1f}% prior quarter) - Warning sign"
|
|
255
|
+
else:
|
|
256
|
+
trend = "stable"
|
|
257
|
+
interpretation = f"Earnings stable ({recent_growth:.1f}% vs {prior_growth:.1f}% prior quarter)"
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
"trend": trend,
|
|
261
|
+
"recent_growth": round(recent_growth, 1),
|
|
262
|
+
"prior_growth": round(prior_growth, 1),
|
|
263
|
+
"interpretation": interpretation
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# Example usage and testing
|
|
268
|
+
if __name__ == "__main__":
|
|
269
|
+
print("Testing Earnings Calculator (C Component)...\n")
|
|
270
|
+
|
|
271
|
+
# Test case 1: Exceptional growth (should score 100)
|
|
272
|
+
test_data_exceptional = [
|
|
273
|
+
{"date": "2023-06-30", "eps": 2.70, "revenue": 13507000000}, # Q2 2023
|
|
274
|
+
{"date": "2023-03-31", "eps": 1.09, "revenue": 7192000000},
|
|
275
|
+
{"date": "2022-12-31", "eps": 0.88, "revenue": 6051000000},
|
|
276
|
+
{"date": "2022-09-30", "eps": 0.58, "revenue": 5931000000},
|
|
277
|
+
{"date": "2022-06-30", "eps": 0.51, "revenue": 6704000000}, # Q2 2022 (year-ago)
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
result1 = calculate_quarterly_growth(test_data_exceptional)
|
|
281
|
+
print("Test 1: Exceptional Growth (NVDA-like)")
|
|
282
|
+
print(f" Score: {result1['score']}/100")
|
|
283
|
+
print(f" EPS Growth: {result1['latest_qtr_eps_growth']}%")
|
|
284
|
+
print(f" Revenue Growth: {result1['latest_qtr_revenue_growth']}%")
|
|
285
|
+
print(f" Interpretation: {result1['interpretation']}\n")
|
|
286
|
+
|
|
287
|
+
# Test case 2: Meets minimum (should score 60)
|
|
288
|
+
test_data_minimum = [
|
|
289
|
+
{"date": "2023-06-30", "eps": 1.20, "revenue": 10500000000},
|
|
290
|
+
{"date": "2023-03-31", "eps": 1.15, "revenue": 10200000000},
|
|
291
|
+
{"date": "2022-12-31", "eps": 1.10, "revenue": 10000000000},
|
|
292
|
+
{"date": "2022-09-30", "eps": 1.05, "revenue": 9800000000},
|
|
293
|
+
{"date": "2022-06-30", "eps": 1.00, "revenue": 9500000000}, # +20% EPS, +10.5% revenue
|
|
294
|
+
]
|
|
295
|
+
|
|
296
|
+
result2 = calculate_quarterly_growth(test_data_minimum)
|
|
297
|
+
print("Test 2: Meets CANSLIM Minimum")
|
|
298
|
+
print(f" Score: {result2['score']}/100")
|
|
299
|
+
print(f" EPS Growth: {result2['latest_qtr_eps_growth']}%")
|
|
300
|
+
print(f" Revenue Growth: {result2['latest_qtr_revenue_growth']}%")
|
|
301
|
+
print(f" Interpretation: {result2['interpretation']}\n")
|
|
302
|
+
|
|
303
|
+
# Test case 3: Below threshold (should score 40)
|
|
304
|
+
test_data_weak = [
|
|
305
|
+
{"date": "2023-06-30", "eps": 1.12, "revenue": 10200000000},
|
|
306
|
+
{"date": "2023-03-31", "eps": 1.08, "revenue": 10100000000},
|
|
307
|
+
{"date": "2022-12-31", "eps": 1.05, "revenue": 10000000000},
|
|
308
|
+
{"date": "2022-09-30", "eps": 1.02, "revenue": 9900000000},
|
|
309
|
+
{"date": "2022-06-30", "eps": 1.00, "revenue": 9800000000}, # +12% EPS, +4% revenue
|
|
310
|
+
]
|
|
311
|
+
|
|
312
|
+
result3 = calculate_quarterly_growth(test_data_weak)
|
|
313
|
+
print("Test 3: Below Threshold")
|
|
314
|
+
print(f" Score: {result3['score']}/100")
|
|
315
|
+
print(f" EPS Growth: {result3['latest_qtr_eps_growth']}%")
|
|
316
|
+
print(f" Revenue Growth: {result3['latest_qtr_revenue_growth']}%")
|
|
317
|
+
print(f" Interpretation: {result3['interpretation']}\n")
|
|
318
|
+
|
|
319
|
+
# Test case 4: Turnaround (negative to positive EPS)
|
|
320
|
+
test_data_turnaround = [
|
|
321
|
+
{"date": "2023-06-30", "eps": 0.50, "revenue": 10000000000},
|
|
322
|
+
{"date": "2023-03-31", "eps": 0.20, "revenue": 9500000000},
|
|
323
|
+
{"date": "2022-12-31", "eps": -0.10, "revenue": 9000000000},
|
|
324
|
+
{"date": "2022-09-30", "eps": -0.30, "revenue": 8500000000},
|
|
325
|
+
{"date": "2022-06-30", "eps": -0.40, "revenue": 8000000000}, # Turnaround situation
|
|
326
|
+
]
|
|
327
|
+
|
|
328
|
+
result4 = calculate_quarterly_growth(test_data_turnaround)
|
|
329
|
+
print("Test 4: Turnaround (Negative to Positive)")
|
|
330
|
+
print(f" Score: {result4['score']}/100")
|
|
331
|
+
print(f" EPS Growth: {result4['latest_qtr_eps_growth']}%")
|
|
332
|
+
print(f" Revenue Growth: {result4['latest_qtr_revenue_growth']}%")
|
|
333
|
+
print(f" Interpretation: {result4['interpretation']}\n")
|
|
334
|
+
|
|
335
|
+
# Test acceleration detection
|
|
336
|
+
print("Test 5: Acceleration Detection")
|
|
337
|
+
accel = detect_earnings_acceleration(test_data_exceptional)
|
|
338
|
+
print(f" Trend: {accel['trend']}")
|
|
339
|
+
print(f" Recent growth: {accel['recent_growth']}%")
|
|
340
|
+
print(f" Prior growth: {accel['prior_growth']}%")
|
|
341
|
+
print(f" Interpretation: {accel['interpretation']}")
|
|
342
|
+
|
|
343
|
+
print("\n✓ All tests completed")
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
A Component - Annual EPS Growth Calculator
|
|
4
|
+
|
|
5
|
+
Calculates CANSLIM 'A' component score based on multi-year EPS CAGR and growth stability.
|
|
6
|
+
|
|
7
|
+
O'Neil's Rule: "Annual earnings per share should be up 25% or more in each
|
|
8
|
+
of the last three years."
|
|
9
|
+
|
|
10
|
+
Scoring:
|
|
11
|
+
- 90-100: CAGR 40%+, stable growth (no down years)
|
|
12
|
+
- 70-89: CAGR 30-39%, stable
|
|
13
|
+
- 50-69: CAGR 25-29% (meets CANSLIM minimum)
|
|
14
|
+
- 30-49: CAGR 15-24% (below threshold)
|
|
15
|
+
- 0-29: CAGR <15% or erratic growth
|
|
16
|
+
|
|
17
|
+
Bonus: +10 points for stability (no down years)
|
|
18
|
+
Penalty: -20% if revenue CAGR significantly lags EPS CAGR (buyback-driven growth)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from typing import Dict, List, Optional
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def calculate_annual_growth(income_statements: List[Dict]) -> Dict:
|
|
25
|
+
"""
|
|
26
|
+
Calculate 3-year EPS CAGR and growth stability
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
income_statements: List of annual income statements from FMP API
|
|
30
|
+
(most recent year first, needs at least 4 years for 3-year CAGR)
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Dict with:
|
|
34
|
+
- score: 0-100 points
|
|
35
|
+
- eps_cagr_3yr: 3-year EPS compound annual growth rate (%)
|
|
36
|
+
- revenue_cagr_3yr: 3-year revenue CAGR (%) for validation
|
|
37
|
+
- stability: "stable" or "erratic"
|
|
38
|
+
- eps_values: List of EPS values (chronological)
|
|
39
|
+
- interpretation: Human-readable interpretation
|
|
40
|
+
- error: Error message if calculation failed
|
|
41
|
+
"""
|
|
42
|
+
# Validate input
|
|
43
|
+
if not income_statements or len(income_statements) < 4:
|
|
44
|
+
return {
|
|
45
|
+
"score": 50, # Default for insufficient data (neutral)
|
|
46
|
+
"error": "Insufficient annual data (need at least 4 years for 3-year CAGR)",
|
|
47
|
+
"eps_cagr_3yr": None,
|
|
48
|
+
"revenue_cagr_3yr": None,
|
|
49
|
+
"stability": "unknown",
|
|
50
|
+
"eps_values": None,
|
|
51
|
+
"interpretation": "Data unavailable"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Extract EPS for last 4 years (most recent first in API response)
|
|
55
|
+
eps_values = []
|
|
56
|
+
revenue_values = []
|
|
57
|
+
|
|
58
|
+
for i in range(4):
|
|
59
|
+
stmt = income_statements[i]
|
|
60
|
+
eps = stmt.get("eps") or stmt.get("epsdiluted")
|
|
61
|
+
revenue = stmt.get("revenue")
|
|
62
|
+
|
|
63
|
+
if eps is None:
|
|
64
|
+
return {
|
|
65
|
+
"score": 0,
|
|
66
|
+
"error": f"Missing EPS data for year {stmt.get('date', 'unknown')}",
|
|
67
|
+
"eps_cagr_3yr": None,
|
|
68
|
+
"revenue_cagr_3yr": None,
|
|
69
|
+
"stability": "unknown",
|
|
70
|
+
"eps_values": None,
|
|
71
|
+
"interpretation": "EPS data incomplete"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if eps <= 0:
|
|
75
|
+
return {
|
|
76
|
+
"score": 0,
|
|
77
|
+
"error": f"Negative or zero EPS in year {stmt.get('date', 'unknown')} - cannot calculate CAGR",
|
|
78
|
+
"eps_cagr_3yr": None,
|
|
79
|
+
"revenue_cagr_3yr": None,
|
|
80
|
+
"stability": "unknown",
|
|
81
|
+
"eps_values": None,
|
|
82
|
+
"interpretation": "Negative EPS - not a CANSLIM candidate"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
eps_values.append(eps)
|
|
86
|
+
revenue_values.append(revenue if revenue else 0)
|
|
87
|
+
|
|
88
|
+
# Reverse to chronological order (oldest first)
|
|
89
|
+
eps_values_chrono = eps_values[::-1]
|
|
90
|
+
revenue_values_chrono = revenue_values[::-1]
|
|
91
|
+
|
|
92
|
+
# Calculate 3-year CAGR
|
|
93
|
+
# CAGR = ((Ending Value / Beginning Value) ^ (1 / Number of Years)) - 1
|
|
94
|
+
eps_start = eps_values_chrono[0] # 3 years ago
|
|
95
|
+
eps_end = eps_values_chrono[3] # Current year
|
|
96
|
+
years = 3
|
|
97
|
+
|
|
98
|
+
eps_cagr = (((eps_end / eps_start) ** (1 / years)) - 1) * 100
|
|
99
|
+
|
|
100
|
+
# Calculate revenue CAGR for validation
|
|
101
|
+
revenue_start = revenue_values_chrono[0]
|
|
102
|
+
revenue_end = revenue_values_chrono[3]
|
|
103
|
+
|
|
104
|
+
if revenue_start > 0:
|
|
105
|
+
revenue_cagr = (((revenue_end / revenue_start) ** (1 / years)) - 1) * 100
|
|
106
|
+
else:
|
|
107
|
+
revenue_cagr = 0
|
|
108
|
+
|
|
109
|
+
# Check growth stability (no down years)
|
|
110
|
+
stable = all(eps_values_chrono[i] >= eps_values_chrono[i - 1]
|
|
111
|
+
for i in range(1, len(eps_values_chrono)))
|
|
112
|
+
|
|
113
|
+
# Calculate score
|
|
114
|
+
score = score_annual_growth(eps_cagr, revenue_cagr, stable)
|
|
115
|
+
|
|
116
|
+
# Generate interpretation
|
|
117
|
+
interpretation = interpret_growth_score(score, eps_cagr, stable)
|
|
118
|
+
|
|
119
|
+
# Quality warning
|
|
120
|
+
quality_warning = None
|
|
121
|
+
if revenue_cagr < (eps_cagr * 0.5) and eps_cagr > 20:
|
|
122
|
+
quality_warning = ("Revenue CAGR significantly lags EPS CAGR - "
|
|
123
|
+
"growth may be buyback-driven rather than organic")
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
"score": score,
|
|
127
|
+
"eps_cagr_3yr": round(eps_cagr, 1),
|
|
128
|
+
"revenue_cagr_3yr": round(revenue_cagr, 1),
|
|
129
|
+
"stability": "stable" if stable else "erratic",
|
|
130
|
+
"eps_values": [round(eps, 2) for eps in eps_values_chrono],
|
|
131
|
+
"revenue_values": [int(rev) for rev in revenue_values_chrono],
|
|
132
|
+
"years_analyzed": len(eps_values_chrono),
|
|
133
|
+
"interpretation": interpretation,
|
|
134
|
+
"quality_warning": quality_warning,
|
|
135
|
+
"error": None
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def score_annual_growth(eps_cagr: float, revenue_cagr: float, stable: bool) -> int:
|
|
140
|
+
"""
|
|
141
|
+
Score A component based on EPS CAGR, revenue validation, and stability
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
eps_cagr: 3-year EPS compound annual growth rate (%)
|
|
145
|
+
revenue_cagr: 3-year revenue CAGR (%)
|
|
146
|
+
stable: True if no down years, False if erratic
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Score (0-100)
|
|
150
|
+
|
|
151
|
+
Scoring Logic:
|
|
152
|
+
- Base score from EPS CAGR:
|
|
153
|
+
- 40%+: 90 points
|
|
154
|
+
- 30-39%: 70 points
|
|
155
|
+
- 25-29%: 50 points (meets CANSLIM minimum)
|
|
156
|
+
- 15-24%: 30 points
|
|
157
|
+
- <15%: 0 points
|
|
158
|
+
- Penalty: -20% if revenue CAGR < 50% of EPS CAGR (buyback concern)
|
|
159
|
+
- Bonus: +10 points if stable growth (no down years)
|
|
160
|
+
"""
|
|
161
|
+
# Base score from EPS CAGR
|
|
162
|
+
if eps_cagr >= 40:
|
|
163
|
+
base_score = 90
|
|
164
|
+
elif eps_cagr >= 30:
|
|
165
|
+
base_score = 70
|
|
166
|
+
elif eps_cagr >= 25:
|
|
167
|
+
base_score = 50 # Meets CANSLIM minimum
|
|
168
|
+
elif eps_cagr >= 15:
|
|
169
|
+
base_score = 30
|
|
170
|
+
else:
|
|
171
|
+
base_score = 0
|
|
172
|
+
|
|
173
|
+
# Revenue growth validation penalty
|
|
174
|
+
if revenue_cagr < (eps_cagr * 0.5):
|
|
175
|
+
base_score = int(base_score * 0.8) # 20% penalty
|
|
176
|
+
|
|
177
|
+
# Stability bonus
|
|
178
|
+
if stable:
|
|
179
|
+
base_score += 10
|
|
180
|
+
|
|
181
|
+
# Cap at 100
|
|
182
|
+
return min(base_score, 100)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def interpret_growth_score(score: int, eps_cagr: float, stable: bool) -> str:
|
|
186
|
+
"""
|
|
187
|
+
Generate human-readable interpretation of A component score
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
score: Component score (0-100)
|
|
191
|
+
eps_cagr: 3-year EPS CAGR (%)
|
|
192
|
+
stable: Growth stability flag
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Interpretation string
|
|
196
|
+
"""
|
|
197
|
+
stability_text = "stable" if stable else "erratic"
|
|
198
|
+
|
|
199
|
+
if score >= 90:
|
|
200
|
+
return (f"Exceptional - {eps_cagr:.1f}% CAGR, {stability_text} growth trajectory")
|
|
201
|
+
|
|
202
|
+
elif score >= 70:
|
|
203
|
+
return (f"Strong - {eps_cagr:.1f}% CAGR, well above CANSLIM 25% threshold ({stability_text})")
|
|
204
|
+
|
|
205
|
+
elif score >= 50:
|
|
206
|
+
return (f"Acceptable - {eps_cagr:.1f}% CAGR meets CANSLIM minimum ({stability_text})")
|
|
207
|
+
|
|
208
|
+
elif score >= 30:
|
|
209
|
+
return (f"Below threshold - {eps_cagr:.1f}% CAGR insufficient (<25% required)")
|
|
210
|
+
|
|
211
|
+
else:
|
|
212
|
+
return (f"Weak - {eps_cagr:.1f}% CAGR does not meet CANSLIM criteria")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def check_consistency(income_statements: List[Dict]) -> Dict:
|
|
216
|
+
"""
|
|
217
|
+
Check year-over-year growth consistency (no down years is ideal)
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
income_statements: List of annual income statements (recent first)
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Dict with:
|
|
224
|
+
- down_years: Count of years with YoY decline
|
|
225
|
+
- consecutive_growth_years: Max consecutive years of growth
|
|
226
|
+
- growth_pattern: List of YoY growth rates
|
|
227
|
+
"""
|
|
228
|
+
if len(income_statements) < 2:
|
|
229
|
+
return {
|
|
230
|
+
"down_years": None,
|
|
231
|
+
"consecutive_growth_years": None,
|
|
232
|
+
"growth_pattern": None,
|
|
233
|
+
"interpretation": "Insufficient data"
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
eps_values = []
|
|
237
|
+
for stmt in income_statements:
|
|
238
|
+
eps = stmt.get("eps") or stmt.get("epsdiluted")
|
|
239
|
+
if eps:
|
|
240
|
+
eps_values.append(eps)
|
|
241
|
+
|
|
242
|
+
# Reverse to chronological order
|
|
243
|
+
eps_values = eps_values[::-1]
|
|
244
|
+
|
|
245
|
+
# Calculate YoY growth rates
|
|
246
|
+
growth_pattern = []
|
|
247
|
+
for i in range(1, len(eps_values)):
|
|
248
|
+
if eps_values[i - 1] > 0:
|
|
249
|
+
yoy_growth = ((eps_values[i] - eps_values[i - 1]) / eps_values[i - 1]) * 100
|
|
250
|
+
growth_pattern.append(round(yoy_growth, 1))
|
|
251
|
+
|
|
252
|
+
# Count down years
|
|
253
|
+
down_years = sum(1 for g in growth_pattern if g < 0)
|
|
254
|
+
|
|
255
|
+
# Find max consecutive growth years
|
|
256
|
+
consecutive = 0
|
|
257
|
+
max_consecutive = 0
|
|
258
|
+
for g in growth_pattern:
|
|
259
|
+
if g >= 0:
|
|
260
|
+
consecutive += 1
|
|
261
|
+
max_consecutive = max(max_consecutive, consecutive)
|
|
262
|
+
else:
|
|
263
|
+
consecutive = 0
|
|
264
|
+
|
|
265
|
+
interpretation = f"{max_consecutive} consecutive years of growth, {down_years} down years"
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
"down_years": down_years,
|
|
269
|
+
"consecutive_growth_years": max_consecutive,
|
|
270
|
+
"growth_pattern": growth_pattern,
|
|
271
|
+
"interpretation": interpretation
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# Example usage and testing
|
|
276
|
+
if __name__ == "__main__":
|
|
277
|
+
print("Testing Growth Calculator (A Component)...\n")
|
|
278
|
+
|
|
279
|
+
# Test case 1: Exceptional growth (NVDA-like, 40%+ CAGR, stable)
|
|
280
|
+
test_data_exceptional = [
|
|
281
|
+
{"date": "2023-01-31", "eps": 4.02, "revenue": 26974000000}, # FY2023
|
|
282
|
+
{"date": "2022-01-31", "eps": 4.44, "revenue": 26914000000}, # FY2022
|
|
283
|
+
{"date": "2021-01-31", "eps": 1.93, "revenue": 16675000000}, # FY2021
|
|
284
|
+
{"date": "2020-01-31", "eps": 1.67, "revenue": 10918000000}, # FY2020
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
result1 = calculate_annual_growth(test_data_exceptional)
|
|
288
|
+
print("Test 1: Exceptional Growth (40%+ CAGR)")
|
|
289
|
+
print(f" Score: {result1['score']}/100")
|
|
290
|
+
print(f" EPS CAGR: {result1['eps_cagr_3yr']}%")
|
|
291
|
+
print(f" Revenue CAGR: {result1['revenue_cagr_3yr']}%")
|
|
292
|
+
print(f" Stability: {result1['stability']}")
|
|
293
|
+
print(f" Interpretation: {result1['interpretation']}\n")
|
|
294
|
+
|
|
295
|
+
# Test case 2: Meets minimum (25-29% CAGR, stable)
|
|
296
|
+
test_data_minimum = [
|
|
297
|
+
{"date": "2023-12-31", "eps": 2.00, "revenue": 50000000000},
|
|
298
|
+
{"date": "2022-12-31", "eps": 1.60, "revenue": 45000000000},
|
|
299
|
+
{"date": "2021-12-31", "eps": 1.28, "revenue": 40000000000},
|
|
300
|
+
{"date": "2020-12-31", "eps": 1.02, "revenue": 36000000000}, # ~25% CAGR
|
|
301
|
+
]
|
|
302
|
+
|
|
303
|
+
result2 = calculate_annual_growth(test_data_minimum)
|
|
304
|
+
print("Test 2: Meets CANSLIM Minimum (25% CAGR)")
|
|
305
|
+
print(f" Score: {result2['score']}/100")
|
|
306
|
+
print(f" EPS CAGR: {result2['eps_cagr_3yr']}%")
|
|
307
|
+
print(f" Revenue CAGR: {result2['revenue_cagr_3yr']}%")
|
|
308
|
+
print(f" Stability: {result2['stability']}")
|
|
309
|
+
print(f" Interpretation: {result2['interpretation']}\n")
|
|
310
|
+
|
|
311
|
+
# Test case 3: Erratic growth (one down year)
|
|
312
|
+
test_data_erratic = [
|
|
313
|
+
{"date": "2023-12-31", "eps": 2.50, "revenue": 50000000000},
|
|
314
|
+
{"date": "2022-12-31", "eps": 1.80, "revenue": 45000000000}, # Down year
|
|
315
|
+
{"date": "2021-12-31", "eps": 2.00, "revenue": 48000000000},
|
|
316
|
+
{"date": "2020-12-31", "eps": 1.60, "revenue": 42000000000},
|
|
317
|
+
]
|
|
318
|
+
|
|
319
|
+
result3 = calculate_annual_growth(test_data_erratic)
|
|
320
|
+
print("Test 3: Erratic Growth (One Down Year)")
|
|
321
|
+
print(f" Score: {result3['score']}/100")
|
|
322
|
+
print(f" EPS CAGR: {result3['eps_cagr_3yr']}%")
|
|
323
|
+
print(f" Stability: {result3['stability']}")
|
|
324
|
+
print(f" Interpretation: {result3['interpretation']}\n")
|
|
325
|
+
|
|
326
|
+
# Test consistency check
|
|
327
|
+
print("Test 4: Consistency Check")
|
|
328
|
+
consistency = check_consistency(test_data_exceptional)
|
|
329
|
+
print(f" Down years: {consistency['down_years']}")
|
|
330
|
+
print(f" Consecutive growth: {consistency['consecutive_growth_years']} years")
|
|
331
|
+
print(f" Growth pattern: {consistency['growth_pattern']}")
|
|
332
|
+
print(f" Interpretation: {consistency['interpretation']}")
|
|
333
|
+
|
|
334
|
+
print("\n✓ All tests completed")
|