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,508 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
FTD Detector - Rally Tracker (State Machine)
|
|
4
|
+
|
|
5
|
+
Implements a state machine for tracking market correction → rally attempt → FTD sequence.
|
|
6
|
+
Supports dual-index tracking (S&P 500 + NASDAQ/QQQ).
|
|
7
|
+
|
|
8
|
+
States:
|
|
9
|
+
NO_SIGNAL → CORRECTION → RALLY_ATTEMPT → FTD_WINDOW → FTD_CONFIRMED
|
|
10
|
+
↑ ↓ ↓ ↓
|
|
11
|
+
└── RALLY_FAILED ←─────────────┘ FTD_INVALIDATED
|
|
12
|
+
|
|
13
|
+
O'Neil's FTD Rules:
|
|
14
|
+
- Swing low: 3%+ decline from recent high with 3+ down days
|
|
15
|
+
- Day 1: first up close (or close in top 50% of range) after swing low
|
|
16
|
+
- Day 2-3: close must not breach Day 1 intraday low
|
|
17
|
+
- Day 4-10: FTD requires >=1.25% gain on volume > previous day
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import Dict, List, Optional, Tuple
|
|
21
|
+
from enum import Enum
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MarketState(Enum):
|
|
25
|
+
NO_SIGNAL = "NO_SIGNAL"
|
|
26
|
+
CORRECTION = "CORRECTION"
|
|
27
|
+
RALLY_ATTEMPT = "RALLY_ATTEMPT"
|
|
28
|
+
FTD_WINDOW = "FTD_WINDOW"
|
|
29
|
+
FTD_CONFIRMED = "FTD_CONFIRMED"
|
|
30
|
+
RALLY_FAILED = "RALLY_FAILED"
|
|
31
|
+
FTD_INVALIDATED = "FTD_INVALIDATED"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Minimum correction depth to qualify
|
|
35
|
+
MIN_CORRECTION_PCT = 3.0
|
|
36
|
+
# Minimum down days during correction
|
|
37
|
+
MIN_DOWN_DAYS = 3
|
|
38
|
+
# FTD window bounds (inclusive)
|
|
39
|
+
FTD_DAY_START = 4
|
|
40
|
+
FTD_DAY_END = 10
|
|
41
|
+
# Minimum FTD gain thresholds
|
|
42
|
+
FTD_GAIN_MINIMUM = 1.25
|
|
43
|
+
FTD_GAIN_RECOMMENDED = 1.5
|
|
44
|
+
FTD_GAIN_STRONG = 2.0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def find_swing_low(history: List[Dict]) -> Optional[Dict]:
|
|
48
|
+
"""
|
|
49
|
+
Find the most recent swing low in chronological history.
|
|
50
|
+
|
|
51
|
+
A swing low requires:
|
|
52
|
+
1. A decline of MIN_CORRECTION_PCT from a recent high (within 40 days)
|
|
53
|
+
2. At least MIN_DOWN_DAYS down days during the decline
|
|
54
|
+
3. Must be a local minimum (not lower closes on both adjacent days)
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
history: Daily OHLCV in chronological order (oldest first)
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Dict with swing_low_idx, swing_low_price, swing_low_date,
|
|
61
|
+
recent_high_price, decline_pct, down_days, or None
|
|
62
|
+
"""
|
|
63
|
+
if not history or len(history) < 5:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
n = len(history)
|
|
67
|
+
|
|
68
|
+
# Scan from recent to old to find the most recent swing low
|
|
69
|
+
for i in range(n - 1, 3, -1):
|
|
70
|
+
low_close = history[i].get("close", 0)
|
|
71
|
+
if low_close <= 0:
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
# Look back up to 40 days for a recent high
|
|
75
|
+
search_start = max(0, i - 40)
|
|
76
|
+
recent_high = 0
|
|
77
|
+
recent_high_idx = search_start
|
|
78
|
+
for j in range(search_start, i):
|
|
79
|
+
c = history[j].get("close", 0)
|
|
80
|
+
if c > recent_high:
|
|
81
|
+
recent_high = c
|
|
82
|
+
recent_high_idx = j
|
|
83
|
+
|
|
84
|
+
if recent_high <= 0:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
decline_pct = (low_close - recent_high) / recent_high * 100
|
|
88
|
+
|
|
89
|
+
if decline_pct > -MIN_CORRECTION_PCT:
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
# Count down days from high to this point
|
|
93
|
+
down_days = 0
|
|
94
|
+
for j in range(recent_high_idx + 1, i + 1):
|
|
95
|
+
prev_c = history[j - 1].get("close", 0)
|
|
96
|
+
curr_c = history[j].get("close", 0)
|
|
97
|
+
if prev_c > 0 and curr_c < prev_c:
|
|
98
|
+
down_days += 1
|
|
99
|
+
|
|
100
|
+
if down_days < MIN_DOWN_DAYS:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# Verify it's a local minimum (not lower closes immediately adjacent)
|
|
104
|
+
is_local_low = True
|
|
105
|
+
if i > 0:
|
|
106
|
+
prev_close = history[i - 1].get("close", 0)
|
|
107
|
+
if prev_close > 0 and prev_close < low_close:
|
|
108
|
+
is_local_low = False
|
|
109
|
+
if i + 1 < n:
|
|
110
|
+
next_close = history[i + 1].get("close", 0)
|
|
111
|
+
if next_close > 0 and next_close < low_close:
|
|
112
|
+
is_local_low = False
|
|
113
|
+
|
|
114
|
+
if not is_local_low:
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
"swing_low_idx": i,
|
|
119
|
+
"swing_low_price": low_close,
|
|
120
|
+
"swing_low_date": history[i].get("date", "N/A"),
|
|
121
|
+
"swing_low_low": history[i].get("low", low_close),
|
|
122
|
+
"recent_high_price": recent_high,
|
|
123
|
+
"recent_high_idx": recent_high_idx,
|
|
124
|
+
"recent_high_date": history[recent_high_idx].get("date", "N/A"),
|
|
125
|
+
"decline_pct": round(decline_pct, 2),
|
|
126
|
+
"down_days": down_days,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def track_rally_attempt(history: List[Dict], swing_low_idx: int) -> Dict:
|
|
133
|
+
"""
|
|
134
|
+
Track rally attempt starting after swing low.
|
|
135
|
+
|
|
136
|
+
Day 1: First up close OR close in top 50% of day's range after swing low.
|
|
137
|
+
Day 2-3: Close must not breach Day 1 intraday low.
|
|
138
|
+
Invalidation: Close below swing low resets the attempt.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
history: Daily OHLCV in chronological order
|
|
142
|
+
swing_low_idx: Index of the swing low in history
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Dict with day1_idx, current_day, rally_days list, invalidated flag, etc.
|
|
146
|
+
"""
|
|
147
|
+
n = len(history)
|
|
148
|
+
swing_low_price = history[swing_low_idx].get("close", 0)
|
|
149
|
+
|
|
150
|
+
result = {
|
|
151
|
+
"day1_idx": None,
|
|
152
|
+
"day1_date": None,
|
|
153
|
+
"day1_low": None,
|
|
154
|
+
"current_day_count": 0,
|
|
155
|
+
"rally_days": [],
|
|
156
|
+
"invalidated": False,
|
|
157
|
+
"invalidation_reason": None,
|
|
158
|
+
"reset_count": 0,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if swing_low_idx >= n - 1:
|
|
162
|
+
return result
|
|
163
|
+
|
|
164
|
+
# Find Day 1
|
|
165
|
+
day1_idx = None
|
|
166
|
+
for i in range(swing_low_idx + 1, n):
|
|
167
|
+
curr_close = history[i].get("close", 0)
|
|
168
|
+
prev_close = history[i - 1].get("close", 0)
|
|
169
|
+
curr_high = history[i].get("high", curr_close)
|
|
170
|
+
curr_low = history[i].get("low", curr_close)
|
|
171
|
+
|
|
172
|
+
# Check invalidation first: close below swing low
|
|
173
|
+
if curr_close < swing_low_price:
|
|
174
|
+
result["invalidated"] = True
|
|
175
|
+
result["invalidation_reason"] = (
|
|
176
|
+
f"Close ${curr_close:.2f} below swing low ${swing_low_price:.2f} "
|
|
177
|
+
f"on {history[i].get('date', 'N/A')}"
|
|
178
|
+
)
|
|
179
|
+
return result
|
|
180
|
+
|
|
181
|
+
# Day 1: up close OR close in top 50% of range
|
|
182
|
+
day_range = curr_high - curr_low
|
|
183
|
+
if prev_close > 0 and curr_close > prev_close:
|
|
184
|
+
day1_idx = i
|
|
185
|
+
break
|
|
186
|
+
elif day_range > 0:
|
|
187
|
+
close_position = (curr_close - curr_low) / day_range
|
|
188
|
+
if close_position >= 0.5:
|
|
189
|
+
day1_idx = i
|
|
190
|
+
break
|
|
191
|
+
|
|
192
|
+
if day1_idx is None:
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
day1_low = history[day1_idx].get("low", history[day1_idx].get("close", 0))
|
|
196
|
+
result["day1_idx"] = day1_idx
|
|
197
|
+
result["day1_date"] = history[day1_idx].get("date", "N/A")
|
|
198
|
+
result["day1_low"] = day1_low
|
|
199
|
+
|
|
200
|
+
# Track days from Day 1 onward
|
|
201
|
+
day_count = 1
|
|
202
|
+
rally_days = [{
|
|
203
|
+
"day": 1,
|
|
204
|
+
"idx": day1_idx,
|
|
205
|
+
"date": history[day1_idx].get("date", "N/A"),
|
|
206
|
+
"close": history[day1_idx].get("close", 0),
|
|
207
|
+
"volume": history[day1_idx].get("volume", 0),
|
|
208
|
+
}]
|
|
209
|
+
|
|
210
|
+
for i in range(day1_idx + 1, n):
|
|
211
|
+
curr_close = history[i].get("close", 0)
|
|
212
|
+
prev_close = history[i - 1].get("close", 0)
|
|
213
|
+
curr_volume = history[i].get("volume", 0)
|
|
214
|
+
|
|
215
|
+
# Invalidation: close below swing low
|
|
216
|
+
if curr_close < swing_low_price:
|
|
217
|
+
result["invalidated"] = True
|
|
218
|
+
result["invalidation_reason"] = (
|
|
219
|
+
f"Close ${curr_close:.2f} below swing low ${swing_low_price:.2f} "
|
|
220
|
+
f"on {history[i].get('date', 'N/A')}"
|
|
221
|
+
)
|
|
222
|
+
break
|
|
223
|
+
|
|
224
|
+
# Day 2-3 special check: close must not breach Day 1 intraday low
|
|
225
|
+
day_count += 1
|
|
226
|
+
if day_count <= 3 and curr_close < day1_low:
|
|
227
|
+
result["invalidated"] = True
|
|
228
|
+
result["invalidation_reason"] = (
|
|
229
|
+
f"Day {day_count} close ${curr_close:.2f} below Day 1 low "
|
|
230
|
+
f"${day1_low:.2f} on {history[i].get('date', 'N/A')}"
|
|
231
|
+
)
|
|
232
|
+
break
|
|
233
|
+
|
|
234
|
+
change_pct = 0
|
|
235
|
+
if prev_close > 0:
|
|
236
|
+
change_pct = (curr_close - prev_close) / prev_close * 100
|
|
237
|
+
|
|
238
|
+
rally_days.append({
|
|
239
|
+
"day": day_count,
|
|
240
|
+
"idx": i,
|
|
241
|
+
"date": history[i].get("date", "N/A"),
|
|
242
|
+
"close": curr_close,
|
|
243
|
+
"volume": curr_volume,
|
|
244
|
+
"change_pct": round(change_pct, 2),
|
|
245
|
+
"volume_vs_prev": (
|
|
246
|
+
round((curr_volume / history[i - 1].get("volume", 1) - 1) * 100, 1)
|
|
247
|
+
if history[i - 1].get("volume", 0) > 0 else 0
|
|
248
|
+
),
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
result["current_day_count"] = day_count
|
|
252
|
+
result["rally_days"] = rally_days
|
|
253
|
+
return result
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def detect_ftd(history: List[Dict], rally_data: Dict,
|
|
257
|
+
avg_volume_50d: Optional[float] = None) -> Dict:
|
|
258
|
+
"""
|
|
259
|
+
Detect Follow-Through Day within the FTD window (Day 4-10).
|
|
260
|
+
|
|
261
|
+
FTD Criteria:
|
|
262
|
+
- Day 4-10 of rally attempt
|
|
263
|
+
- Price gain >= 1.25% (minimum), 1.5% (recommended), 2.0% (strong)
|
|
264
|
+
- Volume > previous day (mandatory)
|
|
265
|
+
- Volume > 50-day average (bonus)
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
history: Daily OHLCV in chronological order
|
|
269
|
+
rally_data: Output from track_rally_attempt()
|
|
270
|
+
avg_volume_50d: Optional 50-day average volume for bonus scoring
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Dict with ftd_detected, ftd_day_number, gain_pct, volume details, etc.
|
|
274
|
+
"""
|
|
275
|
+
result = {
|
|
276
|
+
"ftd_detected": False,
|
|
277
|
+
"ftd_day_number": None,
|
|
278
|
+
"ftd_date": None,
|
|
279
|
+
"ftd_price": None,
|
|
280
|
+
"gain_pct": None,
|
|
281
|
+
"volume": None,
|
|
282
|
+
"prev_day_volume": None,
|
|
283
|
+
"volume_above_avg": None,
|
|
284
|
+
"gain_tier": None,
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if rally_data.get("invalidated"):
|
|
288
|
+
return result
|
|
289
|
+
|
|
290
|
+
rally_days = rally_data.get("rally_days", [])
|
|
291
|
+
|
|
292
|
+
for day_info in rally_days:
|
|
293
|
+
day_num = day_info.get("day", 0)
|
|
294
|
+
if day_num < FTD_DAY_START or day_num > FTD_DAY_END:
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
change_pct = day_info.get("change_pct", 0)
|
|
298
|
+
if change_pct < FTD_GAIN_MINIMUM:
|
|
299
|
+
continue
|
|
300
|
+
|
|
301
|
+
# Volume must be higher than previous day
|
|
302
|
+
idx = day_info.get("idx", 0)
|
|
303
|
+
curr_volume = day_info.get("volume", 0)
|
|
304
|
+
prev_volume = history[idx - 1].get("volume", 0) if idx > 0 else 0
|
|
305
|
+
|
|
306
|
+
if prev_volume <= 0 or curr_volume <= prev_volume:
|
|
307
|
+
continue
|
|
308
|
+
|
|
309
|
+
# FTD detected
|
|
310
|
+
if change_pct >= FTD_GAIN_STRONG:
|
|
311
|
+
gain_tier = "strong"
|
|
312
|
+
elif change_pct >= FTD_GAIN_RECOMMENDED:
|
|
313
|
+
gain_tier = "recommended"
|
|
314
|
+
else:
|
|
315
|
+
gain_tier = "minimum"
|
|
316
|
+
|
|
317
|
+
volume_above_avg = None
|
|
318
|
+
if avg_volume_50d and avg_volume_50d > 0:
|
|
319
|
+
volume_above_avg = curr_volume > avg_volume_50d
|
|
320
|
+
|
|
321
|
+
result.update({
|
|
322
|
+
"ftd_detected": True,
|
|
323
|
+
"ftd_day_number": day_num,
|
|
324
|
+
"ftd_date": day_info.get("date", "N/A"),
|
|
325
|
+
"ftd_price": day_info.get("close", 0),
|
|
326
|
+
"gain_pct": change_pct,
|
|
327
|
+
"volume": curr_volume,
|
|
328
|
+
"prev_day_volume": prev_volume,
|
|
329
|
+
"volume_above_avg": volume_above_avg,
|
|
330
|
+
"gain_tier": gain_tier,
|
|
331
|
+
})
|
|
332
|
+
break # Take the first qualifying FTD
|
|
333
|
+
|
|
334
|
+
return result
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def calculate_avg_volume(history: List[Dict], period: int = 50) -> float:
|
|
338
|
+
"""Calculate average volume over the specified period (most recent data)."""
|
|
339
|
+
if not history:
|
|
340
|
+
return 0
|
|
341
|
+
volumes = [d.get("volume", 0) for d in history[-period:] if d.get("volume", 0) > 0]
|
|
342
|
+
return sum(volumes) / len(volumes) if volumes else 0
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def analyze_single_index(history: List[Dict], index_name: str) -> Dict:
|
|
346
|
+
"""
|
|
347
|
+
Run full FTD analysis for a single index.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
history: Daily OHLCV in chronological order (oldest first)
|
|
351
|
+
index_name: Label (e.g., "S&P 500", "NASDAQ")
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
Complete analysis dict for this index
|
|
355
|
+
"""
|
|
356
|
+
result = {
|
|
357
|
+
"index": index_name,
|
|
358
|
+
"state": MarketState.NO_SIGNAL.value,
|
|
359
|
+
"swing_low": None,
|
|
360
|
+
"rally_attempt": None,
|
|
361
|
+
"ftd": None,
|
|
362
|
+
"current_price": None,
|
|
363
|
+
"lookback_high": None,
|
|
364
|
+
"correction_depth_pct": None,
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if not history or len(history) < 10:
|
|
368
|
+
result["error"] = "Insufficient data"
|
|
369
|
+
return result
|
|
370
|
+
|
|
371
|
+
# Use last 60 trading days for analysis
|
|
372
|
+
lookback = min(60, len(history))
|
|
373
|
+
analysis_window = history[-lookback:]
|
|
374
|
+
n = len(analysis_window)
|
|
375
|
+
|
|
376
|
+
result["current_price"] = analysis_window[-1].get("close", 0)
|
|
377
|
+
|
|
378
|
+
# Find the highest close in the window
|
|
379
|
+
max_close = 0
|
|
380
|
+
for d in analysis_window:
|
|
381
|
+
c = d.get("close", 0)
|
|
382
|
+
if c > max_close:
|
|
383
|
+
max_close = c
|
|
384
|
+
result["lookback_high"] = max_close
|
|
385
|
+
|
|
386
|
+
if max_close > 0 and result["current_price"] > 0:
|
|
387
|
+
result["correction_depth_pct"] = round(
|
|
388
|
+
(result["current_price"] - max_close) / max_close * 100, 2
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Do NOT early-return based on current correction depth.
|
|
392
|
+
# A valid FTD may be in progress even if price has recovered near highs.
|
|
393
|
+
# Instead, let find_swing_low() determine whether a qualifying correction occurred.
|
|
394
|
+
|
|
395
|
+
# Find swing low
|
|
396
|
+
swing_low = find_swing_low(analysis_window)
|
|
397
|
+
if swing_low is None:
|
|
398
|
+
return result
|
|
399
|
+
|
|
400
|
+
result["swing_low"] = swing_low
|
|
401
|
+
result["state"] = MarketState.CORRECTION.value
|
|
402
|
+
|
|
403
|
+
# Track rally attempt
|
|
404
|
+
rally = track_rally_attempt(analysis_window, swing_low["swing_low_idx"])
|
|
405
|
+
result["rally_attempt"] = rally
|
|
406
|
+
|
|
407
|
+
if rally["invalidated"]:
|
|
408
|
+
result["state"] = MarketState.RALLY_FAILED.value
|
|
409
|
+
return result
|
|
410
|
+
|
|
411
|
+
if rally["day1_idx"] is None:
|
|
412
|
+
# Still in correction, no rally attempt started
|
|
413
|
+
return result
|
|
414
|
+
|
|
415
|
+
day_count = rally["current_day_count"]
|
|
416
|
+
|
|
417
|
+
if day_count < FTD_DAY_START:
|
|
418
|
+
result["state"] = MarketState.RALLY_ATTEMPT.value
|
|
419
|
+
return result
|
|
420
|
+
|
|
421
|
+
# In FTD window or beyond
|
|
422
|
+
result["state"] = MarketState.FTD_WINDOW.value
|
|
423
|
+
|
|
424
|
+
# Calculate 50-day average volume for scoring
|
|
425
|
+
avg_vol = calculate_avg_volume(analysis_window)
|
|
426
|
+
|
|
427
|
+
# Detect FTD
|
|
428
|
+
ftd = detect_ftd(analysis_window, rally, avg_volume_50d=avg_vol)
|
|
429
|
+
result["ftd"] = ftd
|
|
430
|
+
|
|
431
|
+
if ftd["ftd_detected"]:
|
|
432
|
+
result["state"] = MarketState.FTD_CONFIRMED.value
|
|
433
|
+
|
|
434
|
+
elif day_count > FTD_DAY_END:
|
|
435
|
+
# FTD window passed without qualifying day
|
|
436
|
+
result["state"] = MarketState.RALLY_FAILED.value
|
|
437
|
+
|
|
438
|
+
return result
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def get_market_state(sp500_history: List[Dict],
|
|
442
|
+
nasdaq_history: List[Dict]) -> Dict:
|
|
443
|
+
"""
|
|
444
|
+
Analyze both indices and produce a merged market state assessment.
|
|
445
|
+
|
|
446
|
+
Priority logic:
|
|
447
|
+
- If either index has FTD_CONFIRMED → overall FTD (single sufficient)
|
|
448
|
+
- If both have FTD_CONFIRMED → strong FTD (dual confirmation)
|
|
449
|
+
- Otherwise, use the more advanced state
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
sp500_history: S&P 500 daily OHLCV, most recent first (API format)
|
|
453
|
+
nasdaq_history: NASDAQ/QQQ daily OHLCV, most recent first (API format)
|
|
454
|
+
|
|
455
|
+
Returns:
|
|
456
|
+
Combined market state with both index analyses
|
|
457
|
+
"""
|
|
458
|
+
# Convert to chronological order (oldest first)
|
|
459
|
+
sp500_chrono = list(reversed(sp500_history)) if sp500_history else []
|
|
460
|
+
nasdaq_chrono = list(reversed(nasdaq_history)) if nasdaq_history else []
|
|
461
|
+
|
|
462
|
+
sp500_analysis = analyze_single_index(sp500_chrono, "S&P 500")
|
|
463
|
+
nasdaq_analysis = analyze_single_index(nasdaq_chrono, "NASDAQ")
|
|
464
|
+
|
|
465
|
+
sp500_state = MarketState(sp500_analysis["state"])
|
|
466
|
+
nasdaq_state = MarketState(nasdaq_analysis["state"])
|
|
467
|
+
|
|
468
|
+
# Determine combined state
|
|
469
|
+
sp500_ftd = sp500_state == MarketState.FTD_CONFIRMED
|
|
470
|
+
nasdaq_ftd = nasdaq_state == MarketState.FTD_CONFIRMED
|
|
471
|
+
|
|
472
|
+
if sp500_ftd and nasdaq_ftd:
|
|
473
|
+
combined_state = MarketState.FTD_CONFIRMED.value
|
|
474
|
+
dual_confirmation = True
|
|
475
|
+
elif sp500_ftd or nasdaq_ftd:
|
|
476
|
+
combined_state = MarketState.FTD_CONFIRMED.value
|
|
477
|
+
dual_confirmation = False
|
|
478
|
+
else:
|
|
479
|
+
# Use the more advanced (hopeful) state
|
|
480
|
+
state_priority = [
|
|
481
|
+
MarketState.FTD_WINDOW,
|
|
482
|
+
MarketState.RALLY_ATTEMPT,
|
|
483
|
+
MarketState.CORRECTION,
|
|
484
|
+
MarketState.RALLY_FAILED,
|
|
485
|
+
MarketState.FTD_INVALIDATED,
|
|
486
|
+
MarketState.NO_SIGNAL,
|
|
487
|
+
]
|
|
488
|
+
combined_state = MarketState.NO_SIGNAL.value
|
|
489
|
+
for state in state_priority:
|
|
490
|
+
if sp500_state == state or nasdaq_state == state:
|
|
491
|
+
combined_state = state.value
|
|
492
|
+
break
|
|
493
|
+
dual_confirmation = False
|
|
494
|
+
|
|
495
|
+
# Determine which index triggered FTD (if any)
|
|
496
|
+
ftd_index = None
|
|
497
|
+
if sp500_ftd:
|
|
498
|
+
ftd_index = "S&P 500"
|
|
499
|
+
if nasdaq_ftd:
|
|
500
|
+
ftd_index = "NASDAQ" if ftd_index is None else "Both"
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
"combined_state": combined_state,
|
|
504
|
+
"dual_confirmation": dual_confirmation,
|
|
505
|
+
"ftd_index": ftd_index,
|
|
506
|
+
"sp500": sp500_analysis,
|
|
507
|
+
"nasdaq": nasdaq_analysis,
|
|
508
|
+
}
|