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,467 @@
1
+ """Minimal E2E test for the Theme Detector pipeline.
2
+
3
+ Tests the full flow: raw industries -> rank -> classify -> score -> report
4
+ without network calls (all I/O is mocked).
5
+ """
6
+
7
+ import json
8
+
9
+ import pytest
10
+
11
+ # Import the pieces of the pipeline
12
+ from calculators.industry_ranker import rank_industries, get_top_bottom_industries
13
+ from calculators.theme_classifier import classify_themes, get_matched_industry_names
14
+ from calculators.theme_discoverer import discover_themes
15
+ from calculators.heat_calculator import (
16
+ momentum_strength_score,
17
+ volume_intensity_score,
18
+ uptrend_signal_score,
19
+ breadth_signal_score,
20
+ calculate_theme_heat,
21
+ )
22
+ from calculators.lifecycle_calculator import (
23
+ estimate_duration_score,
24
+ classify_stage,
25
+ calculate_lifecycle_maturity,
26
+ )
27
+ from scorer import score_theme, get_heat_label, calculate_confidence, determine_data_mode
28
+ from report_generator import generate_json_report, generate_markdown_report
29
+ from config_loader import load_themes_config
30
+
31
+
32
+ # Minimal themes config for E2E
33
+ E2E_THEMES_CONFIG = {
34
+ "cross_sector_min_matches": 2,
35
+ "vertical_min_industries": 3,
36
+ "cross_sector": [
37
+ {
38
+ "theme_name": "AI & Semiconductors",
39
+ "matching_keywords": [
40
+ "Semiconductors",
41
+ "Software - Application",
42
+ "Software - Infrastructure",
43
+ ],
44
+ "proxy_etfs": ["SMH", "SOXX"],
45
+ "static_stocks": ["NVDA", "AVGO", "AMD"],
46
+ },
47
+ ],
48
+ }
49
+
50
+
51
+ def _make_raw_industry(name, sector, perf_1w, perf_1m, perf_3m, perf_6m=None):
52
+ """Build a raw industry dict (simulates FINVIZ output, already in pct)."""
53
+ return {
54
+ "name": name,
55
+ "sector": sector,
56
+ "perf_1w": perf_1w,
57
+ "perf_1m": perf_1m,
58
+ "perf_3m": perf_3m,
59
+ "perf_6m": perf_6m or perf_3m * 1.2,
60
+ }
61
+
62
+
63
+ class TestThemeDetectorE2E:
64
+ """Full pipeline integration test (no network)."""
65
+
66
+ def test_full_pipeline_produces_valid_report(self):
67
+ """Raw industries -> ranked -> classified -> scored -> JSON+Markdown."""
68
+ # Step 1: Raw industry data (already in percent)
69
+ raw = [
70
+ _make_raw_industry("Semiconductors", "Technology", 5.0, 12.0, 25.0),
71
+ _make_raw_industry("Software - Application", "Technology", 4.0, 10.0, 20.0),
72
+ _make_raw_industry("Software - Infrastructure", "Technology", 3.5, 9.0, 18.0),
73
+ _make_raw_industry("Banks - Diversified", "Financial", 2.0, 5.0, 8.0),
74
+ _make_raw_industry("Oil & Gas E&P", "Energy", -1.0, -3.0, -5.0),
75
+ _make_raw_industry("Department Stores", "Consumer Cyclical", -4.0, -10.0, -15.0),
76
+ ]
77
+
78
+ # Step 2: Rank
79
+ ranked = rank_industries(raw)
80
+ assert len(ranked) == 6
81
+ assert ranked[0]["name"] == "Semiconductors" # highest momentum
82
+ assert ranked[0]["direction"] == "bullish"
83
+
84
+ industry_rankings = get_top_bottom_industries(ranked, n=3)
85
+ assert len(industry_rankings["top"]) == 3
86
+ assert len(industry_rankings["bottom"]) == 3
87
+
88
+ # Step 3: Classify
89
+ themes = classify_themes(ranked, E2E_THEMES_CONFIG, top_n=30)
90
+ assert len(themes) >= 1
91
+ ai_theme = [t for t in themes if t["theme_name"] == "AI & Semiconductors"]
92
+ assert len(ai_theme) == 1
93
+ assert ai_theme[0]["direction"] == "bullish"
94
+ assert len(ai_theme[0]["matching_industries"]) == 3
95
+
96
+ # Step 4: Score (simplified - just for AI theme)
97
+ theme = ai_theme[0]
98
+ theme_wr = sum(
99
+ ind.get("weighted_return", 0) for ind in theme["matching_industries"]
100
+ ) / len(theme["matching_industries"])
101
+
102
+ momentum = momentum_strength_score(theme_wr)
103
+ volume = None # no ETF data in E2E
104
+ uptrend = None # no uptrend data in E2E
105
+ breadth = breadth_signal_score(1.0) # all bullish -> ratio 1.0
106
+ heat = calculate_theme_heat(momentum, volume, uptrend, breadth)
107
+ assert 0 <= heat <= 100
108
+
109
+ duration = estimate_duration_score(12.0, 25.0, 30.0, None, False)
110
+ maturity = calculate_lifecycle_maturity(duration, 50, 50, 50, 30)
111
+ stage = classify_stage(maturity)
112
+ confidence = calculate_confidence(True, False, False, False)
113
+ data_mode = determine_data_mode(False, False)
114
+
115
+ score = score_theme(
116
+ round(heat, 2), round(maturity, 2), stage, "bullish", confidence, data_mode
117
+ )
118
+
119
+ scored_theme = {
120
+ "name": "AI & Semiconductors",
121
+ "direction": "bullish",
122
+ "heat": round(heat, 2),
123
+ "maturity": round(maturity, 2),
124
+ "stage": stage,
125
+ "confidence": confidence,
126
+ "heat_label": score["heat_label"],
127
+ "heat_breakdown": {"momentum_strength": round(momentum, 2)},
128
+ "maturity_breakdown": {"duration_estimate": round(duration, 2)},
129
+ "representative_stocks": ["NVDA", "AVGO", "AMD"],
130
+ "proxy_etfs": ["SMH", "SOXX"],
131
+ "industries": ["Semiconductors", "Software - Application",
132
+ "Software - Infrastructure"],
133
+ "sector_weights": {"Technology": 1.0},
134
+ }
135
+
136
+ # Step 5: Generate reports
137
+ metadata = {
138
+ "generated_at": "2026-02-16 09:00:00",
139
+ "data_mode": data_mode,
140
+ "data_sources": {},
141
+ }
142
+ json_report = generate_json_report(
143
+ [scored_theme], industry_rankings, {}, metadata
144
+ )
145
+
146
+ # Verify JSON structure
147
+ assert json_report["report_type"] == "theme_detector"
148
+ assert json_report["summary"]["total_themes"] == 1
149
+ assert json_report["summary"]["bullish_count"] == 1
150
+ assert json_report["summary"]["bearish_count"] == 0
151
+ assert json_report["summary"]["top_bullish"] == "AI & Semiconductors"
152
+
153
+ # Verify JSON is serializable
154
+ serialized = json.dumps(json_report, default=str)
155
+ assert len(serialized) > 100
156
+
157
+ # Step 6: Generate Markdown
158
+ md_report = generate_markdown_report(json_report, top_n_detail=3)
159
+ assert "# Theme Detector Report" in md_report
160
+ assert "AI & Semiconductors" in md_report
161
+ assert "BULL" in md_report
162
+ assert "NVDA" in md_report
163
+ assert "## 1. Theme Dashboard" in md_report
164
+ assert "## 7. Methodology & Data Quality" in md_report
165
+
166
+ # Verify perf values are reasonable (not multiplied by 100 again)
167
+ assert "500.0%" not in md_report
168
+ assert "1200.0%" not in md_report
169
+
170
+ def test_dynamic_stocks_with_mock_selector(self):
171
+ """Dynamic stock selection produces stock_details in scored_theme."""
172
+ from unittest.mock import MagicMock
173
+ from theme_detector import _get_representative_stocks
174
+
175
+ # Mock selector
176
+ mock_selector = MagicMock()
177
+ mock_selector.select_stocks.return_value = [
178
+ {
179
+ "symbol": "NEM",
180
+ "source": "finviz_public",
181
+ "market_cap": 50_000_000_000,
182
+ "matched_industries": ["Gold"],
183
+ "reasons": ["Public screener: Gold"],
184
+ "composite_score": 0.95,
185
+ },
186
+ {
187
+ "symbol": "GOLD",
188
+ "source": "finviz_public",
189
+ "market_cap": 30_000_000_000,
190
+ "matched_industries": ["Gold"],
191
+ "reasons": ["Public screener: Gold"],
192
+ "composite_score": 0.85,
193
+ },
194
+ ]
195
+
196
+ theme = {
197
+ "theme_name": "Gold & Precious Metals",
198
+ "direction": "bullish",
199
+ "matching_industries": [{"name": "Gold"}],
200
+ "proxy_etfs": ["GDX"],
201
+ "static_stocks": ["NEM", "GOLD", "AEM"],
202
+ }
203
+
204
+ tickers, details = _get_representative_stocks(theme, mock_selector, max_stocks=10)
205
+ assert tickers == ["NEM", "GOLD"]
206
+ assert len(details) == 2
207
+ assert details[0]["source"] == "finviz_public"
208
+ assert details[0]["composite_score"] == 0.95
209
+
210
+ def test_static_fallback_without_selector(self):
211
+ """Without selector, static_stocks are returned."""
212
+ from theme_detector import _get_representative_stocks
213
+
214
+ theme = {
215
+ "theme_name": "Test",
216
+ "direction": "bullish",
217
+ "matching_industries": [],
218
+ "proxy_etfs": [],
219
+ "static_stocks": ["A", "B", "C"],
220
+ }
221
+
222
+ tickers, details = _get_representative_stocks(theme, None, max_stocks=10)
223
+ assert tickers == ["A", "B", "C"]
224
+ assert all(d["source"] == "static" for d in details)
225
+
226
+ def test_config_loader_produces_same_themes(self):
227
+ """Config loaded from YAML produces same themes as before."""
228
+ raw = [
229
+ _make_raw_industry("Semiconductors", "Technology", 5.0, 12.0, 25.0),
230
+ _make_raw_industry("Software - Application", "Technology", 4.0, 10.0, 20.0),
231
+ _make_raw_industry("Software - Infrastructure", "Technology", 3.5, 9.0, 18.0),
232
+ _make_raw_industry("Banks - Diversified", "Financial", 2.0, 5.0, 8.0),
233
+ ]
234
+ ranked = rank_industries(raw)
235
+
236
+ # Load config from YAML
237
+ config, catalog = load_themes_config()
238
+ themes = classify_themes(ranked, config, top_n=30)
239
+
240
+ theme_names = [t["theme_name"] for t in themes]
241
+ assert "AI & Semiconductors" in theme_names
242
+ # Should have theme_origin set
243
+ ai = [t for t in themes if t["theme_name"] == "AI & Semiconductors"][0]
244
+ assert ai["theme_origin"] == "seed"
245
+ assert ai["name_confidence"] == "high"
246
+
247
+ def test_discover_path_finds_unmatched_clusters(self):
248
+ """Discover path finds themes from unmatched industries."""
249
+ raw = [
250
+ _make_raw_industry("Semiconductors", "Technology", 5.0, 12.0, 25.0),
251
+ _make_raw_industry("Software - Application", "Technology", 4.0, 10.0, 20.0),
252
+ # Unmatched industries that should cluster
253
+ _make_raw_industry("Gold", "Basic Materials", 8.0, 15.0, 30.0),
254
+ _make_raw_industry("Silver", "Basic Materials", 7.5, 14.0, 28.0),
255
+ _make_raw_industry("Other Precious Metals & Mining", "Basic Materials", 7.0, 13.0, 27.0),
256
+ # Filler for bottom
257
+ _make_raw_industry("Department Stores", "Consumer Cyclical", -4.0, -10.0, -15.0),
258
+ _make_raw_industry("Specialty Retail", "Consumer Cyclical", -3.5, -9.0, -14.0),
259
+ ]
260
+ ranked = rank_industries(raw)
261
+
262
+ # Use a minimal config that only matches Semiconductors + Software
263
+ mini_config = {
264
+ "cross_sector_min_matches": 2,
265
+ "vertical_min_industries": 3,
266
+ "cross_sector": [
267
+ {
268
+ "theme_name": "AI & Semiconductors",
269
+ "matching_keywords": [
270
+ "Semiconductors", "Software - Application",
271
+ ],
272
+ "proxy_etfs": ["SMH"],
273
+ "static_stocks": ["NVDA"],
274
+ },
275
+ ],
276
+ }
277
+ themes = classify_themes(ranked, mini_config, top_n=30)
278
+ matched = get_matched_industry_names(themes)
279
+ discovered = discover_themes(ranked, matched, themes, top_n=30)
280
+
281
+ # Gold/Silver/Precious Metals should form a discovered cluster
282
+ if discovered:
283
+ assert discovered[0]["theme_origin"] == "discovered"
284
+ assert discovered[0]["proxy_etfs"] == []
285
+ assert discovered[0]["name_confidence"] == "medium"
286
+
287
+ def test_max_themes_applied_after_discover(self):
288
+ """max_themes limits seed+discovered combined total."""
289
+ raw = [
290
+ _make_raw_industry("Semiconductors", "Technology", 5.0, 12.0, 25.0),
291
+ _make_raw_industry("Software - Application", "Technology", 4.0, 10.0, 20.0),
292
+ _make_raw_industry("Software - Infrastructure", "Technology", 3.5, 9.0, 18.0),
293
+ _make_raw_industry("Gold", "Basic Materials", 8.0, 15.0, 30.0),
294
+ _make_raw_industry("Silver", "Basic Materials", 7.5, 14.0, 28.0),
295
+ _make_raw_industry("Copper", "Basic Materials", 7.0, 13.0, 27.0),
296
+ _make_raw_industry("Department Stores", "Consumer Cyclical", -4.0, -10.0, -15.0),
297
+ ]
298
+ ranked = rank_industries(raw)
299
+ mini_config = {
300
+ "cross_sector_min_matches": 2,
301
+ "vertical_min_industries": 3,
302
+ "cross_sector": [
303
+ {
304
+ "theme_name": "AI",
305
+ "matching_keywords": ["Semiconductors", "Software - Application"],
306
+ "proxy_etfs": [],
307
+ "static_stocks": [],
308
+ },
309
+ ],
310
+ }
311
+ themes = classify_themes(ranked, mini_config, top_n=30)
312
+ matched = get_matched_industry_names(themes)
313
+ discovered = discover_themes(ranked, matched, themes, top_n=30)
314
+ themes.extend(discovered)
315
+
316
+ # Apply max_themes=2 with priority sort
317
+ def _priority(t):
318
+ inds = t.get("matching_industries", [])
319
+ n = len(inds)
320
+ avg_s = sum(abs(i.get("weighted_return", 0)) for i in inds) / max(n, 1)
321
+ return min(n / 10, 1) * 0.5 + min(avg_s / 30, 1) * 0.5
322
+
323
+ themes.sort(key=_priority, reverse=True)
324
+ themes = themes[:2]
325
+ assert len(themes) <= 2
326
+
327
+ def test_stock_details_in_scored_theme_and_report(self):
328
+ """stock_details flows through to scored_theme and into markdown."""
329
+ raw = [
330
+ _make_raw_industry("Semiconductors", "Technology", 5.0, 12.0, 25.0),
331
+ _make_raw_industry("Software - Application", "Technology", 4.0, 10.0, 20.0),
332
+ _make_raw_industry("Software - Infrastructure", "Technology", 3.5, 9.0, 18.0),
333
+ ]
334
+
335
+ ranked = rank_industries(raw)
336
+ themes = classify_themes(ranked, E2E_THEMES_CONFIG, top_n=30)
337
+ theme = themes[0]
338
+ theme_wr = sum(
339
+ ind.get("weighted_return", 0) for ind in theme["matching_industries"]
340
+ ) / len(theme["matching_industries"])
341
+
342
+ momentum = momentum_strength_score(theme_wr)
343
+ breadth = breadth_signal_score(1.0)
344
+ heat = calculate_theme_heat(momentum, None, None, breadth)
345
+ duration = estimate_duration_score(12.0, 25.0, 30.0, None, False)
346
+ maturity = calculate_lifecycle_maturity(duration, 50, 50, 50, 30)
347
+ stage = classify_stage(maturity)
348
+ confidence = calculate_confidence(True, False, False, False)
349
+ data_mode = determine_data_mode(False, False)
350
+ score = score_theme(
351
+ round(heat, 2), round(maturity, 2), stage, "bullish", confidence, data_mode
352
+ )
353
+
354
+ stock_details = [
355
+ {"symbol": "NVDA", "source": "finviz_public",
356
+ "market_cap": 2_800_000_000_000,
357
+ "matched_industries": ["Semiconductors"],
358
+ "reasons": ["Public screener"], "composite_score": 0.95},
359
+ {"symbol": "AVGO", "source": "finviz_public",
360
+ "market_cap": 500_000_000_000,
361
+ "matched_industries": ["Semiconductors"],
362
+ "reasons": ["Public screener"], "composite_score": 0.80},
363
+ ]
364
+
365
+ scored_theme = {
366
+ "name": "AI & Semiconductors",
367
+ "direction": "bullish",
368
+ "heat": round(heat, 2),
369
+ "maturity": round(maturity, 2),
370
+ "stage": stage,
371
+ "confidence": confidence,
372
+ "heat_label": score["heat_label"],
373
+ "heat_breakdown": {"momentum_strength": round(momentum, 2)},
374
+ "maturity_breakdown": {"duration_estimate": round(duration, 2)},
375
+ "representative_stocks": ["NVDA", "AVGO"],
376
+ "stock_details": stock_details,
377
+ "proxy_etfs": ["SMH", "SOXX"],
378
+ "industries": ["Semiconductors", "Software - Application",
379
+ "Software - Infrastructure"],
380
+ "sector_weights": {"Technology": 1.0},
381
+ }
382
+
383
+ metadata = {
384
+ "generated_at": "2026-02-16 09:00:00",
385
+ "data_mode": data_mode,
386
+ "data_sources": {},
387
+ }
388
+ json_report = generate_json_report(
389
+ [scored_theme], {"top": [], "bottom": []}, {}, metadata
390
+ )
391
+
392
+ # Verify stock_details in JSON
393
+ theme_in_report = json_report["themes"]["bullish"][0]
394
+ assert "stock_details" in theme_in_report
395
+ assert len(theme_in_report["stock_details"]) == 2
396
+
397
+ # Verify markdown has source labels
398
+ md_report = generate_markdown_report(json_report, top_n_detail=3)
399
+ assert "NVDA[Fp]" in md_report
400
+ assert "AVGO[Fp]" in md_report
401
+
402
+
403
+ class TestE2EFMPPath:
404
+ """E2E tests for FMP backend integration in theme_detector."""
405
+
406
+ def test_scanner_receives_fmp_key(self):
407
+ """ETFScanner constructed with fmp_api_key stores it."""
408
+ from etf_scanner import ETFScanner
409
+ scanner = ETFScanner(fmp_api_key="test_key_123")
410
+ assert scanner._fmp_api_key == "test_key_123"
411
+ scanner_no_key = ETFScanner()
412
+ assert scanner_no_key._fmp_api_key is None
413
+
414
+ def test_metadata_contains_scanner_stats(self):
415
+ """After batch_stock_metrics, backend_stats is populated."""
416
+ from unittest.mock import patch, MagicMock
417
+ from etf_scanner import ETFScanner
418
+
419
+ scanner = ETFScanner(fmp_api_key="test_key", rate_limit_sec=0)
420
+
421
+ with patch("etf_scanner._requests_lib") as mock_req:
422
+ quote_resp = MagicMock()
423
+ quote_resp.status_code = 200
424
+ quote_resp.json.return_value = [
425
+ {"symbol": "NVDA", "pe": 60, "price": 800,
426
+ "yearHigh": 950, "yearLow": 400},
427
+ ]
428
+ hist_resp = MagicMock()
429
+ hist_resp.status_code = 200
430
+ hist_resp.json.return_value = {
431
+ "historicalStockList": [
432
+ {"symbol": "NVDA", "historical": [
433
+ {"close": float(800 - i)} for i in range(20)
434
+ ]},
435
+ ]
436
+ }
437
+ mock_req.get.side_effect = [quote_resp, hist_resp]
438
+ scanner.batch_stock_metrics(["NVDA"])
439
+
440
+ stats = scanner.backend_stats()
441
+ assert "fmp_calls" in stats
442
+ assert "fmp_failures" in stats
443
+ assert "yf_calls" in stats
444
+ assert "yf_fallbacks" in stats
445
+ assert stats["fmp_calls"] > 0
446
+
447
+ def test_metadata_still_has_yfinance_stocks_key(self):
448
+ """yfinance_stocks key is preserved for backward compatibility.
449
+
450
+ Simulates the metadata construction in theme_detector.main().
451
+ """
452
+ from etf_scanner import ETFScanner
453
+
454
+ scanner = ETFScanner(fmp_api_key="test_key")
455
+ metadata = {"data_sources": {}}
456
+
457
+ # Simulate what theme_detector does
458
+ all_metrics = [{"symbol": "NVDA"}, {"symbol": "AVGO"}]
459
+ metadata["data_sources"]["yfinance_stocks"] = len(all_metrics)
460
+ scanner_stats = scanner.backend_stats()
461
+ metadata["data_sources"]["scanner_backend"] = scanner_stats
462
+
463
+ # Both keys exist
464
+ assert "yfinance_stocks" in metadata["data_sources"]
465
+ assert "scanner_backend" in metadata["data_sources"]
466
+ assert metadata["data_sources"]["yfinance_stocks"] == 2
467
+ assert isinstance(metadata["data_sources"]["scanner_backend"], dict)