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.
Files changed (362) hide show
  1. package/.claude/skills/README.md +80 -0
  2. package/.claude/skills/backtest-expert/SKILL.md +206 -0
  3. package/.claude/skills/backtest-expert/references/failed_tests.md +236 -0
  4. package/.claude/skills/backtest-expert/references/methodology.md +227 -0
  5. package/.claude/skills/breadth-chart-analyst/SKILL.md +583 -0
  6. package/.claude/skills/breadth-chart-analyst/assets/SP500_Breadth_Index_200MA_8MA.jpeg +0 -0
  7. package/.claude/skills/breadth-chart-analyst/assets/US_Stock_Market_Uptrend_Ratio.jpeg +0 -0
  8. package/.claude/skills/breadth-chart-analyst/assets/breadth_analysis_template.md +558 -0
  9. package/.claude/skills/breadth-chart-analyst/references/breadth_chart_methodology.md +590 -0
  10. package/.claude/skills/canslim-screener/SKILL.md +599 -0
  11. package/.claude/skills/canslim-screener/references/canslim_methodology.md +606 -0
  12. package/.claude/skills/canslim-screener/references/fmp_api_endpoints.md +707 -0
  13. package/.claude/skills/canslim-screener/references/interpretation_guide.md +516 -0
  14. package/.claude/skills/canslim-screener/references/scoring_system.md +597 -0
  15. package/.claude/skills/canslim-screener/scripts/calculators/earnings_calculator.py +343 -0
  16. package/.claude/skills/canslim-screener/scripts/calculators/growth_calculator.py +334 -0
  17. package/.claude/skills/canslim-screener/scripts/calculators/institutional_calculator.py +347 -0
  18. package/.claude/skills/canslim-screener/scripts/calculators/leadership_calculator.py +380 -0
  19. package/.claude/skills/canslim-screener/scripts/calculators/market_calculator.py +244 -0
  20. package/.claude/skills/canslim-screener/scripts/calculators/new_highs_calculator.py +194 -0
  21. package/.claude/skills/canslim-screener/scripts/calculators/supply_demand_calculator.py +221 -0
  22. package/.claude/skills/canslim-screener/scripts/finviz_stock_client.py +227 -0
  23. package/.claude/skills/canslim-screener/scripts/fmp_client.py +393 -0
  24. package/.claude/skills/canslim-screener/scripts/report_generator.py +405 -0
  25. package/.claude/skills/canslim-screener/scripts/scorer.py +625 -0
  26. package/.claude/skills/canslim-screener/scripts/screen_canslim.py +361 -0
  27. package/.claude/skills/canslim-screener/scripts/test_institutional_endpoint.py +109 -0
  28. package/.claude/skills/chart/SKILL.md +20 -0
  29. package/.claude/skills/dividend-growth-pullback-screener/SKILL.md +322 -0
  30. package/.claude/skills/dividend-growth-pullback-screener/references/dividend_growth_compounding.md +400 -0
  31. package/.claude/skills/dividend-growth-pullback-screener/references/fmp_api_guide.md +642 -0
  32. package/.claude/skills/dividend-growth-pullback-screener/references/rsi_oversold_strategy.md +333 -0
  33. package/.claude/skills/dividend-growth-pullback-screener/scripts/screen_dividend_growth_rsi.py +1155 -0
  34. package/.claude/skills/earnings-calendar/SKILL.md +721 -0
  35. package/.claude/skills/earnings-calendar/assets/earnings_report_template.md +102 -0
  36. package/.claude/skills/earnings-calendar/references/fmp_api_guide.md +590 -0
  37. package/.claude/skills/earnings-calendar/scripts/fetch_earnings_fmp.py +443 -0
  38. package/.claude/skills/earnings-calendar/scripts/generate_report.py +366 -0
  39. package/.claude/skills/economic-calendar-fetcher/SKILL.md +365 -0
  40. package/.claude/skills/economic-calendar-fetcher/references/fmp_api_documentation.md +345 -0
  41. package/.claude/skills/economic-calendar-fetcher/scripts/get_economic_calendar.py +267 -0
  42. package/.claude/skills/ftd-detector/SKILL.md +147 -0
  43. package/.claude/skills/ftd-detector/references/ftd_methodology.md +188 -0
  44. package/.claude/skills/ftd-detector/references/post_ftd_guide.md +185 -0
  45. package/.claude/skills/ftd-detector/scripts/fmp_client.py +158 -0
  46. package/.claude/skills/ftd-detector/scripts/ftd_detector.py +280 -0
  47. package/.claude/skills/ftd-detector/scripts/post_ftd_monitor.py +404 -0
  48. package/.claude/skills/ftd-detector/scripts/rally_tracker.py +508 -0
  49. package/.claude/skills/ftd-detector/scripts/report_generator.py +341 -0
  50. package/.claude/skills/ftd-detector/scripts/tests/conftest.py +9 -0
  51. package/.claude/skills/ftd-detector/scripts/tests/helpers.py +107 -0
  52. package/.claude/skills/ftd-detector/scripts/tests/test_post_ftd_monitor.py +311 -0
  53. package/.claude/skills/ftd-detector/scripts/tests/test_rally_tracker.py +302 -0
  54. package/.claude/skills/institutional-flow-tracker/README.md +362 -0
  55. package/.claude/skills/institutional-flow-tracker/SKILL.md +357 -0
  56. package/.claude/skills/institutional-flow-tracker/references/13f_filings_guide.md +383 -0
  57. package/.claude/skills/institutional-flow-tracker/references/institutional_investor_types.md +580 -0
  58. package/.claude/skills/institutional-flow-tracker/references/interpretation_framework.md +573 -0
  59. package/.claude/skills/institutional-flow-tracker/scripts/analyze_single_stock.py +457 -0
  60. package/.claude/skills/institutional-flow-tracker/scripts/track_institution_portfolio.py +108 -0
  61. package/.claude/skills/institutional-flow-tracker/scripts/track_institutional_flow.py +450 -0
  62. package/.claude/skills/macro-regime-detector/SKILL.md +86 -0
  63. package/.claude/skills/macro-regime-detector/references/historical_regimes.md +124 -0
  64. package/.claude/skills/macro-regime-detector/references/indicator_interpretation_guide.md +144 -0
  65. package/.claude/skills/macro-regime-detector/references/regime_detection_methodology.md +138 -0
  66. package/.claude/skills/macro-regime-detector/scripts/calculators/__init__.py +1 -0
  67. package/.claude/skills/macro-regime-detector/scripts/calculators/concentration_calculator.py +165 -0
  68. package/.claude/skills/macro-regime-detector/scripts/calculators/credit_conditions_calculator.py +124 -0
  69. package/.claude/skills/macro-regime-detector/scripts/calculators/equity_bond_calculator.py +198 -0
  70. package/.claude/skills/macro-regime-detector/scripts/calculators/sector_rotation_calculator.py +123 -0
  71. package/.claude/skills/macro-regime-detector/scripts/calculators/size_factor_calculator.py +131 -0
  72. package/.claude/skills/macro-regime-detector/scripts/calculators/utils.py +347 -0
  73. package/.claude/skills/macro-regime-detector/scripts/calculators/yield_curve_calculator.py +279 -0
  74. package/.claude/skills/macro-regime-detector/scripts/fmp_client.py +134 -0
  75. package/.claude/skills/macro-regime-detector/scripts/macro_regime_detector.py +278 -0
  76. package/.claude/skills/macro-regime-detector/scripts/report_generator.py +327 -0
  77. package/.claude/skills/macro-regime-detector/scripts/scorer.py +574 -0
  78. package/.claude/skills/macro-regime-detector/scripts/tests/conftest.py +9 -0
  79. package/.claude/skills/macro-regime-detector/scripts/tests/test_concentration.py +78 -0
  80. package/.claude/skills/macro-regime-detector/scripts/tests/test_credit_conditions.py +59 -0
  81. package/.claude/skills/macro-regime-detector/scripts/tests/test_equity_bond.py +74 -0
  82. package/.claude/skills/macro-regime-detector/scripts/tests/test_helpers.py +90 -0
  83. package/.claude/skills/macro-regime-detector/scripts/tests/test_scorer.py +439 -0
  84. package/.claude/skills/macro-regime-detector/scripts/tests/test_sector_rotation.py +78 -0
  85. package/.claude/skills/macro-regime-detector/scripts/tests/test_size_factor.py +59 -0
  86. package/.claude/skills/macro-regime-detector/scripts/tests/test_utils.py +126 -0
  87. package/.claude/skills/macro-regime-detector/scripts/tests/test_yield_curve.py +64 -0
  88. package/.claude/skills/market-breadth-analyzer/SKILL.md +121 -0
  89. package/.claude/skills/market-breadth-analyzer/references/breadth_analysis_methodology.md +168 -0
  90. package/.claude/skills/market-breadth-analyzer/scripts/calculators/__init__.py +1 -0
  91. package/.claude/skills/market-breadth-analyzer/scripts/calculators/bearish_signal_calculator.py +150 -0
  92. package/.claude/skills/market-breadth-analyzer/scripts/calculators/cycle_calculator.py +168 -0
  93. package/.claude/skills/market-breadth-analyzer/scripts/calculators/divergence_calculator.py +119 -0
  94. package/.claude/skills/market-breadth-analyzer/scripts/calculators/historical_context_calculator.py +120 -0
  95. package/.claude/skills/market-breadth-analyzer/scripts/calculators/ma_crossover_calculator.py +115 -0
  96. package/.claude/skills/market-breadth-analyzer/scripts/calculators/trend_level_calculator.py +103 -0
  97. package/.claude/skills/market-breadth-analyzer/scripts/csv_client.py +225 -0
  98. package/.claude/skills/market-breadth-analyzer/scripts/market_breadth_analyzer.py +307 -0
  99. package/.claude/skills/market-breadth-analyzer/scripts/report_generator.py +330 -0
  100. package/.claude/skills/market-breadth-analyzer/scripts/scorer.py +271 -0
  101. package/.claude/skills/market-environment-analysis/SKILL.md +139 -0
  102. package/.claude/skills/market-environment-analysis/references/analysis_patterns.md +124 -0
  103. package/.claude/skills/market-environment-analysis/references/indicators.md +99 -0
  104. package/.claude/skills/market-environment-analysis/scripts/market_utils.py +127 -0
  105. package/.claude/skills/market-news-analyst/SKILL.md +714 -0
  106. package/.claude/skills/market-news-analyst/references/corporate_news_impact.md +446 -0
  107. package/.claude/skills/market-news-analyst/references/geopolitical_commodity_correlations.md +499 -0
  108. package/.claude/skills/market-news-analyst/references/market_event_patterns.md +393 -0
  109. package/.claude/skills/market-news-analyst/references/trusted_news_sources.md +510 -0
  110. package/.claude/skills/market-top-detector/SKILL.md +159 -0
  111. package/.claude/skills/market-top-detector/references/distribution_day_guide.md +100 -0
  112. package/.claude/skills/market-top-detector/references/historical_tops.md +142 -0
  113. package/.claude/skills/market-top-detector/references/market_top_methodology.md +167 -0
  114. package/.claude/skills/market-top-detector/scripts/calculators/__init__.py +17 -0
  115. package/.claude/skills/market-top-detector/scripts/calculators/breadth_calculator.py +116 -0
  116. package/.claude/skills/market-top-detector/scripts/calculators/defensive_rotation_calculator.py +127 -0
  117. package/.claude/skills/market-top-detector/scripts/calculators/distribution_day_calculator.py +161 -0
  118. package/.claude/skills/market-top-detector/scripts/calculators/index_technical_calculator.py +254 -0
  119. package/.claude/skills/market-top-detector/scripts/calculators/leading_stock_calculator.py +198 -0
  120. package/.claude/skills/market-top-detector/scripts/calculators/sentiment_calculator.py +213 -0
  121. package/.claude/skills/market-top-detector/scripts/fmp_client.py +158 -0
  122. package/.claude/skills/market-top-detector/scripts/market_top_detector.py +349 -0
  123. package/.claude/skills/market-top-detector/scripts/report_generator.py +314 -0
  124. package/.claude/skills/market-top-detector/scripts/scorer.py +473 -0
  125. package/.claude/skills/market-top-detector/scripts/tests/conftest.py +9 -0
  126. package/.claude/skills/market-top-detector/scripts/tests/helpers.py +49 -0
  127. package/.claude/skills/market-top-detector/scripts/tests/test_breadth.py +62 -0
  128. package/.claude/skills/market-top-detector/scripts/tests/test_defensive_rotation.py +56 -0
  129. package/.claude/skills/market-top-detector/scripts/tests/test_distribution_day.py +92 -0
  130. package/.claude/skills/market-top-detector/scripts/tests/test_index_technical.py +73 -0
  131. package/.claude/skills/market-top-detector/scripts/tests/test_leading_stock.py +57 -0
  132. package/.claude/skills/market-top-detector/scripts/tests/test_scorer.py +180 -0
  133. package/.claude/skills/market-top-detector/scripts/tests/test_sentiment.py +64 -0
  134. package/.claude/skills/options-strategy-advisor/README.md +469 -0
  135. package/.claude/skills/options-strategy-advisor/SKILL.md +959 -0
  136. package/.claude/skills/options-strategy-advisor/scripts/black_scholes.py +495 -0
  137. package/.claude/skills/pair-trade-screener/README.md +389 -0
  138. package/.claude/skills/pair-trade-screener/SKILL.md +622 -0
  139. package/.claude/skills/pair-trade-screener/references/cointegration_guide.md +745 -0
  140. package/.claude/skills/pair-trade-screener/references/methodology.md +853 -0
  141. package/.claude/skills/pair-trade-screener/scripts/analyze_spread.py +394 -0
  142. package/.claude/skills/pair-trade-screener/scripts/find_pairs.py +535 -0
  143. package/.claude/skills/portfolio-manager/README.md +394 -0
  144. package/.claude/skills/portfolio-manager/SKILL.md +750 -0
  145. package/.claude/skills/portfolio-manager/references/alpaca-mcp-setup.md +367 -0
  146. package/.claude/skills/portfolio-manager/references/asset-allocation.md +502 -0
  147. package/.claude/skills/portfolio-manager/references/diversification-principles.md +553 -0
  148. package/.claude/skills/portfolio-manager/references/portfolio-risk-metrics.md +603 -0
  149. package/.claude/skills/portfolio-manager/references/position-evaluation.md +477 -0
  150. package/.claude/skills/portfolio-manager/references/rebalancing-strategies.md +715 -0
  151. package/.claude/skills/portfolio-manager/references/risk-profile-questionnaire.md +608 -0
  152. package/.claude/skills/portfolio-manager/references/target-allocations.md +558 -0
  153. package/.claude/skills/portfolio-manager/scripts/test_alpaca_connection.py +286 -0
  154. package/.claude/skills/scenario-analyzer/SKILL.md +317 -0
  155. package/.claude/skills/scenario-analyzer/references/headline_event_patterns.md +264 -0
  156. package/.claude/skills/scenario-analyzer/references/scenario_playbooks.md +320 -0
  157. package/.claude/skills/scenario-analyzer/references/sector_sensitivity_matrix.md +217 -0
  158. package/.claude/skills/sector-analyst/SKILL.md +206 -0
  159. package/.claude/skills/sector-analyst/assets/industory_performance_1.jpeg +0 -0
  160. package/.claude/skills/sector-analyst/assets/industory_performance_2.jpeg +0 -0
  161. package/.claude/skills/sector-analyst/assets/sector_performance.jpeg +0 -0
  162. package/.claude/skills/sector-analyst/references/sector_rotation.md +170 -0
  163. package/.claude/skills/stanley-druckenmiller-investment/SKILL.md +84 -0
  164. package/.claude/skills/stanley-druckenmiller-investment/references/case-studies.md +148 -0
  165. package/.claude/skills/stanley-druckenmiller-investment/references/investment-philosophy.md +80 -0
  166. package/.claude/skills/stanley-druckenmiller-investment/references/market-analysis-guide.md +146 -0
  167. package/.claude/skills/stock/NOTION_SETUP.md +33 -0
  168. package/.claude/skills/stock/SKILL.md +38 -0
  169. package/.claude/skills/technical-analyst/SKILL.md +238 -0
  170. package/.claude/skills/technical-analyst/assets/analysis_template.md +183 -0
  171. package/.claude/skills/technical-analyst/references/technical_analysis_framework.md +282 -0
  172. package/.claude/skills/theme-detector/SKILL.md +320 -0
  173. package/.claude/skills/theme-detector/assets/report_template.md +155 -0
  174. package/.claude/skills/theme-detector/references/cross_sector_themes.md +252 -0
  175. package/.claude/skills/theme-detector/references/finviz_industry_codes.md +403 -0
  176. package/.claude/skills/theme-detector/references/thematic_etf_catalog.md +333 -0
  177. package/.claude/skills/theme-detector/references/theme_detection_methodology.md +430 -0
  178. package/.claude/skills/theme-detector/scripts/calculators/__init__.py +1 -0
  179. package/.claude/skills/theme-detector/scripts/calculators/heat_calculator.py +123 -0
  180. package/.claude/skills/theme-detector/scripts/calculators/industry_ranker.py +98 -0
  181. package/.claude/skills/theme-detector/scripts/calculators/lifecycle_calculator.py +172 -0
  182. package/.claude/skills/theme-detector/scripts/calculators/theme_classifier.py +195 -0
  183. package/.claude/skills/theme-detector/scripts/calculators/theme_discoverer.py +280 -0
  184. package/.claude/skills/theme-detector/scripts/config_loader.py +142 -0
  185. package/.claude/skills/theme-detector/scripts/default_theme_config.py +254 -0
  186. package/.claude/skills/theme-detector/scripts/etf_scanner.py +609 -0
  187. package/.claude/skills/theme-detector/scripts/finviz_performance_client.py +131 -0
  188. package/.claude/skills/theme-detector/scripts/report_generator.py +490 -0
  189. package/.claude/skills/theme-detector/scripts/representative_stock_selector.py +673 -0
  190. package/.claude/skills/theme-detector/scripts/scorer.py +87 -0
  191. package/.claude/skills/theme-detector/scripts/tests/README.md +21 -0
  192. package/.claude/skills/theme-detector/scripts/tests/conftest.py +9 -0
  193. package/.claude/skills/theme-detector/scripts/tests/test_config_loader.py +239 -0
  194. package/.claude/skills/theme-detector/scripts/tests/test_etf_scanner.py +810 -0
  195. package/.claude/skills/theme-detector/scripts/tests/test_heat_calculator.py +245 -0
  196. package/.claude/skills/theme-detector/scripts/tests/test_industry_ranker.py +256 -0
  197. package/.claude/skills/theme-detector/scripts/tests/test_lifecycle_calculator.py +301 -0
  198. package/.claude/skills/theme-detector/scripts/tests/test_report_generator.py +624 -0
  199. package/.claude/skills/theme-detector/scripts/tests/test_representative_stock_selector.py +898 -0
  200. package/.claude/skills/theme-detector/scripts/tests/test_scorer.py +185 -0
  201. package/.claude/skills/theme-detector/scripts/tests/test_theme_classifier.py +534 -0
  202. package/.claude/skills/theme-detector/scripts/tests/test_theme_detector_e2e.py +467 -0
  203. package/.claude/skills/theme-detector/scripts/tests/test_theme_discoverer.py +458 -0
  204. package/.claude/skills/theme-detector/scripts/tests/test_uptrend_client.py +76 -0
  205. package/.claude/skills/theme-detector/scripts/theme_detector.py +815 -0
  206. package/.claude/skills/theme-detector/scripts/themes.yaml +168 -0
  207. package/.claude/skills/theme-detector/scripts/uptrend_client.py +241 -0
  208. package/.claude/skills/uptrend-analyzer/SKILL.md +108 -0
  209. package/.claude/skills/uptrend-analyzer/references/uptrend_methodology.md +215 -0
  210. package/.claude/skills/uptrend-analyzer/scripts/calculators/__init__.py +1 -0
  211. package/.claude/skills/uptrend-analyzer/scripts/calculators/historical_context_calculator.py +122 -0
  212. package/.claude/skills/uptrend-analyzer/scripts/calculators/market_breadth_calculator.py +145 -0
  213. package/.claude/skills/uptrend-analyzer/scripts/calculators/momentum_calculator.py +183 -0
  214. package/.claude/skills/uptrend-analyzer/scripts/calculators/sector_participation_calculator.py +204 -0
  215. package/.claude/skills/uptrend-analyzer/scripts/calculators/sector_rotation_calculator.py +218 -0
  216. package/.claude/skills/uptrend-analyzer/scripts/data_fetcher.py +236 -0
  217. package/.claude/skills/uptrend-analyzer/scripts/report_generator.py +329 -0
  218. package/.claude/skills/uptrend-analyzer/scripts/scorer.py +276 -0
  219. package/.claude/skills/uptrend-analyzer/scripts/uptrend_analyzer.py +219 -0
  220. package/.claude/skills/us-market-bubble-detector/CHANGELOG.md +118 -0
  221. package/.claude/skills/us-market-bubble-detector/SKILL.md +545 -0
  222. package/.claude/skills/us-market-bubble-detector/references/bubble_framework.md +335 -0
  223. package/.claude/skills/us-market-bubble-detector/references/historical_cases.md +327 -0
  224. package/.claude/skills/us-market-bubble-detector/references/implementation_guide.md +473 -0
  225. package/.claude/skills/us-market-bubble-detector/references/quick_reference.md +354 -0
  226. package/.claude/skills/us-market-bubble-detector/references/quick_reference_en.md +342 -0
  227. package/.claude/skills/us-market-bubble-detector/scripts/bubble_scorer.py +309 -0
  228. package/.claude/skills/us-stock-analysis/SKILL.md +294 -0
  229. package/.claude/skills/us-stock-analysis/references/financial-metrics.md +172 -0
  230. package/.claude/skills/us-stock-analysis/references/fundamental-analysis.md +129 -0
  231. package/.claude/skills/us-stock-analysis/references/report-template.md +207 -0
  232. package/.claude/skills/us-stock-analysis/references/technical-analysis.md +93 -0
  233. package/.claude/skills/value-dividend-screener/SKILL.md +562 -0
  234. package/.claude/skills/value-dividend-screener/references/fmp_api_guide.md +348 -0
  235. package/.claude/skills/value-dividend-screener/references/screening_methodology.md +315 -0
  236. package/.claude/skills/value-dividend-screener/scripts/screen_dividend_stocks.py +1138 -0
  237. package/.claude/skills/vcp-screener/SKILL.md +79 -0
  238. package/.claude/skills/vcp-screener/references/fmp_api_endpoints.md +45 -0
  239. package/.claude/skills/vcp-screener/references/scoring_system.md +154 -0
  240. package/.claude/skills/vcp-screener/references/vcp_methodology.md +124 -0
  241. package/.claude/skills/vcp-screener/scripts/calculators/__init__.py +1 -0
  242. package/.claude/skills/vcp-screener/scripts/calculators/pivot_proximity_calculator.py +139 -0
  243. package/.claude/skills/vcp-screener/scripts/calculators/relative_strength_calculator.py +161 -0
  244. package/.claude/skills/vcp-screener/scripts/calculators/trend_template_calculator.py +228 -0
  245. package/.claude/skills/vcp-screener/scripts/calculators/vcp_pattern_calculator.py +322 -0
  246. package/.claude/skills/vcp-screener/scripts/calculators/volume_pattern_calculator.py +121 -0
  247. package/.claude/skills/vcp-screener/scripts/fmp_client.py +162 -0
  248. package/.claude/skills/vcp-screener/scripts/report_generator.py +317 -0
  249. package/.claude/skills/vcp-screener/scripts/scorer.py +155 -0
  250. package/.claude/skills/vcp-screener/scripts/screen_vcp.py +536 -0
  251. package/.claude/skills/vcp-screener/scripts/tests/__init__.py +0 -0
  252. package/.claude/skills/vcp-screener/scripts/tests/conftest.py +9 -0
  253. package/.claude/skills/vcp-screener/scripts/tests/test_vcp_screener.py +834 -0
  254. package/.claude/skills/weekly-trade-strategy/.claude/agents/druckenmiller-strategy-planner.md +300 -0
  255. package/.claude/skills/weekly-trade-strategy/.claude/agents/market-news-analyzer.md +239 -0
  256. package/.claude/skills/weekly-trade-strategy/.claude/agents/technical-market-analyst.md +187 -0
  257. package/.claude/skills/weekly-trade-strategy/.claude/agents/us-market-analyst.md +218 -0
  258. package/.claude/skills/weekly-trade-strategy/.claude/agents/weekly-trade-blog-writer.md +318 -0
  259. package/.claude/skills/weekly-trade-strategy/.claude/skills/breadth-chart-analyst/SKILL.md +662 -0
  260. package/.claude/skills/weekly-trade-strategy/.claude/skills/breadth-chart-analyst/assets/SP500_Breadth_Index_200MA_8MA.jpeg +0 -0
  261. package/.claude/skills/weekly-trade-strategy/.claude/skills/breadth-chart-analyst/assets/US_Stock_Market_Uptrend_Ratio.jpeg +0 -0
  262. package/.claude/skills/weekly-trade-strategy/.claude/skills/breadth-chart-analyst/assets/breadth_analysis_template.md +558 -0
  263. package/.claude/skills/weekly-trade-strategy/.claude/skills/breadth-chart-analyst/references/breadth_chart_methodology.md +590 -0
  264. package/.claude/skills/weekly-trade-strategy/.claude/skills/earnings-calendar/SKILL.md +721 -0
  265. package/.claude/skills/weekly-trade-strategy/.claude/skills/earnings-calendar/assets/earnings_report_template.md +102 -0
  266. package/.claude/skills/weekly-trade-strategy/.claude/skills/earnings-calendar/earnings_calendar_2025-11-02.md +447 -0
  267. package/.claude/skills/weekly-trade-strategy/.claude/skills/earnings-calendar/references/fmp_api_guide.md +590 -0
  268. package/.claude/skills/weekly-trade-strategy/.claude/skills/earnings-calendar/scripts/fetch_earnings_fmp.py +443 -0
  269. package/.claude/skills/weekly-trade-strategy/.claude/skills/earnings-calendar/scripts/generate_report.py +366 -0
  270. package/.claude/skills/weekly-trade-strategy/.claude/skills/economic-calendar-fetcher/SKILL.md +365 -0
  271. package/.claude/skills/weekly-trade-strategy/.claude/skills/economic-calendar-fetcher/references/fmp_api_documentation.md +345 -0
  272. package/.claude/skills/weekly-trade-strategy/.claude/skills/economic-calendar-fetcher/scripts/get_economic_calendar.py +267 -0
  273. package/.claude/skills/weekly-trade-strategy/.claude/skills/market-environment-analysis/SKILL.md +139 -0
  274. package/.claude/skills/weekly-trade-strategy/.claude/skills/market-environment-analysis/references/analysis_patterns.md +124 -0
  275. package/.claude/skills/weekly-trade-strategy/.claude/skills/market-environment-analysis/references/indicators.md +99 -0
  276. package/.claude/skills/weekly-trade-strategy/.claude/skills/market-environment-analysis/scripts/market_utils.py +127 -0
  277. package/.claude/skills/weekly-trade-strategy/.claude/skills/market-news-analyst/SKILL.md +714 -0
  278. package/.claude/skills/weekly-trade-strategy/.claude/skills/market-news-analyst/references/corporate_news_impact.md +446 -0
  279. package/.claude/skills/weekly-trade-strategy/.claude/skills/market-news-analyst/references/geopolitical_commodity_correlations.md +499 -0
  280. package/.claude/skills/weekly-trade-strategy/.claude/skills/market-news-analyst/references/market_event_patterns.md +393 -0
  281. package/.claude/skills/weekly-trade-strategy/.claude/skills/market-news-analyst/references/trusted_news_sources.md +510 -0
  282. package/.claude/skills/weekly-trade-strategy/.claude/skills/sector-analyst/SKILL.md +206 -0
  283. package/.claude/skills/weekly-trade-strategy/.claude/skills/sector-analyst/assets/industory_performance_1.jpeg +0 -0
  284. package/.claude/skills/weekly-trade-strategy/.claude/skills/sector-analyst/assets/industory_performance_2.jpeg +0 -0
  285. package/.claude/skills/weekly-trade-strategy/.claude/skills/sector-analyst/assets/sector_performance.jpeg +0 -0
  286. package/.claude/skills/weekly-trade-strategy/.claude/skills/sector-analyst/references/sector_rotation.md +170 -0
  287. package/.claude/skills/weekly-trade-strategy/.claude/skills/stanley-druckenmiller-investment/SKILL.md +84 -0
  288. package/.claude/skills/weekly-trade-strategy/.claude/skills/stanley-druckenmiller-investment/references/case-studies.md +148 -0
  289. package/.claude/skills/weekly-trade-strategy/.claude/skills/stanley-druckenmiller-investment/references/investment-philosophy.md +80 -0
  290. package/.claude/skills/weekly-trade-strategy/.claude/skills/stanley-druckenmiller-investment/references/market-analysis-guide.md +146 -0
  291. package/.claude/skills/weekly-trade-strategy/.claude/skills/technical-analyst/SKILL.md +238 -0
  292. package/.claude/skills/weekly-trade-strategy/.claude/skills/technical-analyst/assets/analysis_template.md +183 -0
  293. package/.claude/skills/weekly-trade-strategy/.claude/skills/technical-analyst/references/technical_analysis_framework.md +282 -0
  294. package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/CHANGELOG.md +118 -0
  295. package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/SKILL.md +545 -0
  296. package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/references/bubble_framework.md +335 -0
  297. package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/references/historical_cases.md +327 -0
  298. package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/references/implementation_guide.md +473 -0
  299. package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/references/quick_reference.md +354 -0
  300. package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/references/quick_reference_en.md +342 -0
  301. package/.claude/skills/weekly-trade-strategy/.claude/skills/us-market-bubble-detector/scripts/bubble_scorer.py +309 -0
  302. package/.claude/skills/weekly-trade-strategy/.claude/skills/us-stock-analysis/SKILL.md +294 -0
  303. package/.claude/skills/weekly-trade-strategy/.claude/skills/us-stock-analysis/references/financial-metrics.md +172 -0
  304. package/.claude/skills/weekly-trade-strategy/.claude/skills/us-stock-analysis/references/fundamental-analysis.md +129 -0
  305. package/.claude/skills/weekly-trade-strategy/.claude/skills/us-stock-analysis/references/report-template.md +207 -0
  306. package/.claude/skills/weekly-trade-strategy/.claude/skills/us-stock-analysis/references/technical-analysis.md +93 -0
  307. package/.claude/skills/weekly-trade-strategy/CLAUDE.md +454 -0
  308. package/.claude/skills/weekly-trade-strategy/README.md +287 -0
  309. package/.claude/skills/weekly-trade-strategy/blogs/.gitkeep +0 -0
  310. package/.claude/skills/weekly-trade-strategy/charts/.gitkeep +0 -0
  311. package/.claude/skills/weekly-trade-strategy/earnings_data.json +10054 -0
  312. package/.claude/skills/weekly-trade-strategy/skills/breadth-chart-analyst/SKILL.md +662 -0
  313. package/.claude/skills/weekly-trade-strategy/skills/breadth-chart-analyst/assets/SP500_Breadth_Index_200MA_8MA.jpeg +0 -0
  314. package/.claude/skills/weekly-trade-strategy/skills/breadth-chart-analyst/assets/US_Stock_Market_Uptrend_Ratio.jpeg +0 -0
  315. package/.claude/skills/weekly-trade-strategy/skills/breadth-chart-analyst/assets/breadth_analysis_template.md +558 -0
  316. package/.claude/skills/weekly-trade-strategy/skills/breadth-chart-analyst/references/breadth_chart_methodology.md +590 -0
  317. package/.claude/skills/weekly-trade-strategy/skills/earnings-calendar/SKILL.md +721 -0
  318. package/.claude/skills/weekly-trade-strategy/skills/earnings-calendar/assets/earnings_report_template.md +102 -0
  319. package/.claude/skills/weekly-trade-strategy/skills/earnings-calendar/earnings_calendar_2025-11-02.md +447 -0
  320. package/.claude/skills/weekly-trade-strategy/skills/earnings-calendar/references/fmp_api_guide.md +590 -0
  321. package/.claude/skills/weekly-trade-strategy/skills/earnings-calendar/scripts/fetch_earnings_fmp.py +443 -0
  322. package/.claude/skills/weekly-trade-strategy/skills/earnings-calendar/scripts/generate_report.py +366 -0
  323. package/.claude/skills/weekly-trade-strategy/skills/economic-calendar-fetcher/SKILL.md +365 -0
  324. package/.claude/skills/weekly-trade-strategy/skills/economic-calendar-fetcher/references/fmp_api_documentation.md +345 -0
  325. package/.claude/skills/weekly-trade-strategy/skills/economic-calendar-fetcher/scripts/get_economic_calendar.py +267 -0
  326. package/.claude/skills/weekly-trade-strategy/skills/market-environment-analysis/SKILL.md +139 -0
  327. package/.claude/skills/weekly-trade-strategy/skills/market-environment-analysis/references/analysis_patterns.md +124 -0
  328. package/.claude/skills/weekly-trade-strategy/skills/market-environment-analysis/references/indicators.md +99 -0
  329. package/.claude/skills/weekly-trade-strategy/skills/market-environment-analysis/scripts/market_utils.py +127 -0
  330. package/.claude/skills/weekly-trade-strategy/skills/market-news-analyst/SKILL.md +714 -0
  331. package/.claude/skills/weekly-trade-strategy/skills/market-news-analyst/references/corporate_news_impact.md +446 -0
  332. package/.claude/skills/weekly-trade-strategy/skills/market-news-analyst/references/geopolitical_commodity_correlations.md +499 -0
  333. package/.claude/skills/weekly-trade-strategy/skills/market-news-analyst/references/market_event_patterns.md +393 -0
  334. package/.claude/skills/weekly-trade-strategy/skills/market-news-analyst/references/trusted_news_sources.md +510 -0
  335. package/.claude/skills/weekly-trade-strategy/skills/sector-analyst/SKILL.md +206 -0
  336. package/.claude/skills/weekly-trade-strategy/skills/sector-analyst/assets/industory_performance_1.jpeg +0 -0
  337. package/.claude/skills/weekly-trade-strategy/skills/sector-analyst/assets/industory_performance_2.jpeg +0 -0
  338. package/.claude/skills/weekly-trade-strategy/skills/sector-analyst/assets/sector_performance.jpeg +0 -0
  339. package/.claude/skills/weekly-trade-strategy/skills/sector-analyst/references/sector_rotation.md +170 -0
  340. package/.claude/skills/weekly-trade-strategy/skills/stanley-druckenmiller-investment/SKILL.md +84 -0
  341. package/.claude/skills/weekly-trade-strategy/skills/stanley-druckenmiller-investment/references/case-studies.md +148 -0
  342. package/.claude/skills/weekly-trade-strategy/skills/stanley-druckenmiller-investment/references/investment-philosophy.md +80 -0
  343. package/.claude/skills/weekly-trade-strategy/skills/stanley-druckenmiller-investment/references/market-analysis-guide.md +146 -0
  344. package/.claude/skills/weekly-trade-strategy/skills/technical-analyst/SKILL.md +238 -0
  345. package/.claude/skills/weekly-trade-strategy/skills/technical-analyst/assets/analysis_template.md +183 -0
  346. package/.claude/skills/weekly-trade-strategy/skills/technical-analyst/references/technical_analysis_framework.md +282 -0
  347. package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/CHANGELOG.md +118 -0
  348. package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/SKILL.md +545 -0
  349. package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/references/bubble_framework.md +335 -0
  350. package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/references/historical_cases.md +327 -0
  351. package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/references/implementation_guide.md +473 -0
  352. package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/references/quick_reference.md +354 -0
  353. package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/references/quick_reference_en.md +342 -0
  354. package/.claude/skills/weekly-trade-strategy/skills/us-market-bubble-detector/scripts/bubble_scorer.py +309 -0
  355. package/.claude/skills/weekly-trade-strategy/skills/us-stock-analysis/SKILL.md +294 -0
  356. package/.claude/skills/weekly-trade-strategy/skills/us-stock-analysis/references/financial-metrics.md +172 -0
  357. package/.claude/skills/weekly-trade-strategy/skills/us-stock-analysis/references/fundamental-analysis.md +129 -0
  358. package/.claude/skills/weekly-trade-strategy/skills/us-stock-analysis/references/report-template.md +207 -0
  359. package/.claude/skills/weekly-trade-strategy/skills/us-stock-analysis/references/technical-analysis.md +93 -0
  360. package/.mcp.json +3 -0
  361. package/cli.mjs +16 -16
  362. package/package.json +4 -2
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Component 1: Distribution Day Count (Weight: 25%)
4
+
5
+ O'Neil's Distribution Day Rules:
6
+ - Distribution Day: Index drops >= 0.2% on higher volume than previous day
7
+ - Stalling Day: Volume increases but price gain < 0.1% (half weight)
8
+ - Days expire after 25 trading days
9
+ - Count the higher of S&P 500 or NASDAQ
10
+
11
+ Scoring:
12
+ 6+ distribution days -> 100 (Critical)
13
+ 5 days -> 90
14
+ 4 days -> 75 (O'Neil's warning threshold)
15
+ 3 days -> 55
16
+ 2 days -> 30
17
+ 1 day -> 15
18
+ 0 days -> 0
19
+ """
20
+
21
+ from typing import Dict, List, Optional
22
+
23
+
24
+ def calculate_distribution_days(sp500_history: List[Dict],
25
+ nasdaq_history: List[Dict]) -> Dict:
26
+ """
27
+ Calculate distribution day count for S&P 500 and NASDAQ.
28
+
29
+ Args:
30
+ sp500_history: List of daily OHLCV dicts (most recent first), at least 30 days
31
+ nasdaq_history: List of daily OHLCV dicts (most recent first), at least 30 days
32
+
33
+ Returns:
34
+ Dict with score (0-100), distribution_days, stalling_days, details
35
+ """
36
+ sp500_result = _count_distribution_days(sp500_history, "S&P 500")
37
+ nasdaq_result = _count_distribution_days(nasdaq_history, "NASDAQ")
38
+
39
+ # Use the higher (worse) effective count
40
+ sp500_effective = sp500_result["distribution_days"] + 0.5 * sp500_result["stalling_days"]
41
+ nasdaq_effective = nasdaq_result["distribution_days"] + 0.5 * nasdaq_result["stalling_days"]
42
+
43
+ if sp500_effective >= nasdaq_effective:
44
+ primary = sp500_result
45
+ primary_name = "S&P 500"
46
+ secondary = nasdaq_result
47
+ secondary_name = "NASDAQ"
48
+ else:
49
+ primary = nasdaq_result
50
+ primary_name = "NASDAQ"
51
+ secondary = sp500_result
52
+ secondary_name = "S&P 500"
53
+
54
+ effective_count = max(sp500_effective, nasdaq_effective)
55
+ score = _score_distribution_days(effective_count)
56
+
57
+ # Build signal description
58
+ if effective_count >= 5:
59
+ signal = "CRITICAL: Heavy distribution detected"
60
+ elif effective_count >= 4:
61
+ signal = "WARNING: O'Neil's threshold reached"
62
+ elif effective_count >= 3:
63
+ signal = "CAUTION: Moderate distribution"
64
+ elif effective_count >= 1:
65
+ signal = "MINOR: Some distribution present"
66
+ else:
67
+ signal = "CLEAR: No distribution"
68
+
69
+ return {
70
+ "score": score,
71
+ "effective_count": effective_count,
72
+ "signal": signal,
73
+ "primary_index": primary_name,
74
+ "sp500": {
75
+ "distribution_days": sp500_result["distribution_days"],
76
+ "stalling_days": sp500_result["stalling_days"],
77
+ "effective_count": sp500_effective,
78
+ "details": sp500_result["details"],
79
+ },
80
+ "nasdaq": {
81
+ "distribution_days": nasdaq_result["distribution_days"],
82
+ "stalling_days": nasdaq_result["stalling_days"],
83
+ "effective_count": nasdaq_effective,
84
+ "details": nasdaq_result["details"],
85
+ },
86
+ }
87
+
88
+
89
+ def _count_distribution_days(history: List[Dict], index_name: str) -> Dict:
90
+ """Count distribution and stalling days in the last 25 trading days"""
91
+ if not history or len(history) < 2:
92
+ return {"distribution_days": 0, "stalling_days": 0, "details": []}
93
+
94
+ # We need at least 26 days to check 25 days of change
95
+ # history[0] = most recent, history[1] = day before, etc.
96
+ window = min(25, len(history) - 1) # 25 trading day window
97
+
98
+ distribution_days = 0
99
+ stalling_days = 0
100
+ details = []
101
+
102
+ for i in range(window):
103
+ today = history[i]
104
+ yesterday = history[i + 1]
105
+
106
+ today_close = today.get("close", today.get("adjClose", 0))
107
+ yesterday_close = yesterday.get("close", yesterday.get("adjClose", 0))
108
+ today_volume = today.get("volume", 0)
109
+ yesterday_volume = yesterday.get("volume", 0)
110
+
111
+ if yesterday_close == 0 or yesterday_volume == 0:
112
+ continue
113
+
114
+ pct_change = (today_close - yesterday_close) / yesterday_close * 100
115
+ volume_increase = today_volume > yesterday_volume
116
+
117
+ date = today.get("date", f"day-{i}")
118
+
119
+ # Distribution day: price drops >= 0.2% AND volume increases
120
+ if pct_change <= -0.2 and volume_increase:
121
+ distribution_days += 1
122
+ details.append({
123
+ "date": date,
124
+ "type": "distribution",
125
+ "pct_change": round(pct_change, 2),
126
+ "volume_change": round((today_volume / yesterday_volume - 1) * 100, 1),
127
+ })
128
+
129
+ # Stalling day: volume increases but price gain < 0.1%
130
+ elif volume_increase and 0 <= pct_change < 0.1:
131
+ stalling_days += 1
132
+ details.append({
133
+ "date": date,
134
+ "type": "stalling",
135
+ "pct_change": round(pct_change, 2),
136
+ "volume_change": round((today_volume / yesterday_volume - 1) * 100, 1),
137
+ })
138
+
139
+ return {
140
+ "distribution_days": distribution_days,
141
+ "stalling_days": stalling_days,
142
+ "details": details,
143
+ }
144
+
145
+
146
+ def _score_distribution_days(effective_count: float) -> int:
147
+ """Convert effective distribution day count to 0-100 score"""
148
+ if effective_count >= 6:
149
+ return 100
150
+ elif effective_count >= 5:
151
+ return 90
152
+ elif effective_count >= 4:
153
+ return 75
154
+ elif effective_count >= 3:
155
+ return 55
156
+ elif effective_count >= 2:
157
+ return 30
158
+ elif effective_count >= 1:
159
+ return 15
160
+ else:
161
+ return 0
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Component 5: Index Technical Condition (Weight: 15%)
4
+
5
+ Evaluates technical health of S&P 500 and NASDAQ.
6
+ Uses shared historical data from Component 1 (no additional API calls).
7
+
8
+ Checkpoints (per index, S&P 500 + NASDAQ):
9
+ 1. Price < 21 EMA (short-term weakness) -> +8pt
10
+ 2. Price < 50 EMA (medium-term breakdown) -> +12pt
11
+ 3. 21 EMA < 50 EMA (MA bearish crossover) -> +10pt
12
+ 4. Price < 200 SMA (long-term trend breakdown) -> +15pt
13
+ 5. Failed rally pattern -> +10pt
14
+ 6. Lower highs pattern -> +10pt
15
+ 7. Gap down on volume -> +10pt
16
+
17
+ Max per index: ~75 points (not all conditions fire at once in early stages)
18
+ Final: average of both indices, scaled to 0-100
19
+ """
20
+
21
+ from typing import Dict, List, Optional
22
+ import sys
23
+ import os
24
+
25
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
26
+ from fmp_client import FMPClient
27
+
28
+
29
+ def calculate_index_technical(sp500_history: List[Dict],
30
+ nasdaq_history: List[Dict],
31
+ sp500_quote: Optional[Dict] = None,
32
+ nasdaq_quote: Optional[Dict] = None) -> Dict:
33
+ """
34
+ Calculate index technical condition score.
35
+
36
+ Args:
37
+ sp500_history: S&P 500 daily OHLCV (most recent first, 250+ days preferred)
38
+ nasdaq_history: NASDAQ daily OHLCV (most recent first, 250+ days preferred)
39
+ sp500_quote: Current S&P 500 quote (optional, for real-time price)
40
+ nasdaq_quote: Current NASDAQ/QQQ quote (optional)
41
+
42
+ Returns:
43
+ Dict with score (0-100), sp500_details, nasdaq_details, signal
44
+ """
45
+ sp500_result = _evaluate_index("S&P 500", sp500_history, sp500_quote)
46
+ nasdaq_result = _evaluate_index("NASDAQ", nasdaq_history, nasdaq_quote)
47
+
48
+ # Average only indices with available data
49
+ scores = []
50
+ if sp500_result.get("data_available", False):
51
+ scores.append(sp500_result["raw_score"])
52
+ if nasdaq_result.get("data_available", False):
53
+ scores.append(nasdaq_result["raw_score"])
54
+
55
+ if not scores:
56
+ final_score = 50 # Neutral when no data
57
+ signal = "NO DATA: Index data unavailable"
58
+ return {
59
+ "score": final_score,
60
+ "signal": signal,
61
+ "sp500": sp500_result,
62
+ "nasdaq": nasdaq_result,
63
+ "data_available": False,
64
+ }
65
+
66
+ avg_score = sum(scores) / len(scores)
67
+ final_score = round(min(100, max(0, avg_score)))
68
+
69
+ if final_score >= 70:
70
+ signal = "CRITICAL: Major technical breakdown"
71
+ elif final_score >= 50:
72
+ signal = "WARNING: Significant technical deterioration"
73
+ elif final_score >= 35:
74
+ signal = "CAUTION: Short-term weakness detected"
75
+ elif final_score >= 15:
76
+ signal = "MIXED: Minor technical concerns"
77
+ else:
78
+ signal = "HEALTHY: Technical structure intact"
79
+
80
+ return {
81
+ "score": final_score,
82
+ "signal": signal,
83
+ "sp500": sp500_result,
84
+ "nasdaq": nasdaq_result,
85
+ "data_available": True,
86
+ }
87
+
88
+
89
+ def _evaluate_index(name: str, history: List[Dict],
90
+ quote: Optional[Dict] = None) -> Dict:
91
+ """Evaluate a single index's technical condition"""
92
+ if not history or len(history) < 21:
93
+ return {"raw_score": 0, "flags": ["Insufficient data"], "mas": {},
94
+ "data_available": False}
95
+
96
+ closes = [d.get("close", d.get("adjClose", 0)) for d in history]
97
+ highs = [d.get("high", d.get("close", 0)) for d in history]
98
+ lows = [d.get("low", d.get("close", 0)) for d in history]
99
+ volumes = [d.get("volume", 0) for d in history]
100
+
101
+ # Current price (from quote or most recent close)
102
+ if quote:
103
+ price = quote.get("price", closes[0])
104
+ else:
105
+ price = closes[0]
106
+
107
+ score = 0
108
+ flags = []
109
+ mas = {}
110
+
111
+ # Calculate moving averages
112
+ ema21 = _calc_ema(closes, 21)
113
+ mas["ema21"] = round(ema21, 2)
114
+
115
+ if len(closes) >= 50:
116
+ ema50 = _calc_ema(closes, 50)
117
+ mas["ema50"] = round(ema50, 2)
118
+ else:
119
+ ema50 = None
120
+
121
+ if len(closes) >= 200:
122
+ sma200 = sum(closes[:200]) / 200
123
+ mas["sma200"] = round(sma200, 2)
124
+ else:
125
+ sma200 = None
126
+
127
+ # Check 1: Price < 21 EMA (short-term weakness)
128
+ if price < ema21:
129
+ score += 8
130
+ pct_below = (price - ema21) / ema21 * 100
131
+ flags.append(f"Below 21 EMA ({pct_below:+.1f}%)")
132
+
133
+ # Check 2: Price < 50 EMA (medium-term breakdown)
134
+ if ema50 and price < ema50:
135
+ score += 12
136
+ pct_below = (price - ema50) / ema50 * 100
137
+ flags.append(f"Below 50 EMA ({pct_below:+.1f}%)")
138
+
139
+ # Check 3: 21 EMA < 50 EMA (bearish crossover)
140
+ if ema50 and ema21 < ema50:
141
+ score += 10
142
+ flags.append("21 EMA < 50 EMA (bearish crossover)")
143
+
144
+ # Check 4: Price < 200 SMA (long-term trend breakdown)
145
+ if sma200 and price < sma200:
146
+ score += 15
147
+ pct_below = (price - sma200) / sma200 * 100
148
+ flags.append(f"Below 200 SMA ({pct_below:+.1f}%)")
149
+
150
+ # Check 5: Failed rally pattern
151
+ if _detect_failed_rally(closes, volumes):
152
+ score += 10
153
+ flags.append("Failed rally pattern detected")
154
+
155
+ # Check 6: Lower highs pattern (20 day)
156
+ if _detect_lower_highs(highs, lookback=20):
157
+ score += 10
158
+ flags.append("Lower highs pattern (20 day)")
159
+
160
+ # Check 7: Gap down on volume
161
+ if _detect_gap_down(history):
162
+ score += 10
163
+ flags.append("Recent gap down on volume")
164
+
165
+ return {
166
+ "raw_score": min(100, score),
167
+ "price": round(price, 2),
168
+ "flags": flags,
169
+ "mas": mas,
170
+ "data_available": True,
171
+ }
172
+
173
+
174
+ def _calc_ema(prices: List[float], period: int) -> float:
175
+ """Calculate EMA from prices (most recent first)"""
176
+ if len(prices) < period:
177
+ return sum(prices) / len(prices)
178
+
179
+ prices_rev = prices[::-1]
180
+ sma = sum(prices_rev[:period]) / period
181
+ ema = sma
182
+ k = 2 / (period + 1)
183
+ for p in prices_rev[period:]:
184
+ ema = p * k + ema * (1 - k)
185
+ return ema
186
+
187
+
188
+ def _detect_failed_rally(closes: List[float], volumes: List[int],
189
+ lookback: int = 15) -> bool:
190
+ """
191
+ Detect failed rally: price bounces then fails to make new high
192
+ within recent lookback period.
193
+ """
194
+ if len(closes) < lookback:
195
+ return False
196
+
197
+ recent = closes[:lookback]
198
+ # Find the peak in recent data
199
+ peak_idx = recent.index(max(recent))
200
+
201
+ # Failed rally: peak occurred 3-10 days ago, current price is below that peak
202
+ if 3 <= peak_idx <= 10:
203
+ # Price fell from peak
204
+ drop = (recent[0] - recent[peak_idx]) / recent[peak_idx] * 100
205
+ if drop < -2.0: # Dropped >2% from recent peak
206
+ return True
207
+ return False
208
+
209
+
210
+ def _detect_lower_highs(highs: List[float], lookback: int = 20) -> bool:
211
+ """Detect lower highs pattern in price action"""
212
+ if len(highs) < lookback:
213
+ return False
214
+
215
+ recent_highs = highs[:lookback]
216
+
217
+ # Find swing highs (local maxima)
218
+ swing_highs = []
219
+ for i in range(1, len(recent_highs) - 1):
220
+ if recent_highs[i] > recent_highs[i-1] and recent_highs[i] > recent_highs[i+1]:
221
+ swing_highs.append(recent_highs[i])
222
+
223
+ if len(swing_highs) < 2:
224
+ return False
225
+
226
+ # Most recent swing high lower than previous
227
+ return swing_highs[0] < swing_highs[1]
228
+
229
+
230
+ def _detect_gap_down(history: List[Dict], lookback: int = 5) -> bool:
231
+ """Detect gap down on increased volume in recent sessions"""
232
+ if len(history) < lookback + 1:
233
+ return False
234
+
235
+ for i in range(lookback):
236
+ today = history[i]
237
+ yesterday = history[i + 1]
238
+
239
+ today_open = today.get("open", 0)
240
+ yesterday_close = yesterday.get("close", yesterday.get("adjClose", 0))
241
+ today_volume = today.get("volume", 0)
242
+ yesterday_volume = yesterday.get("volume", 0)
243
+
244
+ if yesterday_close == 0 or yesterday_volume == 0:
245
+ continue
246
+
247
+ gap_pct = (today_open - yesterday_close) / yesterday_close * 100
248
+ volume_increase = today_volume > yesterday_volume * 1.1
249
+
250
+ # Gap down >= 0.5% on higher volume
251
+ if gap_pct <= -0.5 and volume_increase:
252
+ return True
253
+
254
+ return False
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Component 2: Leading Stock Health (Weight: 20%)
4
+
5
+ Evaluates health of leading/growth ETFs as proxy for market leadership.
6
+ Uses ETF baskets instead of individual stocks for API efficiency.
7
+
8
+ ETF Basket: ARKK, WCLD, IGV, XBI, SOXX, SMH, KWEB, TAN
9
+
10
+ Evaluation per ETF:
11
+ - Distance from 52-week high
12
+ - Position vs 50DMA and 200DMA
13
+ - Lower highs pattern detection
14
+
15
+ Scoring: Weighted average of ETF deterioration signals.
16
+ If 60%+ ETFs are deteriorating, apply 1.3x amplification.
17
+ """
18
+
19
+ from typing import Dict, List, Optional
20
+ import sys
21
+
22
+ # Leading/Growth ETF basket
23
+ LEADING_ETFS = ["ARKK", "WCLD", "IGV", "XBI", "SOXX", "SMH", "KWEB", "TAN"]
24
+
25
+
26
+ def calculate_leading_stock_health(quotes: Dict[str, Dict],
27
+ historical: Dict[str, List[Dict]]) -> Dict:
28
+ """
29
+ Calculate leading stock health score.
30
+
31
+ Args:
32
+ quotes: Dict of symbol -> quote data (from FMP batch quote)
33
+ historical: Dict of symbol -> list of daily OHLCV (most recent first, ~60 days)
34
+
35
+ Returns:
36
+ Dict with score (0-100), etf_details, signal
37
+ """
38
+ etf_scores = []
39
+ etf_details = {}
40
+
41
+ for symbol in LEADING_ETFS:
42
+ quote = quotes.get(symbol)
43
+ hist = historical.get(symbol, [])
44
+
45
+ if not quote:
46
+ continue
47
+
48
+ detail = _evaluate_etf(symbol, quote, hist)
49
+ etf_scores.append(detail["deterioration_score"])
50
+ etf_details[symbol] = detail
51
+
52
+ if not etf_scores:
53
+ return {
54
+ "score": 50,
55
+ "signal": "INSUFFICIENT DATA",
56
+ "etf_details": {},
57
+ "etfs_evaluated": 0,
58
+ "etfs_deteriorating": 0,
59
+ "data_available": False,
60
+ }
61
+
62
+ avg_deterioration = sum(etf_scores) / len(etf_scores)
63
+
64
+ # Count deteriorating ETFs (score >= 50)
65
+ deteriorating_count = sum(1 for s in etf_scores if s >= 50)
66
+ deteriorating_pct = deteriorating_count / len(etf_scores)
67
+
68
+ # Amplification: if 60%+ ETFs deteriorating, multiply by 1.3
69
+ if deteriorating_pct >= 0.60:
70
+ final_score = min(100, avg_deterioration * 1.3)
71
+ else:
72
+ final_score = avg_deterioration
73
+
74
+ final_score = round(min(100, max(0, final_score)))
75
+
76
+ # Signal
77
+ if final_score >= 70:
78
+ signal = "CRITICAL: Leadership broadly deteriorating"
79
+ elif final_score >= 50:
80
+ signal = "WARNING: Multiple leaders weakening"
81
+ elif final_score >= 30:
82
+ signal = "CAUTION: Some leaders showing strain"
83
+ else:
84
+ signal = "HEALTHY: Leadership intact"
85
+
86
+ return {
87
+ "score": final_score,
88
+ "signal": signal,
89
+ "avg_deterioration": round(avg_deterioration, 1),
90
+ "etfs_evaluated": len(etf_scores),
91
+ "etfs_deteriorating": deteriorating_count,
92
+ "deteriorating_pct": round(deteriorating_pct * 100, 1),
93
+ "amplified": deteriorating_pct >= 0.60,
94
+ "etf_details": etf_details,
95
+ "data_available": True,
96
+ }
97
+
98
+
99
+ def _evaluate_etf(symbol: str, quote: Dict, history: List[Dict]) -> Dict:
100
+ """Evaluate a single ETF's deterioration level"""
101
+ score = 0
102
+ flags = []
103
+
104
+ price = quote.get("price", 0)
105
+ year_high = quote.get("yearHigh", 0)
106
+ year_low = quote.get("yearLow", 0)
107
+
108
+ # 1. Distance from 52-week high (0-40 points)
109
+ if year_high > 0:
110
+ distance_pct = (price - year_high) / year_high * 100
111
+ if distance_pct <= -25:
112
+ score += 40
113
+ flags.append(f">{abs(distance_pct):.0f}% below 52wk high (bear territory)")
114
+ elif distance_pct <= -15:
115
+ score += 30
116
+ flags.append(f"{abs(distance_pct):.0f}% below 52wk high (correction)")
117
+ elif distance_pct <= -10:
118
+ score += 20
119
+ flags.append(f"{abs(distance_pct):.0f}% below 52wk high")
120
+ elif distance_pct <= -5:
121
+ score += 10
122
+ flags.append(f"{abs(distance_pct):.0f}% below 52wk high")
123
+ else:
124
+ distance_pct = 0
125
+
126
+ # 2. Position vs moving averages (0-40 points)
127
+ if history and len(history) >= 50:
128
+ closes = [d.get("close", d.get("adjClose", 0)) for d in history]
129
+
130
+ # 50DMA
131
+ sma50 = sum(closes[:50]) / 50
132
+ if price < sma50:
133
+ score += 20
134
+ flags.append(f"Below 50DMA (${sma50:.2f})")
135
+
136
+ # 200DMA (if enough data)
137
+ if len(closes) >= 200:
138
+ sma200 = sum(closes[:200]) / 200
139
+ if price < sma200:
140
+ score += 20
141
+ flags.append(f"Below 200DMA (${sma200:.2f})")
142
+ elif len(closes) >= 50:
143
+ # Estimate 200DMA position from available data
144
+ if price < sma50 * 0.95: # >5% below 50DMA suggests below 200DMA
145
+ score += 10
146
+ flags.append("Likely below 200DMA (estimated)")
147
+
148
+ elif history and len(history) >= 20:
149
+ closes = [d.get("close", d.get("adjClose", 0)) for d in history]
150
+ sma20 = sum(closes[:20]) / 20
151
+ if price < sma20:
152
+ score += 15
153
+ flags.append(f"Below 20DMA (${sma20:.2f})")
154
+
155
+ # 3. Lower highs pattern (0-20 points)
156
+ if history and len(history) >= 20:
157
+ lower_highs = _detect_lower_highs(history)
158
+ if lower_highs:
159
+ score += 20
160
+ flags.append("Lower highs pattern detected")
161
+
162
+ score = min(100, score)
163
+
164
+ return {
165
+ "deterioration_score": score,
166
+ "price": price,
167
+ "year_high": year_high,
168
+ "distance_from_high_pct": round(distance_pct, 1),
169
+ "flags": flags,
170
+ }
171
+
172
+
173
+ def _detect_lower_highs(history: List[Dict], lookback: int = 20) -> bool:
174
+ """
175
+ Detect lower highs pattern in recent price action.
176
+
177
+ Look for at least 2 consecutive lower swing highs in the last 20 days.
178
+ """
179
+ if len(history) < lookback:
180
+ return False
181
+
182
+ highs = [d.get("high", d.get("close", 0)) for d in history[:lookback]]
183
+
184
+ # Find local maxima (swing highs)
185
+ swing_highs = []
186
+ for i in range(1, len(highs) - 1):
187
+ if highs[i] > highs[i-1] and highs[i] > highs[i+1]:
188
+ swing_highs.append(highs[i])
189
+
190
+ # Need at least 2 swing highs to compare
191
+ if len(swing_highs) < 2:
192
+ return False
193
+
194
+ # Check if most recent swing highs are declining
195
+ # swing_highs[0] is earliest (since history is most-recent-first, reversed)
196
+ # Actually history[0] = most recent, so highs[0] = most recent high
197
+ # So swing_highs are in reverse chronological order
198
+ return swing_highs[0] < swing_highs[1]