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,131 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Theme Detector - FINVIZ Performance Client
4
+
5
+ Fetches sector and industry performance data from FINVIZ using the
6
+ finvizfinance library. No API key required (public data).
7
+
8
+ Data Source: finvizfinance.group.performance
9
+ """
10
+
11
+ import sys
12
+ from typing import Dict, List, Optional
13
+
14
+
15
+ try:
16
+ from finvizfinance.group import performance as fvperf
17
+ HAS_FINVIZFINANCE = True
18
+ except ImportError:
19
+ HAS_FINVIZFINANCE = False
20
+
21
+
22
+ # Mapping from finvizfinance DataFrame columns to standardized keys
23
+ COLUMN_MAP = {
24
+ "Name": "name",
25
+ "Perf Week": "perf_1w",
26
+ "Perf Month": "perf_1m",
27
+ "Perf Quart": "perf_3m",
28
+ "Perf Half": "perf_6m",
29
+ "Perf Year": "perf_1y",
30
+ "Perf YTD": "perf_ytd",
31
+ }
32
+
33
+
34
+ def _parse_perf_value(val) -> Optional[float]:
35
+ """Parse a performance value to float.
36
+
37
+ The finvizfinance library may return:
38
+ - float already (e.g., 0.12 for 12%)
39
+ - string like "0.12%" or "12.34%"
40
+ - None or NaN
41
+ """
42
+ if val is None:
43
+ return None
44
+ try:
45
+ import math
46
+ if isinstance(val, float) and math.isnan(val):
47
+ return None
48
+ except (TypeError, ValueError):
49
+ pass
50
+ if isinstance(val, (int, float)):
51
+ return float(val)
52
+ if isinstance(val, str):
53
+ cleaned = val.strip().rstrip("%")
54
+ if not cleaned:
55
+ return None
56
+ try:
57
+ num = float(cleaned)
58
+ # If original had % sign but value looks like it's already in
59
+ # decimal form (e.g., "0.12%"), it's ambiguous.
60
+ # finvizfinance typically returns decimal (0.12 = 12%).
61
+ # If string had % and value > 1, it's likely a percentage.
62
+ if "%" in val and abs(num) > 1:
63
+ return num / 100.0
64
+ return num
65
+ except ValueError:
66
+ return None
67
+ return None
68
+
69
+
70
+ def _dataframe_to_dicts(df) -> List[Dict]:
71
+ """Convert a finvizfinance DataFrame to standardized list of dicts."""
72
+ rows = []
73
+ for _, row in df.iterrows():
74
+ entry = {}
75
+ for src_col, dst_key in COLUMN_MAP.items():
76
+ if src_col in row.index:
77
+ if dst_key == "name":
78
+ entry[dst_key] = str(row[src_col]).strip()
79
+ else:
80
+ entry[dst_key] = _parse_perf_value(row[src_col])
81
+ else:
82
+ if dst_key != "name":
83
+ entry[dst_key] = None
84
+ if entry.get("name"):
85
+ rows.append(entry)
86
+ return rows
87
+
88
+
89
+ def get_sector_performance() -> List[Dict]:
90
+ """Fetch sector-level performance data from FINVIZ.
91
+
92
+ Returns:
93
+ List of dicts with keys: name, perf_1w, perf_1m, perf_3m,
94
+ perf_6m, perf_1y, perf_ytd. Values are floats in decimal
95
+ form (e.g., 0.05 = 5%).
96
+ """
97
+ if not HAS_FINVIZFINANCE:
98
+ print("WARNING: finvizfinance not installed. "
99
+ "Install with: pip install finvizfinance", file=sys.stderr)
100
+ return []
101
+
102
+ try:
103
+ perf = fvperf.Performance()
104
+ df = perf.screener_view(group="Sector")
105
+ return _dataframe_to_dicts(df)
106
+ except Exception as e:
107
+ print(f"WARNING: Failed to fetch sector performance: {e}",
108
+ file=sys.stderr)
109
+ return []
110
+
111
+
112
+ def get_industry_performance() -> List[Dict]:
113
+ """Fetch industry-level performance data from FINVIZ.
114
+
115
+ Returns:
116
+ List of dicts with same structure as get_sector_performance().
117
+ Typically 140+ industries.
118
+ """
119
+ if not HAS_FINVIZFINANCE:
120
+ print("WARNING: finvizfinance not installed. "
121
+ "Install with: pip install finvizfinance", file=sys.stderr)
122
+ return []
123
+
124
+ try:
125
+ perf = fvperf.Performance()
126
+ df = perf.screener_view(group="Industry")
127
+ return _dataframe_to_dicts(df)
128
+ except Exception as e:
129
+ print(f"WARNING: Failed to fetch industry performance: {e}",
130
+ file=sys.stderr)
131
+ return []
@@ -0,0 +1,490 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Theme Detector - Report Generator
4
+
5
+ Generates JSON and Markdown reports for detected market themes.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ from datetime import datetime
11
+ from typing import Dict, List, Optional
12
+
13
+
14
+ def generate_json_report(themes: List[Dict],
15
+ industry_rankings: Dict,
16
+ sector_uptrend: Dict,
17
+ metadata: Dict) -> Dict:
18
+ """Create the full JSON output structure.
19
+
20
+ Args:
21
+ themes: List of scored theme dicts from the classifier/scorer.
22
+ Each theme has: name, direction, heat, maturity, stage,
23
+ confidence, heat_breakdown, maturity_breakdown,
24
+ representative_stocks, proxy_etfs, etc.
25
+ industry_rankings: Dict with "top" and "bottom" lists of industries.
26
+ sector_uptrend: Dict mapping sector name to uptrend data
27
+ (ratio, ma_10, slope, trend, latest_date).
28
+ metadata: Dict with run metadata (generated_at, data_sources, etc.)
29
+
30
+ Returns:
31
+ Complete JSON-serializable report dict.
32
+ """
33
+ bullish = [t for t in themes if t.get("direction") == "bullish"]
34
+ bearish = [t for t in themes if t.get("direction") == "bearish"]
35
+
36
+ # Sort by heat descending within each group
37
+ bullish.sort(key=lambda t: t.get("heat", 0), reverse=True)
38
+ bearish.sort(key=lambda t: t.get("heat", 0), reverse=True)
39
+
40
+ # Data quality flags
41
+ data_quality = _assess_data_quality(themes, industry_rankings,
42
+ sector_uptrend, metadata)
43
+
44
+ return {
45
+ "report_type": "theme_detector",
46
+ "generated_at": metadata.get("generated_at",
47
+ datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
48
+ "metadata": metadata,
49
+ "summary": {
50
+ "total_themes": len(themes),
51
+ "bullish_count": len(bullish),
52
+ "bearish_count": len(bearish),
53
+ "top_bullish": bullish[0]["name"] if bullish else None,
54
+ "top_bearish": bearish[0]["name"] if bearish else None,
55
+ },
56
+ "themes": {
57
+ "all": themes,
58
+ "bullish": bullish,
59
+ "bearish": bearish,
60
+ },
61
+ "industry_rankings": industry_rankings,
62
+ "sector_uptrend": sector_uptrend,
63
+ "data_quality": data_quality,
64
+ }
65
+
66
+
67
+ def generate_markdown_report(json_data: Dict,
68
+ top_n_detail: int = 3) -> str:
69
+ """Generate a formatted Markdown report from JSON data.
70
+
71
+ Args:
72
+ json_data: Full JSON report dict from generate_json_report().
73
+ top_n_detail: Number of top themes to show in detail sections
74
+ (default 3, corresponds to --top CLI arg).
75
+
76
+ Sections:
77
+ 1. Theme Dashboard (all themes table)
78
+ 2. Bullish Themes Detail (top N)
79
+ 3. Bearish Themes Detail (top N)
80
+ 4. All Themes Summary Table
81
+ 5. Industry Rankings (top/bottom 15)
82
+ 6. Sector Uptrend Ratios (3-point display)
83
+ 7. Methodology Notes + Data Quality Flags
84
+ """
85
+ lines = []
86
+ themes_data = json_data.get("themes", {})
87
+ all_themes = themes_data.get("all", [])
88
+ bullish = themes_data.get("bullish", [])
89
+ bearish = themes_data.get("bearish", [])
90
+ summary = json_data.get("summary", {})
91
+
92
+ # Header
93
+ lines.append("# Theme Detector Report")
94
+ lines.append("")
95
+ lines.append(f"**Generated:** {json_data.get('generated_at', 'N/A')}")
96
+ lines.append(f"**Themes Detected:** {summary.get('total_themes', 0)} "
97
+ f"({summary.get('bullish_count', 0)} bullish, "
98
+ f"{summary.get('bearish_count', 0)} bearish)")
99
+ lines.append("")
100
+
101
+ # Section 1: Theme Dashboard
102
+ lines.append("---")
103
+ lines.append("")
104
+ lines.append("## 1. Theme Dashboard")
105
+ lines.append("")
106
+
107
+ if not all_themes:
108
+ lines.append("**WARNING:** No themes detected. Check data sources.")
109
+ lines.append("")
110
+ else:
111
+ lines.append("| Theme | Origin | Direction | Heat | Maturity | Stage | Confidence |")
112
+ lines.append("|-------|--------|-----------|------|----------|-------|------------|")
113
+ for t in all_themes:
114
+ heat_bar = _heat_bar(t.get("heat", 0))
115
+ origin = _origin_label(t.get("theme_origin", "seed"))
116
+ lines.append(
117
+ f"| {t.get('name', 'N/A')} "
118
+ f"| {origin} "
119
+ f"| {_direction_label(t.get('direction'))} "
120
+ f"| {heat_bar} {t.get('heat', 0):.1f} "
121
+ f"| {t.get('maturity', 0):.1f} "
122
+ f"| {t.get('stage', 'N/A')} "
123
+ f"| {t.get('confidence', 'N/A')} |"
124
+ )
125
+ lines.append("")
126
+
127
+ # Section 2: Bullish Themes Detail
128
+ lines.append("---")
129
+ lines.append("")
130
+ lines.append(f"## 2. Bullish Themes (Top {top_n_detail})")
131
+ lines.append("")
132
+ _add_theme_details(lines, bullish[:top_n_detail])
133
+
134
+ # Section 3: Bearish Themes Detail
135
+ lines.append("---")
136
+ lines.append("")
137
+ lines.append(f"## 3. Bearish Themes (Top {top_n_detail})")
138
+ lines.append("")
139
+ _add_theme_details(lines, bearish[:top_n_detail])
140
+
141
+ # Section 4: All Themes Summary Table
142
+ lines.append("---")
143
+ lines.append("")
144
+ lines.append("## 4. All Themes Summary")
145
+ lines.append("")
146
+ if all_themes:
147
+ lines.append("| # | Theme | Dir | Heat | Maturity | Stage | Industries |")
148
+ lines.append("|---|-------|-----|------|----------|-------|------------|")
149
+ for i, t in enumerate(all_themes, 1):
150
+ industries = t.get("industries", [])
151
+ ind_str = ", ".join(industries[:3])
152
+ if len(industries) > 3:
153
+ ind_str += f" (+{len(industries) - 3})"
154
+ lines.append(
155
+ f"| {i} "
156
+ f"| {t.get('name', 'N/A')} "
157
+ f"| {_direction_arrow(t.get('direction'))} "
158
+ f"| {t.get('heat', 0):.1f} "
159
+ f"| {t.get('maturity', 0):.1f} "
160
+ f"| {t.get('stage', 'N/A')} "
161
+ f"| {ind_str} |"
162
+ )
163
+ lines.append("")
164
+ else:
165
+ lines.append("No themes detected.")
166
+ lines.append("")
167
+
168
+ # Section 5: Industry Rankings
169
+ lines.append("---")
170
+ lines.append("")
171
+ lines.append("## 5. Industry Rankings")
172
+ lines.append("")
173
+ rankings = json_data.get("industry_rankings", {})
174
+ top = rankings.get("top", [])
175
+ bottom = rankings.get("bottom", [])
176
+
177
+ if top:
178
+ lines.append("### Top 15 Industries")
179
+ lines.append("")
180
+ lines.append("| # | Industry | Perf 1W | Perf 1M | Perf 3M | Score |")
181
+ lines.append("|---|----------|---------|---------|---------|-------|")
182
+ for i, ind in enumerate(top[:15], 1):
183
+ lines.append(
184
+ f"| {i} "
185
+ f"| {ind.get('name', 'N/A')} "
186
+ f"| {_fmt_pct(ind.get('perf_1w'))} "
187
+ f"| {_fmt_pct(ind.get('perf_1m'))} "
188
+ f"| {_fmt_pct(ind.get('perf_3m'))} "
189
+ f"| {ind.get('momentum_score', 0):.2f} |"
190
+ )
191
+ lines.append("")
192
+
193
+ if bottom:
194
+ lines.append("### Bottom 15 Industries")
195
+ lines.append("")
196
+ lines.append("| # | Industry | Perf 1W | Perf 1M | Perf 3M | Score |")
197
+ lines.append("|---|----------|---------|---------|---------|-------|")
198
+ for i, ind in enumerate(bottom[:15], 1):
199
+ lines.append(
200
+ f"| {i} "
201
+ f"| {ind.get('name', 'N/A')} "
202
+ f"| {_fmt_pct(ind.get('perf_1w'))} "
203
+ f"| {_fmt_pct(ind.get('perf_1m'))} "
204
+ f"| {_fmt_pct(ind.get('perf_3m'))} "
205
+ f"| {ind.get('momentum_score', 0):.2f} |"
206
+ )
207
+ lines.append("")
208
+
209
+ if not top and not bottom:
210
+ lines.append("Industry ranking data unavailable.")
211
+ lines.append("")
212
+
213
+ # Section 6: Sector Uptrend Ratios
214
+ lines.append("---")
215
+ lines.append("")
216
+ lines.append("## 6. Sector Uptrend Ratios")
217
+ lines.append("")
218
+ uptrend = json_data.get("sector_uptrend", {})
219
+ if uptrend:
220
+ lines.append("| Sector | Ratio | 10MA | Slope | Trend | Date |")
221
+ lines.append("|--------|-------|------|-------|-------|------|")
222
+ for sector_name, data in sorted(uptrend.items()):
223
+ if not isinstance(data, dict):
224
+ continue
225
+ ratio_pct = f"{data['ratio'] * 100:.1f}%" if data.get("ratio") is not None else "N/A"
226
+ ma_pct = f"{data['ma_10'] * 100:.1f}%" if data.get("ma_10") is not None else "N/A"
227
+ slope_str = f"{data['slope']:+.4f}" if data.get("slope") is not None else "N/A"
228
+ lines.append(
229
+ f"| {sector_name} "
230
+ f"| {ratio_pct} "
231
+ f"| {ma_pct} "
232
+ f"| {slope_str} "
233
+ f"| {data.get('trend', 'N/A')} "
234
+ f"| {data.get('latest_date', 'N/A')} |"
235
+ )
236
+ lines.append("")
237
+ else:
238
+ lines.append("Sector uptrend data unavailable.")
239
+ lines.append("")
240
+
241
+ # Section 7: Methodology + Data Quality
242
+ lines.append("---")
243
+ lines.append("")
244
+ lines.append("## 7. Methodology & Data Quality")
245
+ lines.append("")
246
+ lines.append("### Methodology")
247
+ lines.append("")
248
+ lines.append("The Theme Detector identifies market themes by:")
249
+ lines.append("")
250
+ lines.append("1. **Industry Ranking:** FINVIZ performance data ranked by "
251
+ "multi-timeframe composite score (1W, 1M, 3M weighted)")
252
+ lines.append("2. **Theme Classification:** Industries grouped into "
253
+ "thematic clusters (AI/Semiconductors, Energy Transition, etc.)")
254
+ lines.append("3. **Heat Scoring:** Theme strength measured by performance "
255
+ "momentum, volume confirmation, and breadth")
256
+ lines.append("4. **Lifecycle Stage:** Theme maturity assessed via "
257
+ "early/growth/mature/decline classification")
258
+ lines.append("5. **Uptrend Overlay:** Monty's sector uptrend ratios "
259
+ "provide breadth context")
260
+ lines.append("")
261
+
262
+ data_quality = json_data.get("data_quality", {})
263
+ flags = data_quality.get("flags", [])
264
+ if flags:
265
+ lines.append("### Data Quality Flags")
266
+ lines.append("")
267
+ for flag in flags:
268
+ lines.append(f"- {flag}")
269
+ lines.append("")
270
+ else:
271
+ lines.append("### Data Quality")
272
+ lines.append("")
273
+ lines.append("All data sources returned valid results.")
274
+ lines.append("")
275
+
276
+ # Disclaimer
277
+ lines.append("---")
278
+ lines.append("")
279
+ lines.append("**Disclaimer:** This analysis is for educational and "
280
+ "informational purposes only. Not investment advice. "
281
+ "Past patterns may not predict future outcomes.")
282
+ lines.append("")
283
+
284
+ return "\n".join(lines)
285
+
286
+
287
+ def save_reports(json_data: Dict, markdown: str,
288
+ output_dir: str) -> Dict[str, str]:
289
+ """Save JSON and Markdown report files.
290
+
291
+ Args:
292
+ json_data: Full JSON report dict
293
+ markdown: Formatted markdown string
294
+ output_dir: Directory to save reports
295
+
296
+ Returns:
297
+ Dict with "json" and "markdown" keys containing file paths.
298
+ """
299
+ os.makedirs(output_dir, exist_ok=True)
300
+
301
+ timestamp = datetime.now().strftime("%Y-%m-%d_%H%M%S")
302
+ json_path = os.path.join(output_dir, f"theme_detector_{timestamp}.json")
303
+ md_path = os.path.join(output_dir, f"theme_detector_{timestamp}.md")
304
+
305
+ with open(json_path, "w") as f:
306
+ json.dump(json_data, f, indent=2, default=str)
307
+
308
+ with open(md_path, "w") as f:
309
+ f.write(markdown)
310
+
311
+ return {"json": json_path, "markdown": md_path}
312
+
313
+
314
+ # --- Private helpers ---
315
+
316
+ def _assess_data_quality(themes: List[Dict],
317
+ industry_rankings: Dict,
318
+ sector_uptrend: Dict,
319
+ metadata: Dict) -> Dict:
320
+ """Assess data quality and return flags."""
321
+ flags = []
322
+
323
+ if not themes:
324
+ flags.append("No themes detected - check FINVIZ connectivity")
325
+
326
+ top = industry_rankings.get("top", [])
327
+ bottom = industry_rankings.get("bottom", [])
328
+ if not top and not bottom:
329
+ flags.append("Industry rankings unavailable - FINVIZ data may be missing")
330
+
331
+ if not sector_uptrend:
332
+ flags.append("Sector uptrend data unavailable - GitHub CSV fetch may have failed")
333
+ else:
334
+ # Check for stale data
335
+ for sector, data in sector_uptrend.items():
336
+ if isinstance(data, dict) and data.get("latest_date"):
337
+ try:
338
+ latest = datetime.strptime(data["latest_date"], "%Y-%m-%d")
339
+ age = (datetime.now() - latest).days
340
+ if age > 3:
341
+ flags.append(
342
+ f"Uptrend data for {sector} is {age} days old"
343
+ )
344
+ break # One warning is enough
345
+ except (ValueError, TypeError):
346
+ pass
347
+
348
+ sources = metadata.get("data_sources", {})
349
+ if sources.get("finviz_error"):
350
+ flags.append(f"FINVIZ error: {sources['finviz_error']}")
351
+ if sources.get("uptrend_error"):
352
+ flags.append(f"Uptrend error: {sources['uptrend_error']}")
353
+
354
+ return {
355
+ "status": "warning" if flags else "ok",
356
+ "flags": flags,
357
+ }
358
+
359
+
360
+ def _heat_bar(heat: float) -> str:
361
+ """Create a text bar for heat visualization (0-100 scale)."""
362
+ if heat >= 80.0:
363
+ return "████"
364
+ elif heat >= 60.0:
365
+ return "███░"
366
+ elif heat >= 40.0:
367
+ return "██░░"
368
+ elif heat >= 20.0:
369
+ return "█░░░"
370
+ else:
371
+ return "░░░░"
372
+
373
+
374
+ _ORIGIN_LABELS = {"seed": "Seed", "vertical": "Vertical", "discovered": "*NEW*"}
375
+
376
+
377
+ def _origin_label(origin: Optional[str]) -> str:
378
+ """Format theme origin for display."""
379
+ return _ORIGIN_LABELS.get(origin or "seed", "Seed")
380
+
381
+
382
+ def _direction_label(direction: Optional[str]) -> str:
383
+ """Format direction for display."""
384
+ if direction == "bullish":
385
+ return "BULL"
386
+ elif direction == "bearish":
387
+ return "BEAR"
388
+ return "N/A"
389
+
390
+
391
+ def _direction_arrow(direction: Optional[str]) -> str:
392
+ """Format direction as arrow."""
393
+ if direction == "bullish":
394
+ return "^"
395
+ elif direction == "bearish":
396
+ return "v"
397
+ return "-"
398
+
399
+
400
+ def _fmt_pct(value: Optional[float]) -> str:
401
+ """Format a percent value as percentage string (no conversion)."""
402
+ if value is None:
403
+ return "N/A"
404
+ return f"{value:+.1f}%"
405
+
406
+
407
+ _SOURCE_LABELS = {
408
+ "finviz_elite": "Fe",
409
+ "finviz_public": "Fp",
410
+ "etf_holdings": "E",
411
+ "static": "S",
412
+ }
413
+
414
+
415
+ def _format_stock_list(theme_data: Dict) -> str:
416
+ """Format stock list with optional source labels.
417
+
418
+ When stock_details is present and contains non-static sources,
419
+ appends [Fe]/[Fp]/[E]/[S] labels. Otherwise plain comma-joined tickers.
420
+ """
421
+ details = theme_data.get("stock_details", [])
422
+ tickers = theme_data.get("representative_stocks", [])
423
+
424
+ if not details:
425
+ return ", ".join(tickers) if tickers else "N/A"
426
+
427
+ parts = []
428
+ for d in details:
429
+ label = _SOURCE_LABELS.get(d.get("source", ""), "?")
430
+ parts.append(f"{d['symbol']}[{label}]")
431
+ return ", ".join(parts) if parts else "N/A"
432
+
433
+
434
+ def _add_theme_details(lines: List[str], themes: List[Dict]) -> None:
435
+ """Add detailed theme sections to the report lines."""
436
+ if not themes:
437
+ lines.append("No themes in this category.")
438
+ lines.append("")
439
+ return
440
+
441
+ for t in themes:
442
+ lines.append(f"### {t.get('name', 'Unknown Theme')}")
443
+ lines.append("")
444
+ # Show origin line for discovered themes (seed/vertical are self-evident)
445
+ t_origin = t.get("theme_origin", "seed")
446
+ if t_origin == "discovered":
447
+ name_conf = t.get("name_confidence", "medium")
448
+ lines.append(f"- **Origin:** Discovered (name confidence: {name_conf})")
449
+ lines.append(f"- **Direction:** {_direction_label(t.get('direction'))}")
450
+ lines.append(f"- **Heat:** {t.get('heat', 0):.1f}/100 ({t.get('heat_label', 'N/A')})")
451
+ lines.append(f"- **Maturity:** {t.get('maturity', 0):.1f}/100")
452
+ lines.append(f"- **Stage:** {t.get('stage', 'N/A')}")
453
+ lines.append(f"- **Confidence:** {t.get('confidence', 'N/A')}")
454
+ lines.append("")
455
+
456
+ # Heat breakdown
457
+ heat_bd = t.get("heat_breakdown", {})
458
+ if heat_bd:
459
+ lines.append("**Heat Breakdown:**")
460
+ lines.append("")
461
+ for key, val in heat_bd.items():
462
+ label = key.replace("_", " ").title()
463
+ if isinstance(val, float):
464
+ lines.append(f"- {label}: {val:.2f}")
465
+ else:
466
+ lines.append(f"- {label}: {val}")
467
+ lines.append("")
468
+
469
+ # Maturity breakdown
470
+ mat_bd = t.get("maturity_breakdown", {})
471
+ if mat_bd:
472
+ lines.append("**Maturity Breakdown:**")
473
+ lines.append("")
474
+ for key, val in mat_bd.items():
475
+ label = key.replace("_", " ").title()
476
+ if isinstance(val, float):
477
+ lines.append(f"- {label}: {val:.2f}")
478
+ else:
479
+ lines.append(f"- {label}: {val}")
480
+ lines.append("")
481
+
482
+ # Representative stocks
483
+ lines.append(f"**Representative Stocks:** {_format_stock_list(t)}")
484
+ lines.append("")
485
+
486
+ # Proxy ETFs
487
+ etfs = t.get("proxy_etfs", [])
488
+ if etfs:
489
+ lines.append(f"**Proxy ETFs:** {', '.join(etfs)}")
490
+ lines.append("")