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,322 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
VCP Pattern Calculator - Core Volatility Contraction Pattern Detection
|
|
4
|
+
|
|
5
|
+
Implements Mark Minervini's VCP detection algorithm:
|
|
6
|
+
1. Find swing highs and lows within a 120-day lookback window
|
|
7
|
+
2. Identify successive contractions (T1, T2, T3, T4)
|
|
8
|
+
3. Validate that each contraction is tighter than the previous
|
|
9
|
+
4. Score based on number of contractions, tightness, and depth ratios
|
|
10
|
+
|
|
11
|
+
VCP Characteristics:
|
|
12
|
+
- T1 (first correction): 8-35% depth for S&P 500 large-caps
|
|
13
|
+
- Each successive contraction should be 25%+ tighter than the previous
|
|
14
|
+
- Minimum 2 contractions required for valid VCP
|
|
15
|
+
- Successive highs should be within 5% of each other
|
|
16
|
+
- Pattern duration: 15-325 trading days
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import Dict, List, Optional, Tuple
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def calculate_vcp_pattern(
|
|
23
|
+
historical_prices: List[Dict],
|
|
24
|
+
lookback_days: int = 120,
|
|
25
|
+
) -> Dict:
|
|
26
|
+
"""
|
|
27
|
+
Detect Volatility Contraction Pattern in price data.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
historical_prices: Daily OHLCV data (most recent first), need 30+ days
|
|
31
|
+
lookback_days: Number of days to look back for pattern (default 120)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Dict with score (0-100), contractions list, pattern validity, pivot point
|
|
35
|
+
"""
|
|
36
|
+
if not historical_prices or len(historical_prices) < 30:
|
|
37
|
+
return {
|
|
38
|
+
"score": 0,
|
|
39
|
+
"valid_vcp": False,
|
|
40
|
+
"contractions": [],
|
|
41
|
+
"num_contractions": 0,
|
|
42
|
+
"pivot_price": None,
|
|
43
|
+
"error": "Insufficient data (need 30+ days)",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Work in chronological order (oldest first)
|
|
47
|
+
prices = list(reversed(historical_prices[:lookback_days]))
|
|
48
|
+
n = len(prices)
|
|
49
|
+
|
|
50
|
+
if n < 30:
|
|
51
|
+
return {
|
|
52
|
+
"score": 0,
|
|
53
|
+
"valid_vcp": False,
|
|
54
|
+
"contractions": [],
|
|
55
|
+
"num_contractions": 0,
|
|
56
|
+
"pivot_price": None,
|
|
57
|
+
"error": "Insufficient data in lookback window",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Step A: Find swing points
|
|
61
|
+
highs = [d.get("high", d.get("close", 0)) for d in prices]
|
|
62
|
+
lows = [d.get("low", d.get("close", 0)) for d in prices]
|
|
63
|
+
closes = [d.get("close", 0) for d in prices]
|
|
64
|
+
dates = [d.get("date", f"day-{i}") for i, d in enumerate(prices)]
|
|
65
|
+
|
|
66
|
+
swing_highs = _find_swing_highs(highs, window=5)
|
|
67
|
+
swing_lows = _find_swing_lows(lows, window=5)
|
|
68
|
+
|
|
69
|
+
if len(swing_highs) < 1 or len(swing_lows) < 1:
|
|
70
|
+
return {
|
|
71
|
+
"score": 0,
|
|
72
|
+
"valid_vcp": False,
|
|
73
|
+
"contractions": [],
|
|
74
|
+
"num_contractions": 0,
|
|
75
|
+
"pivot_price": None,
|
|
76
|
+
"error": "Insufficient swing points detected",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Step B: Identify contractions
|
|
80
|
+
contractions = _identify_contractions(swing_highs, swing_lows, highs, lows, dates)
|
|
81
|
+
|
|
82
|
+
if len(contractions) < 2:
|
|
83
|
+
return {
|
|
84
|
+
"score": 0,
|
|
85
|
+
"valid_vcp": False,
|
|
86
|
+
"contractions": contractions,
|
|
87
|
+
"num_contractions": len(contractions),
|
|
88
|
+
"pivot_price": _get_pivot_price(contractions, highs, swing_highs),
|
|
89
|
+
"error": "Fewer than 2 contractions found" if len(contractions) < 2 else None,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Step C: Validate VCP
|
|
93
|
+
validation = _validate_vcp(contractions, n)
|
|
94
|
+
|
|
95
|
+
# Pivot price = high of the last contraction
|
|
96
|
+
pivot_price = _get_pivot_price(contractions, highs, swing_highs)
|
|
97
|
+
|
|
98
|
+
# Calculate pattern duration
|
|
99
|
+
if len(contractions) >= 2:
|
|
100
|
+
first_idx = contractions[0]["high_idx"]
|
|
101
|
+
last_low_idx = contractions[-1]["low_idx"]
|
|
102
|
+
pattern_duration = last_low_idx - first_idx
|
|
103
|
+
else:
|
|
104
|
+
pattern_duration = 0
|
|
105
|
+
|
|
106
|
+
# Score the pattern
|
|
107
|
+
score = _score_vcp(contractions, validation)
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"score": score,
|
|
111
|
+
"valid_vcp": validation["valid"],
|
|
112
|
+
"contractions": contractions,
|
|
113
|
+
"num_contractions": len(contractions),
|
|
114
|
+
"pivot_price": round(pivot_price, 2) if pivot_price else None,
|
|
115
|
+
"pattern_duration_days": pattern_duration,
|
|
116
|
+
"validation": validation,
|
|
117
|
+
"error": None,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _find_swing_highs(highs: List[float], window: int = 5) -> List[Tuple[int, float]]:
|
|
122
|
+
"""Find swing high points. Returns list of (index, value)."""
|
|
123
|
+
swing_highs = []
|
|
124
|
+
for i in range(window, len(highs) - window):
|
|
125
|
+
is_high = True
|
|
126
|
+
for j in range(1, window + 1):
|
|
127
|
+
if highs[i] <= highs[i - j] or highs[i] <= highs[i + j]:
|
|
128
|
+
is_high = False
|
|
129
|
+
break
|
|
130
|
+
if is_high:
|
|
131
|
+
swing_highs.append((i, highs[i]))
|
|
132
|
+
return swing_highs
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _find_swing_lows(lows: List[float], window: int = 5) -> List[Tuple[int, float]]:
|
|
136
|
+
"""Find swing low points. Returns list of (index, value)."""
|
|
137
|
+
swing_lows = []
|
|
138
|
+
for i in range(window, len(lows) - window):
|
|
139
|
+
is_low = True
|
|
140
|
+
for j in range(1, window + 1):
|
|
141
|
+
if lows[i] >= lows[i - j] or lows[i] >= lows[i + j]:
|
|
142
|
+
is_low = False
|
|
143
|
+
break
|
|
144
|
+
if is_low:
|
|
145
|
+
swing_lows.append((i, lows[i]))
|
|
146
|
+
return swing_lows
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _identify_contractions(
|
|
150
|
+
swing_highs: List[Tuple[int, float]],
|
|
151
|
+
swing_lows: List[Tuple[int, float]],
|
|
152
|
+
highs: List[float],
|
|
153
|
+
lows: List[float],
|
|
154
|
+
dates: List[str],
|
|
155
|
+
) -> List[Dict]:
|
|
156
|
+
"""
|
|
157
|
+
Identify successive contractions from swing points.
|
|
158
|
+
Each contraction is defined by a swing high followed by a swing low.
|
|
159
|
+
"""
|
|
160
|
+
if not swing_highs:
|
|
161
|
+
return []
|
|
162
|
+
|
|
163
|
+
# Start from the highest swing high in the lookback
|
|
164
|
+
h1_idx, h1_val = max(swing_highs, key=lambda x: x[1])
|
|
165
|
+
|
|
166
|
+
contractions = []
|
|
167
|
+
current_high_idx = h1_idx
|
|
168
|
+
current_high_val = h1_val
|
|
169
|
+
|
|
170
|
+
# Find successive contraction pairs
|
|
171
|
+
for _ in range(4): # Max 4 contractions
|
|
172
|
+
# Find next swing low after current high
|
|
173
|
+
next_low = None
|
|
174
|
+
for idx, val in swing_lows:
|
|
175
|
+
if idx > current_high_idx:
|
|
176
|
+
next_low = (idx, val)
|
|
177
|
+
break
|
|
178
|
+
|
|
179
|
+
if next_low is None:
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
low_idx, low_val = next_low
|
|
183
|
+
depth_pct = (current_high_val - low_val) / current_high_val * 100 if current_high_val > 0 else 0
|
|
184
|
+
|
|
185
|
+
contractions.append({
|
|
186
|
+
"label": f"T{len(contractions) + 1}",
|
|
187
|
+
"high_idx": current_high_idx,
|
|
188
|
+
"high_price": round(current_high_val, 2),
|
|
189
|
+
"high_date": dates[current_high_idx] if current_high_idx < len(dates) else "N/A",
|
|
190
|
+
"low_idx": low_idx,
|
|
191
|
+
"low_price": round(low_val, 2),
|
|
192
|
+
"low_date": dates[low_idx] if low_idx < len(dates) else "N/A",
|
|
193
|
+
"depth_pct": round(depth_pct, 2),
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
# Find next swing high after this low (for the next contraction)
|
|
197
|
+
next_high = None
|
|
198
|
+
for idx, val in swing_highs:
|
|
199
|
+
if idx > low_idx:
|
|
200
|
+
next_high = (idx, val)
|
|
201
|
+
break
|
|
202
|
+
|
|
203
|
+
if next_high is None:
|
|
204
|
+
break
|
|
205
|
+
|
|
206
|
+
current_high_idx, current_high_val = next_high
|
|
207
|
+
|
|
208
|
+
return contractions
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _validate_vcp(contractions: List[Dict], total_days: int) -> Dict:
|
|
212
|
+
"""Validate whether the contraction pattern qualifies as a VCP."""
|
|
213
|
+
issues = []
|
|
214
|
+
valid = True
|
|
215
|
+
|
|
216
|
+
if len(contractions) < 2:
|
|
217
|
+
return {"valid": False, "issues": ["Need at least 2 contractions"]}
|
|
218
|
+
|
|
219
|
+
# Check T1 depth (8-35% for large-caps)
|
|
220
|
+
t1_depth = contractions[0]["depth_pct"]
|
|
221
|
+
if t1_depth < 8:
|
|
222
|
+
issues.append(f"T1 depth too shallow ({t1_depth:.1f}%, need >= 8%)")
|
|
223
|
+
valid = False
|
|
224
|
+
elif t1_depth > 35:
|
|
225
|
+
issues.append(f"T1 depth too deep ({t1_depth:.1f}%, prefer <= 35%)")
|
|
226
|
+
# Don't invalidate, just flag
|
|
227
|
+
|
|
228
|
+
# Check contraction tightening (each T should be <= 75% of previous)
|
|
229
|
+
contraction_ratios = []
|
|
230
|
+
for i in range(1, len(contractions)):
|
|
231
|
+
prev_depth = contractions[i - 1]["depth_pct"]
|
|
232
|
+
curr_depth = contractions[i]["depth_pct"]
|
|
233
|
+
if prev_depth > 0:
|
|
234
|
+
ratio = curr_depth / prev_depth
|
|
235
|
+
contraction_ratios.append(ratio)
|
|
236
|
+
if ratio > 0.75:
|
|
237
|
+
issues.append(
|
|
238
|
+
f"{contractions[i]['label']} ({curr_depth:.1f}%) does not contract "
|
|
239
|
+
f"25%+ vs {contractions[i-1]['label']} ({prev_depth:.1f}%), "
|
|
240
|
+
f"ratio={ratio:.2f} (need <= 0.75)"
|
|
241
|
+
)
|
|
242
|
+
valid = False
|
|
243
|
+
|
|
244
|
+
# Check successive highs within 5% of each other
|
|
245
|
+
for i in range(1, len(contractions)):
|
|
246
|
+
prev_high = contractions[i - 1]["high_price"]
|
|
247
|
+
curr_high = contractions[i]["high_price"] if i < len(contractions) else contractions[-1]["high_price"]
|
|
248
|
+
# The high of subsequent contraction should be near the first
|
|
249
|
+
if prev_high > 0:
|
|
250
|
+
pct_diff = abs(curr_high - contractions[0]["high_price"]) / contractions[0]["high_price"] * 100
|
|
251
|
+
if pct_diff > 5:
|
|
252
|
+
issues.append(
|
|
253
|
+
f"{contractions[i]['label']} high ${curr_high:.2f} is "
|
|
254
|
+
f"{pct_diff:.1f}% from H1 ${contractions[0]['high_price']:.2f}"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Pattern duration check (15-325 trading days)
|
|
258
|
+
if len(contractions) >= 2:
|
|
259
|
+
duration = contractions[-1]["low_idx"] - contractions[0]["high_idx"]
|
|
260
|
+
if duration < 15:
|
|
261
|
+
issues.append(f"Pattern too short ({duration} days, need >= 15)")
|
|
262
|
+
valid = False
|
|
263
|
+
elif duration > 325:
|
|
264
|
+
issues.append(f"Pattern too long ({duration} days, prefer <= 325)")
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
"valid": valid,
|
|
268
|
+
"issues": issues,
|
|
269
|
+
"contraction_ratios": [round(r, 3) for r in contraction_ratios],
|
|
270
|
+
"t1_depth": t1_depth,
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _get_pivot_price(
|
|
275
|
+
contractions: List[Dict],
|
|
276
|
+
highs: List[float],
|
|
277
|
+
swing_highs: List[Tuple[int, float]],
|
|
278
|
+
) -> Optional[float]:
|
|
279
|
+
"""Get the pivot (breakout) price - high of the last contraction."""
|
|
280
|
+
if contractions:
|
|
281
|
+
return contractions[-1]["high_price"]
|
|
282
|
+
elif swing_highs:
|
|
283
|
+
return swing_highs[-1][1]
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _score_vcp(contractions: List[Dict], validation: Dict) -> int:
|
|
288
|
+
"""Score the VCP pattern quality (0-100)."""
|
|
289
|
+
if not validation["valid"]:
|
|
290
|
+
# Even invalid patterns get partial credit for structure
|
|
291
|
+
return min(40, len(contractions) * 15)
|
|
292
|
+
|
|
293
|
+
num = len(contractions)
|
|
294
|
+
|
|
295
|
+
# Base score by contraction count
|
|
296
|
+
if num >= 4:
|
|
297
|
+
base = 90
|
|
298
|
+
elif num >= 3:
|
|
299
|
+
base = 80
|
|
300
|
+
elif num >= 2:
|
|
301
|
+
base = 60
|
|
302
|
+
else:
|
|
303
|
+
return 20
|
|
304
|
+
|
|
305
|
+
score = base
|
|
306
|
+
|
|
307
|
+
# Bonus: tight final contraction (< 5% depth)
|
|
308
|
+
final_depth = contractions[-1]["depth_pct"]
|
|
309
|
+
if final_depth < 5:
|
|
310
|
+
score += 10
|
|
311
|
+
|
|
312
|
+
# Bonus: good contraction ratio (avg < 0.4 of T1)
|
|
313
|
+
ratios = validation.get("contraction_ratios", [])
|
|
314
|
+
if ratios and sum(ratios) / len(ratios) < 0.4:
|
|
315
|
+
score += 10
|
|
316
|
+
|
|
317
|
+
# Penalty: deep T1 (> 30%)
|
|
318
|
+
t1_depth = validation.get("t1_depth", 0)
|
|
319
|
+
if t1_depth > 30:
|
|
320
|
+
score -= 10
|
|
321
|
+
|
|
322
|
+
return max(0, min(100, score))
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Volume Pattern Calculator - Volume Dry-Up Analysis
|
|
4
|
+
|
|
5
|
+
Analyzes volume behavior near the pivot point of a VCP pattern.
|
|
6
|
+
Key principle: Volume should contract (dry up) as the pattern tightens,
|
|
7
|
+
then expand on breakout.
|
|
8
|
+
|
|
9
|
+
Key Metric: Volume dry-up ratio = avg volume (last 10 bars near pivot) / 50-day avg volume
|
|
10
|
+
|
|
11
|
+
Scoring:
|
|
12
|
+
- Dry-up ratio < 0.30: 90 (exceptional volume contraction)
|
|
13
|
+
- 0.30-0.50: 75 (strong dry-up)
|
|
14
|
+
- 0.50-0.70: 60 (moderate dry-up)
|
|
15
|
+
- 0.70-1.00: 40 (weak dry-up)
|
|
16
|
+
- > 1.00: 20 (no dry-up, not ideal)
|
|
17
|
+
|
|
18
|
+
Modifiers:
|
|
19
|
+
- Breakout on 1.5x+ volume: +10
|
|
20
|
+
- Net accumulation > 3 days: +10
|
|
21
|
+
- Net distribution > 3 days: -10
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from typing import Dict, List, Optional
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def calculate_volume_pattern(
|
|
28
|
+
historical_prices: List[Dict],
|
|
29
|
+
pivot_price: Optional[float] = None,
|
|
30
|
+
) -> Dict:
|
|
31
|
+
"""
|
|
32
|
+
Analyze volume behavior near the VCP pivot point.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
historical_prices: Daily OHLCV data (most recent first), need 50+ days
|
|
36
|
+
pivot_price: The pivot (breakout) price level. If None, uses recent high.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Dict with score (0-100), dry_up_ratio, volume details
|
|
40
|
+
"""
|
|
41
|
+
if not historical_prices or len(historical_prices) < 20:
|
|
42
|
+
return {
|
|
43
|
+
"score": 0,
|
|
44
|
+
"dry_up_ratio": None,
|
|
45
|
+
"error": "Insufficient data (need 20+ days)",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
volumes = [d.get("volume", 0) for d in historical_prices]
|
|
49
|
+
closes = [d.get("close", d.get("adjClose", 0)) for d in historical_prices]
|
|
50
|
+
|
|
51
|
+
# 50-day average volume (or available)
|
|
52
|
+
vol_period = min(50, len(volumes))
|
|
53
|
+
avg_volume_50d = sum(volumes[:vol_period]) / vol_period if vol_period > 0 else 0
|
|
54
|
+
|
|
55
|
+
if avg_volume_50d <= 0:
|
|
56
|
+
return {
|
|
57
|
+
"score": 0,
|
|
58
|
+
"dry_up_ratio": None,
|
|
59
|
+
"error": "No volume data available",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Recent volume (last 10 bars, representing area near pivot)
|
|
63
|
+
recent_period = min(10, len(volumes))
|
|
64
|
+
avg_volume_recent = sum(volumes[:recent_period]) / recent_period if recent_period > 0 else 0
|
|
65
|
+
|
|
66
|
+
# Volume dry-up ratio
|
|
67
|
+
dry_up_ratio = avg_volume_recent / avg_volume_50d if avg_volume_50d > 0 else 1.0
|
|
68
|
+
|
|
69
|
+
# Base score from dry-up ratio
|
|
70
|
+
if dry_up_ratio < 0.30:
|
|
71
|
+
base_score = 90
|
|
72
|
+
elif dry_up_ratio < 0.50:
|
|
73
|
+
base_score = 75
|
|
74
|
+
elif dry_up_ratio < 0.70:
|
|
75
|
+
base_score = 60
|
|
76
|
+
elif dry_up_ratio <= 1.00:
|
|
77
|
+
base_score = 40
|
|
78
|
+
else:
|
|
79
|
+
base_score = 20
|
|
80
|
+
|
|
81
|
+
score = base_score
|
|
82
|
+
|
|
83
|
+
# Modifier: Check for breakout volume (most recent day)
|
|
84
|
+
breakout_volume = False
|
|
85
|
+
if len(volumes) >= 2 and volumes[0] > avg_volume_50d * 1.5:
|
|
86
|
+
current_price = closes[0] if closes else 0
|
|
87
|
+
if pivot_price and current_price > pivot_price:
|
|
88
|
+
breakout_volume = True
|
|
89
|
+
score += 10
|
|
90
|
+
|
|
91
|
+
# Modifier: Net accumulation/distribution in last 20 days
|
|
92
|
+
# Count up-volume vs down-volume days
|
|
93
|
+
up_vol_days = 0
|
|
94
|
+
down_vol_days = 0
|
|
95
|
+
analysis_period = min(20, len(closes) - 1)
|
|
96
|
+
|
|
97
|
+
for i in range(analysis_period):
|
|
98
|
+
if i + 1 < len(closes) and closes[i] > closes[i + 1]:
|
|
99
|
+
up_vol_days += 1
|
|
100
|
+
elif i + 1 < len(closes) and closes[i] < closes[i + 1]:
|
|
101
|
+
down_vol_days += 1
|
|
102
|
+
|
|
103
|
+
net_accumulation = up_vol_days - down_vol_days
|
|
104
|
+
if net_accumulation > 3:
|
|
105
|
+
score += 10
|
|
106
|
+
elif net_accumulation < -3:
|
|
107
|
+
score -= 10
|
|
108
|
+
|
|
109
|
+
score = max(0, min(100, score))
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
"score": score,
|
|
113
|
+
"dry_up_ratio": round(dry_up_ratio, 3),
|
|
114
|
+
"avg_volume_50d": int(avg_volume_50d),
|
|
115
|
+
"avg_volume_recent_10d": int(avg_volume_recent),
|
|
116
|
+
"breakout_volume_detected": breakout_volume,
|
|
117
|
+
"up_volume_days_20d": up_vol_days,
|
|
118
|
+
"down_volume_days_20d": down_vol_days,
|
|
119
|
+
"net_accumulation": net_accumulation,
|
|
120
|
+
"error": None,
|
|
121
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
FMP API Client for VCP Screener
|
|
4
|
+
|
|
5
|
+
Provides rate-limited access to Financial Modeling Prep API endpoints
|
|
6
|
+
for VCP (Volatility Contraction Pattern) screening.
|
|
7
|
+
|
|
8
|
+
Features:
|
|
9
|
+
- Rate limiting (0.3s between requests)
|
|
10
|
+
- Automatic retry on 429 errors
|
|
11
|
+
- Session caching for duplicate requests
|
|
12
|
+
- Batch quote support
|
|
13
|
+
- S&P 500 constituents fetching
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import time
|
|
19
|
+
from typing import Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
import requests
|
|
23
|
+
except ImportError:
|
|
24
|
+
print("ERROR: requests library not found. Install with: pip install requests", file=sys.stderr)
|
|
25
|
+
sys.exit(1)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class FMPClient:
|
|
29
|
+
"""Client for Financial Modeling Prep API with rate limiting and caching"""
|
|
30
|
+
|
|
31
|
+
BASE_URL = "https://financialmodelingprep.com/api/v3"
|
|
32
|
+
RATE_LIMIT_DELAY = 0.3 # 300ms between requests
|
|
33
|
+
|
|
34
|
+
def __init__(self, api_key: Optional[str] = None):
|
|
35
|
+
self.api_key = api_key or os.getenv("FMP_API_KEY")
|
|
36
|
+
if not self.api_key:
|
|
37
|
+
raise ValueError(
|
|
38
|
+
"FMP API key required. Set FMP_API_KEY environment variable "
|
|
39
|
+
"or pass api_key parameter."
|
|
40
|
+
)
|
|
41
|
+
self.session = requests.Session()
|
|
42
|
+
self.cache = {}
|
|
43
|
+
self.last_call_time = 0
|
|
44
|
+
self.rate_limit_reached = False
|
|
45
|
+
self.retry_count = 0
|
|
46
|
+
self.max_retries = 1
|
|
47
|
+
self.api_calls_made = 0
|
|
48
|
+
|
|
49
|
+
def _rate_limited_get(self, url: str, params: Optional[Dict] = None) -> Optional[Dict]:
|
|
50
|
+
if self.rate_limit_reached:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
if params is None:
|
|
54
|
+
params = {}
|
|
55
|
+
params['apikey'] = self.api_key
|
|
56
|
+
|
|
57
|
+
elapsed = time.time() - self.last_call_time
|
|
58
|
+
if elapsed < self.RATE_LIMIT_DELAY:
|
|
59
|
+
time.sleep(self.RATE_LIMIT_DELAY - elapsed)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
response = self.session.get(url, params=params, timeout=30)
|
|
63
|
+
self.last_call_time = time.time()
|
|
64
|
+
self.api_calls_made += 1
|
|
65
|
+
|
|
66
|
+
if response.status_code == 200:
|
|
67
|
+
self.retry_count = 0
|
|
68
|
+
return response.json()
|
|
69
|
+
elif response.status_code == 429:
|
|
70
|
+
self.retry_count += 1
|
|
71
|
+
if self.retry_count <= self.max_retries:
|
|
72
|
+
print("WARNING: Rate limit exceeded. Waiting 60 seconds...", file=sys.stderr)
|
|
73
|
+
time.sleep(60)
|
|
74
|
+
return self._rate_limited_get(url, params)
|
|
75
|
+
else:
|
|
76
|
+
print("ERROR: Daily API rate limit reached.", file=sys.stderr)
|
|
77
|
+
self.rate_limit_reached = True
|
|
78
|
+
return None
|
|
79
|
+
else:
|
|
80
|
+
print(f"ERROR: API request failed: {response.status_code} - {response.text[:200]}",
|
|
81
|
+
file=sys.stderr)
|
|
82
|
+
return None
|
|
83
|
+
except requests.exceptions.RequestException as e:
|
|
84
|
+
print(f"ERROR: Request exception: {e}", file=sys.stderr)
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
def get_sp500_constituents(self) -> Optional[List[Dict]]:
|
|
88
|
+
"""Fetch S&P 500 constituent list.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
List of dicts with keys: symbol, name, sector, subSector
|
|
92
|
+
or None on failure.
|
|
93
|
+
"""
|
|
94
|
+
cache_key = "sp500_constituents"
|
|
95
|
+
if cache_key in self.cache:
|
|
96
|
+
return self.cache[cache_key]
|
|
97
|
+
|
|
98
|
+
url = f"{self.BASE_URL}/sp500_constituent"
|
|
99
|
+
data = self._rate_limited_get(url)
|
|
100
|
+
if data:
|
|
101
|
+
self.cache[cache_key] = data
|
|
102
|
+
return data
|
|
103
|
+
|
|
104
|
+
def get_quote(self, symbols: str) -> Optional[List[Dict]]:
|
|
105
|
+
"""Fetch real-time quote data for one or more symbols (comma-separated)"""
|
|
106
|
+
cache_key = f"quote_{symbols}"
|
|
107
|
+
if cache_key in self.cache:
|
|
108
|
+
return self.cache[cache_key]
|
|
109
|
+
|
|
110
|
+
url = f"{self.BASE_URL}/quote/{symbols}"
|
|
111
|
+
data = self._rate_limited_get(url)
|
|
112
|
+
if data:
|
|
113
|
+
self.cache[cache_key] = data
|
|
114
|
+
return data
|
|
115
|
+
|
|
116
|
+
def get_historical_prices(self, symbol: str, days: int = 365) -> Optional[Dict]:
|
|
117
|
+
"""Fetch historical daily OHLCV data"""
|
|
118
|
+
cache_key = f"prices_{symbol}_{days}"
|
|
119
|
+
if cache_key in self.cache:
|
|
120
|
+
return self.cache[cache_key]
|
|
121
|
+
|
|
122
|
+
url = f"{self.BASE_URL}/historical-price-full/{symbol}"
|
|
123
|
+
params = {"timeseries": days}
|
|
124
|
+
data = self._rate_limited_get(url, params)
|
|
125
|
+
if data:
|
|
126
|
+
self.cache[cache_key] = data
|
|
127
|
+
return data
|
|
128
|
+
|
|
129
|
+
def get_batch_quotes(self, symbols: List[str]) -> Dict[str, Dict]:
|
|
130
|
+
"""Fetch quotes for a list of symbols, batching up to 5 per request"""
|
|
131
|
+
results = {}
|
|
132
|
+
batch_size = 5
|
|
133
|
+
for i in range(0, len(symbols), batch_size):
|
|
134
|
+
batch = symbols[i:i+batch_size]
|
|
135
|
+
batch_str = ",".join(batch)
|
|
136
|
+
quotes = self.get_quote(batch_str)
|
|
137
|
+
if quotes:
|
|
138
|
+
for q in quotes:
|
|
139
|
+
results[q['symbol']] = q
|
|
140
|
+
return results
|
|
141
|
+
|
|
142
|
+
def get_batch_historical(self, symbols: List[str], days: int = 260) -> Dict[str, List[Dict]]:
|
|
143
|
+
"""Fetch historical prices for multiple symbols"""
|
|
144
|
+
results = {}
|
|
145
|
+
for symbol in symbols:
|
|
146
|
+
data = self.get_historical_prices(symbol, days=days)
|
|
147
|
+
if data and 'historical' in data:
|
|
148
|
+
results[symbol] = data['historical']
|
|
149
|
+
return results
|
|
150
|
+
|
|
151
|
+
def calculate_sma(self, prices: List[float], period: int) -> float:
|
|
152
|
+
"""Calculate Simple Moving Average from a list of prices (most recent first)"""
|
|
153
|
+
if len(prices) < period:
|
|
154
|
+
return sum(prices) / len(prices)
|
|
155
|
+
return sum(prices[:period]) / period
|
|
156
|
+
|
|
157
|
+
def get_api_stats(self) -> Dict:
|
|
158
|
+
return {
|
|
159
|
+
"cache_entries": len(self.cache),
|
|
160
|
+
"api_calls_made": self.api_calls_made,
|
|
161
|
+
"rate_limit_reached": self.rate_limit_reached,
|
|
162
|
+
}
|