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,204 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Component 2: Sector Participation - Weight: 25%
4
+
5
+ Evaluates how many sectors are participating in the uptrend and the
6
+ uniformity of participation (spread between strongest and weakest).
7
+
8
+ Data Source: Sector Summary CSV
9
+
10
+ Sub-scores:
11
+ - Uptrend Count (60%): How many of 11 sectors are in uptrend
12
+ - Spread (40%): Max-min ratio spread (uniform = healthy, selective = weak)
13
+ """
14
+
15
+ import sys
16
+ from typing import Dict, List, Optional
17
+
18
+ from data_fetcher import build_summary_from_timeseries
19
+
20
+
21
+ # Monty's official dashboard thresholds
22
+ OVERBOUGHT_THRESHOLD = 0.37 # Upper threshold
23
+ OVERSOLD_THRESHOLD = 0.097 # Lower threshold
24
+
25
+
26
+ def calculate_sector_participation(sector_summary: List[Dict],
27
+ sector_timeseries: Dict[str, List[Dict]]) -> Dict:
28
+ """
29
+ Calculate sector participation score.
30
+
31
+ Args:
32
+ sector_summary: List of sector summary rows
33
+ sector_timeseries: Dict mapping sector -> timeseries rows; used as
34
+ fallback if sector_summary is unavailable
35
+
36
+ Returns:
37
+ Dict with score (0-100), signal, and detail fields
38
+ """
39
+ if not sector_summary:
40
+ if sector_timeseries:
41
+ sector_summary = build_summary_from_timeseries(sector_timeseries)
42
+ print(" (fallback: built sector summary from timeseries data)",
43
+ file=sys.stderr)
44
+ else:
45
+ return {
46
+ "score": 50,
47
+ "signal": "NO DATA: Sector summary unavailable (neutral default)",
48
+ "data_available": False,
49
+ "uptrend_count": None,
50
+ "total_sectors": None,
51
+ "spread": None,
52
+ "sector_details": [],
53
+ }
54
+
55
+ total_sectors = len(sector_summary)
56
+ if total_sectors == 0:
57
+ return {
58
+ "score": 50,
59
+ "signal": "NO DATA: No sectors in summary",
60
+ "data_available": False,
61
+ "uptrend_count": 0,
62
+ "total_sectors": 0,
63
+ "spread": None,
64
+ "sector_details": [],
65
+ }
66
+
67
+ # Count sectors in uptrend
68
+ uptrend_count = sum(
69
+ 1 for s in sector_summary
70
+ if s.get("Trend", "").lower() == "up"
71
+ )
72
+
73
+ # Get ratios for spread calculation
74
+ ratios = [s["Ratio"] for s in sector_summary
75
+ if s.get("Ratio") is not None]
76
+
77
+ if ratios:
78
+ max_ratio = max(ratios)
79
+ min_ratio = min(ratios)
80
+ spread = max_ratio - min_ratio
81
+ else:
82
+ max_ratio = None
83
+ min_ratio = None
84
+ spread = None
85
+
86
+ # Sub-score 1: Uptrend Count (60%)
87
+ count_score = _score_uptrend_count(uptrend_count, total_sectors)
88
+
89
+ # Sub-score 2: Spread (40%)
90
+ spread_score = _score_spread(spread) if spread is not None else 50
91
+
92
+ # Composite
93
+ raw_score = count_score * 0.60 + spread_score * 0.40
94
+ score = round(min(100, max(0, raw_score)))
95
+
96
+ # Identify overbought/oversold sectors
97
+ overbought = [s for s in sector_summary
98
+ if s.get("Ratio") is not None and s["Ratio"] >= OVERBOUGHT_THRESHOLD]
99
+ oversold = [s for s in sector_summary
100
+ if s.get("Ratio") is not None and s["Ratio"] < OVERSOLD_THRESHOLD]
101
+
102
+ signal = _build_signal(score, uptrend_count, total_sectors, spread)
103
+
104
+ # Build sector details sorted by ratio descending
105
+ sector_details = []
106
+ for s in sorted(sector_summary, key=lambda x: x.get("Ratio") or 0, reverse=True):
107
+ sector_details.append({
108
+ "sector": s.get("Sector", "Unknown"),
109
+ "ratio": s.get("Ratio"),
110
+ "ratio_pct": round(s["Ratio"] * 100, 1) if s.get("Ratio") is not None else None,
111
+ "ma_10": s.get("10MA"),
112
+ "trend": s.get("Trend", ""),
113
+ "slope": s.get("Slope"),
114
+ "status": s.get("Status", ""),
115
+ })
116
+
117
+ return {
118
+ "score": score,
119
+ "signal": signal,
120
+ "data_available": True,
121
+ "uptrend_count": uptrend_count,
122
+ "total_sectors": total_sectors,
123
+ "count_score": round(count_score),
124
+ "spread": round(spread, 4) if spread is not None else None,
125
+ "spread_pct": round(spread * 100, 1) if spread is not None else None,
126
+ "spread_score": round(spread_score),
127
+ "max_ratio": max_ratio,
128
+ "min_ratio": min_ratio,
129
+ "overbought_count": len(overbought),
130
+ "overbought_sectors": [s.get("Sector", "") for s in overbought],
131
+ "oversold_count": len(oversold),
132
+ "oversold_sectors": [s.get("Sector", "") for s in oversold],
133
+ "sector_details": sector_details,
134
+ }
135
+
136
+
137
+ def _score_uptrend_count(uptrend_count: int, total_sectors: int) -> float:
138
+ """Score based on number of sectors in uptrend.
139
+
140
+ 10-11 sectors -> 100
141
+ 8-9 sectors -> 80
142
+ 6-7 sectors -> 60
143
+ 4-5 sectors -> 40
144
+ 2-3 sectors -> 20
145
+ 0-1 sectors -> 0
146
+ """
147
+ if total_sectors == 0:
148
+ return 50
149
+
150
+ if uptrend_count >= 10:
151
+ return 100
152
+ elif uptrend_count >= 8:
153
+ return 80
154
+ elif uptrend_count >= 6:
155
+ return 60
156
+ elif uptrend_count >= 4:
157
+ return 40
158
+ elif uptrend_count >= 2:
159
+ return 20
160
+ else:
161
+ return 0
162
+
163
+
164
+ def _score_spread(spread: float) -> float:
165
+ """Score based on max-min ratio spread (0-1 scale).
166
+
167
+ < 0.15 -> 100 (uniform participation)
168
+ 0.15-0.25 -> 80 (healthy spread)
169
+ 0.25-0.35 -> 60 (moderate dispersion)
170
+ 0.35-0.45 -> 30 (wide divergence)
171
+ > 0.45 -> 0 (extremely selective)
172
+ """
173
+ if spread < 0.15:
174
+ return 100
175
+ elif spread < 0.25:
176
+ return 100 - (spread - 0.15) / 0.10 * 20
177
+ elif spread < 0.35:
178
+ return 80 - (spread - 0.25) / 0.10 * 20
179
+ elif spread < 0.45:
180
+ return 60 - (spread - 0.35) / 0.10 * 30
181
+ else:
182
+ return max(0, 30 - (spread - 0.45) / 0.10 * 30)
183
+
184
+
185
+ def _build_signal(score: int, uptrend_count: int,
186
+ total_sectors: int, spread: Optional[float]) -> str:
187
+ """Build human-readable signal."""
188
+ spread_pct = f", spread {round(spread * 100, 1)}%" if spread is not None else ""
189
+
190
+ if score >= 80:
191
+ return (f"BROAD PARTICIPATION: {uptrend_count}/{total_sectors} "
192
+ f"sectors uptrending{spread_pct}")
193
+ elif score >= 60:
194
+ return (f"HEALTHY: {uptrend_count}/{total_sectors} "
195
+ f"sectors uptrending{spread_pct}")
196
+ elif score >= 40:
197
+ return (f"MODERATE: {uptrend_count}/{total_sectors} "
198
+ f"sectors uptrending{spread_pct}")
199
+ elif score >= 20:
200
+ return (f"NARROW: {uptrend_count}/{total_sectors} "
201
+ f"sectors uptrending{spread_pct}")
202
+ else:
203
+ return (f"VERY NARROW: {uptrend_count}/{total_sectors} "
204
+ f"sectors uptrending{spread_pct}")
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Component 3: Sector Rotation - Weight: 15%
4
+
5
+ Evaluates whether cyclical (risk-on) or defensive (risk-off) sectors are
6
+ leading, plus commodity sector dynamics for late-cycle detection.
7
+
8
+ Data Source: Sector Summary CSV
9
+
10
+ Sector Groups:
11
+ Cyclical: Technology, Consumer Cyclical, Communication Services, Financial, Industrials
12
+ Defensive: Utilities, Consumer Defensive, Healthcare, Real Estate
13
+ Commodity: Energy, Basic Materials
14
+
15
+ Primary score: cyclical_avg - defensive_avg difference
16
+ > +0.15 -> 90-100 (strong risk-on)
17
+ +0.05~+0.15 -> 70-89 (healthy cyclical lead)
18
+ -0.05~+0.05 -> 45-69 (balanced)
19
+ -0.15~-0.05 -> 20-44 (defensive tilt)
20
+ < -0.15 -> 0-19 (strong risk-off)
21
+
22
+ Commodity adjustment: if commodity_avg > both groups -> late cycle flag, penalty
23
+ """
24
+
25
+ import sys
26
+ from typing import Dict, List, Optional
27
+
28
+ from data_fetcher import build_summary_from_timeseries
29
+
30
+
31
+ # Sector classification
32
+ CYCLICAL_SECTORS = [
33
+ "Technology", "Consumer Cyclical", "Communication Services",
34
+ "Financial", "Industrials",
35
+ ]
36
+ DEFENSIVE_SECTORS = [
37
+ "Utilities", "Consumer Defensive", "Healthcare", "Real Estate",
38
+ ]
39
+ COMMODITY_SECTORS = [
40
+ "Energy", "Basic Materials",
41
+ ]
42
+
43
+
44
+ def calculate_sector_rotation(sector_summary: List[Dict],
45
+ sector_timeseries: Dict[str, List[Dict]]) -> Dict:
46
+ """
47
+ Calculate sector rotation score.
48
+
49
+ Args:
50
+ sector_summary: List of sector summary rows
51
+ sector_timeseries: Dict mapping sector -> timeseries rows (reserved)
52
+
53
+ Returns:
54
+ Dict with score (0-100), signal, and detail fields
55
+ """
56
+ if not sector_summary:
57
+ if sector_timeseries:
58
+ sector_summary = build_summary_from_timeseries(sector_timeseries)
59
+ print(" (fallback: built sector summary from timeseries data)",
60
+ file=sys.stderr)
61
+ else:
62
+ return {
63
+ "score": 50,
64
+ "signal": "NO DATA: Sector summary unavailable (neutral default)",
65
+ "data_available": False,
66
+ "cyclical_avg": None,
67
+ "defensive_avg": None,
68
+ "commodity_avg": None,
69
+ "difference": None,
70
+ }
71
+
72
+ # Build lookup by sector name
73
+ sector_map = {s["Sector"]: s for s in sector_summary if s.get("Sector")}
74
+
75
+ # Calculate group averages
76
+ cyclical_ratios = _get_group_ratios(sector_map, CYCLICAL_SECTORS)
77
+ defensive_ratios = _get_group_ratios(sector_map, DEFENSIVE_SECTORS)
78
+ commodity_ratios = _get_group_ratios(sector_map, COMMODITY_SECTORS)
79
+
80
+ cyclical_avg = _avg(cyclical_ratios) if cyclical_ratios else None
81
+ defensive_avg = _avg(defensive_ratios) if defensive_ratios else None
82
+ commodity_avg = _avg(commodity_ratios) if commodity_ratios else None
83
+
84
+ if cyclical_avg is None or defensive_avg is None:
85
+ return {
86
+ "score": 50,
87
+ "signal": "INCOMPLETE DATA: Cannot calculate rotation (neutral default)",
88
+ "data_available": False,
89
+ "cyclical_avg": cyclical_avg,
90
+ "defensive_avg": defensive_avg,
91
+ "commodity_avg": commodity_avg,
92
+ "difference": None,
93
+ }
94
+
95
+ difference = cyclical_avg - defensive_avg
96
+
97
+ # Map difference to base score
98
+ base_score = _difference_to_score(difference)
99
+
100
+ # Commodity adjustment: late-cycle penalty
101
+ late_cycle_flag = False
102
+ commodity_penalty = 0
103
+ if commodity_avg is not None:
104
+ if commodity_avg > cyclical_avg and commodity_avg > defensive_avg:
105
+ late_cycle_flag = True
106
+ # Stronger penalty if commodity leads by a lot
107
+ excess = commodity_avg - max(cyclical_avg, defensive_avg)
108
+ if excess > 0.10:
109
+ commodity_penalty = -10
110
+ else:
111
+ commodity_penalty = -5
112
+
113
+ score = round(min(100, max(0, base_score + commodity_penalty)))
114
+
115
+ signal = _build_signal(score, difference, late_cycle_flag)
116
+
117
+ # Build group details
118
+ cyclical_details = _build_group_details(sector_map, CYCLICAL_SECTORS)
119
+ defensive_details = _build_group_details(sector_map, DEFENSIVE_SECTORS)
120
+ commodity_details = _build_group_details(sector_map, COMMODITY_SECTORS)
121
+
122
+ return {
123
+ "score": score,
124
+ "signal": signal,
125
+ "data_available": True,
126
+ "cyclical_avg": round(cyclical_avg, 4),
127
+ "cyclical_avg_pct": round(cyclical_avg * 100, 1),
128
+ "defensive_avg": round(defensive_avg, 4),
129
+ "defensive_avg_pct": round(defensive_avg * 100, 1),
130
+ "commodity_avg": round(commodity_avg, 4) if commodity_avg is not None else None,
131
+ "commodity_avg_pct": round(commodity_avg * 100, 1) if commodity_avg is not None else None,
132
+ "difference": round(difference, 4),
133
+ "difference_pct": round(difference * 100, 1),
134
+ "late_cycle_flag": late_cycle_flag,
135
+ "commodity_penalty": commodity_penalty,
136
+ "cyclical_details": cyclical_details,
137
+ "defensive_details": defensive_details,
138
+ "commodity_details": commodity_details,
139
+ }
140
+
141
+
142
+ def _get_group_ratios(sector_map: Dict[str, Dict],
143
+ sector_names: List[str]) -> List[float]:
144
+ """Extract ratios for a group of sectors."""
145
+ ratios = []
146
+ for name in sector_names:
147
+ sector = sector_map.get(name)
148
+ if sector and sector.get("Ratio") is not None:
149
+ ratios.append(sector["Ratio"])
150
+ return ratios
151
+
152
+
153
+ def _avg(values: List[float]) -> float:
154
+ """Simple average."""
155
+ return sum(values) / len(values)
156
+
157
+
158
+ def _difference_to_score(diff: float) -> float:
159
+ """Map cyclical-defensive difference to score.
160
+
161
+ > +0.15 -> 90-100 (strong risk-on)
162
+ +0.05~+0.15 -> 70-89 (healthy cyclical lead)
163
+ -0.05~+0.05 -> 45-69 (balanced)
164
+ -0.15~-0.05 -> 20-44 (defensive tilt)
165
+ < -0.15 -> 0-19 (strong risk-off)
166
+ """
167
+ if diff > 0.15:
168
+ return min(100, 90 + (diff - 0.15) / 0.10 * 10)
169
+ elif diff > 0.05:
170
+ return 70 + (diff - 0.05) / 0.10 * 19
171
+ elif diff > -0.05:
172
+ return 45 + (diff + 0.05) / 0.10 * 24
173
+ elif diff > -0.15:
174
+ return 20 + (diff + 0.15) / 0.10 * 24
175
+ else:
176
+ return max(0, 19 + (diff + 0.15) / 0.10 * 19)
177
+
178
+
179
+ def _build_signal(score: int, difference: float, late_cycle: bool) -> str:
180
+ """Build human-readable signal."""
181
+ diff_pct = round(difference * 100, 1)
182
+ late_str = " [LATE CYCLE WARNING]" if late_cycle else ""
183
+
184
+ if score >= 90:
185
+ return f"STRONG RISK-ON: Cyclical leads by {diff_pct}pp{late_str}"
186
+ elif score >= 70:
187
+ return f"RISK-ON: Cyclical leads by {diff_pct}pp{late_str}"
188
+ elif score >= 45:
189
+ return f"BALANCED: Cyclical-Defensive gap {diff_pct}pp{late_str}"
190
+ elif score >= 20:
191
+ return f"DEFENSIVE TILT: Defensive leads by {abs(diff_pct)}pp{late_str}"
192
+ else:
193
+ return f"STRONG RISK-OFF: Defensive leads by {abs(diff_pct)}pp{late_str}"
194
+
195
+
196
+ def _build_group_details(sector_map: Dict[str, Dict],
197
+ sector_names: List[str]) -> List[Dict]:
198
+ """Build detail rows for a sector group."""
199
+ details = []
200
+ for name in sector_names:
201
+ sector = sector_map.get(name)
202
+ if sector:
203
+ details.append({
204
+ "sector": name,
205
+ "ratio": sector.get("Ratio"),
206
+ "ratio_pct": round(sector["Ratio"] * 100, 1) if sector.get("Ratio") is not None else None,
207
+ "trend": sector.get("Trend", ""),
208
+ "slope": sector.get("Slope"),
209
+ })
210
+ else:
211
+ details.append({
212
+ "sector": name,
213
+ "ratio": None,
214
+ "ratio_pct": None,
215
+ "trend": "N/A",
216
+ "slope": None,
217
+ })
218
+ return details
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Uptrend Analyzer - CSV Data Fetcher
4
+
5
+ Downloads and parses Monty's Uptrend Ratio Dashboard CSV data from GitHub.
6
+ No API key required - uses publicly available CSV files.
7
+
8
+ Data Sources:
9
+ - Timeseries: uptrend_ratio_timeseries.csv (all + 11 sectors, 2023/08~present)
10
+ - Sector Summary: sector_summary.csv (latest snapshot)
11
+ """
12
+
13
+ import csv
14
+ import io
15
+ import sys
16
+ import time
17
+ from typing import Dict, List, Optional
18
+
19
+ try:
20
+ import requests
21
+ except ImportError:
22
+ print("ERROR: requests library not found. Install with: pip install requests",
23
+ file=sys.stderr)
24
+ sys.exit(1)
25
+
26
+
27
+ TIMESERIES_URL = (
28
+ "https://raw.githubusercontent.com/tradermonty/uptrend-dashboard/"
29
+ "main/data/uptrend_ratio_timeseries.csv"
30
+ )
31
+ SECTOR_SUMMARY_URL = (
32
+ "https://raw.githubusercontent.com/tradermonty/uptrend-dashboard/"
33
+ "main/data/sector_summary.csv"
34
+ )
35
+
36
+
37
+ WORKSHEET_TO_DISPLAY = {
38
+ "sec_basicmaterials": "Basic Materials",
39
+ "sec_communicationservices": "Communication Services",
40
+ "sec_consumercyclical": "Consumer Cyclical",
41
+ "sec_consumerdefensive": "Consumer Defensive",
42
+ "sec_energy": "Energy",
43
+ "sec_financial": "Financial",
44
+ "sec_healthcare": "Healthcare",
45
+ "sec_industrials": "Industrials",
46
+ "sec_realestate": "Real Estate",
47
+ "sec_technology": "Technology",
48
+ "sec_utilities": "Utilities",
49
+ }
50
+
51
+ # Monty's official dashboard thresholds (same as in sector_participation_calculator)
52
+ OVERBOUGHT_THRESHOLD = 0.37
53
+ OVERSOLD_THRESHOLD = 0.097
54
+
55
+
56
+ def build_summary_from_timeseries(sector_timeseries: Dict[str, Dict]) -> List[Dict]:
57
+ """Build sector_summary-compatible list from timeseries latest rows.
58
+
59
+ Used as fallback when sector_summary.csv is unavailable.
60
+
61
+ Args:
62
+ sector_timeseries: Dict mapping worksheet name -> latest timeseries row
63
+ e.g. {"sec_technology": {"ratio": 0.288, "ma_10": 0.266, ...}}
64
+
65
+ Returns:
66
+ List of dicts matching sector_summary format:
67
+ [{"Sector": "Technology", "Ratio": 0.288, "10MA": 0.266, ...}]
68
+ """
69
+ rows = []
70
+ for ws_name, row in sector_timeseries.items():
71
+ display_name = WORKSHEET_TO_DISPLAY.get(ws_name, ws_name)
72
+ ratio = row.get("ratio")
73
+ status = ("Overbought" if ratio is not None and ratio > OVERBOUGHT_THRESHOLD else
74
+ "Oversold" if ratio is not None and ratio < OVERSOLD_THRESHOLD else
75
+ "Normal")
76
+ rows.append({
77
+ "Sector": display_name,
78
+ "Ratio": ratio,
79
+ "10MA": row.get("ma_10"),
80
+ "Trend": (row.get("trend") or "").capitalize(),
81
+ "Slope": row.get("slope"),
82
+ "Status": status,
83
+ })
84
+ return rows
85
+
86
+
87
+ class UptrendDataFetcher:
88
+ """Client for Monty's Uptrend Ratio Dashboard CSV data"""
89
+
90
+ def __init__(self):
91
+ self.session = requests.Session()
92
+ self._timeseries_cache: Optional[List[Dict]] = None
93
+ self._sector_summary_cache: Optional[List[Dict]] = None
94
+
95
+ def fetch_timeseries(self) -> List[Dict]:
96
+ """Download and parse the timeseries CSV.
97
+
98
+ Returns:
99
+ List of dicts with keys: worksheet, date, count, total, ratio,
100
+ ma_10, slope, trend. Numeric fields cast to float/int.
101
+ """
102
+ if self._timeseries_cache is not None:
103
+ return self._timeseries_cache
104
+
105
+ try:
106
+ response = self.session.get(TIMESERIES_URL, timeout=30)
107
+ response.raise_for_status()
108
+ except requests.exceptions.RequestException as e:
109
+ print(f"WARNING: Failed to fetch timeseries CSV: {e}",
110
+ file=sys.stderr)
111
+ return []
112
+
113
+ rows = []
114
+ reader = csv.DictReader(io.StringIO(response.text))
115
+ for row in reader:
116
+ parsed = _parse_timeseries_row(row)
117
+ if parsed:
118
+ rows.append(parsed)
119
+
120
+ self._timeseries_cache = rows
121
+ return rows
122
+
123
+ def fetch_sector_summary(self) -> List[Dict]:
124
+ """Download and parse the sector summary CSV.
125
+
126
+ Returns:
127
+ List of dicts with keys: Sector, Ratio, 10MA, Trend, Slope, Status.
128
+ Numeric fields cast to float.
129
+ """
130
+ if self._sector_summary_cache is not None:
131
+ return self._sector_summary_cache
132
+
133
+ try:
134
+ response = self.session.get(SECTOR_SUMMARY_URL, timeout=30)
135
+ response.raise_for_status()
136
+ except requests.exceptions.RequestException as e:
137
+ print(f"WARNING: Failed to fetch sector summary CSV: {e}",
138
+ file=sys.stderr)
139
+ return []
140
+
141
+ rows = []
142
+ reader = csv.DictReader(io.StringIO(response.text))
143
+ for row in reader:
144
+ parsed = _parse_sector_summary_row(row)
145
+ if parsed:
146
+ rows.append(parsed)
147
+
148
+ self._sector_summary_cache = rows
149
+ return rows
150
+
151
+ def get_all_timeseries(self) -> List[Dict]:
152
+ """Get timeseries filtered to worksheet=='all', sorted by date ascending."""
153
+ data = self.fetch_timeseries()
154
+ filtered = [r for r in data if r["worksheet"] == "all"]
155
+ filtered.sort(key=lambda r: r["date"])
156
+ return filtered
157
+
158
+ def get_sector_timeseries(self, sector: str) -> List[Dict]:
159
+ """Get timeseries for a specific sector worksheet, sorted by date ascending."""
160
+ data = self.fetch_timeseries()
161
+ filtered = [r for r in data if r["worksheet"] == sector]
162
+ filtered.sort(key=lambda r: r["date"])
163
+ return filtered
164
+
165
+ def get_latest_all(self) -> Optional[Dict]:
166
+ """Get the most recent 'all' row."""
167
+ ts = self.get_all_timeseries()
168
+ return ts[-1] if ts else None
169
+
170
+ def get_all_sector_latest(self) -> Dict[str, Dict]:
171
+ """Get latest row for each sector (excluding 'all').
172
+
173
+ Returns:
174
+ Dict mapping sector name -> latest timeseries row
175
+ """
176
+ data = self.fetch_timeseries()
177
+ sectors: Dict[str, Dict] = {}
178
+ for row in data:
179
+ ws = row["worksheet"]
180
+ if ws == "all":
181
+ continue
182
+ if ws not in sectors or row["date"] > sectors[ws]["date"]:
183
+ sectors[ws] = row
184
+ return sectors
185
+
186
+
187
+ def _parse_timeseries_row(row: Dict) -> Optional[Dict]:
188
+ """Parse a timeseries CSV row, casting numeric fields."""
189
+ try:
190
+ return {
191
+ "worksheet": row.get("worksheet", "").strip(),
192
+ "date": row.get("date", "").strip(),
193
+ "count": _safe_int(row.get("count")),
194
+ "total": _safe_int(row.get("total")),
195
+ "ratio": _safe_float(row.get("ratio")),
196
+ "ma_10": _safe_float(row.get("ma_10")),
197
+ "slope": _safe_float(row.get("slope")),
198
+ "trend": row.get("trend", "").strip(),
199
+ }
200
+ except (ValueError, TypeError):
201
+ return None
202
+
203
+
204
+ def _parse_sector_summary_row(row: Dict) -> Optional[Dict]:
205
+ """Parse a sector summary CSV row, casting numeric fields."""
206
+ try:
207
+ return {
208
+ "Sector": row.get("Sector", "").strip(),
209
+ "Ratio": _safe_float(row.get("Ratio")),
210
+ "10MA": _safe_float(row.get("10MA")),
211
+ "Trend": row.get("Trend", "").strip(),
212
+ "Slope": _safe_float(row.get("Slope")),
213
+ "Status": row.get("Status", "").strip(),
214
+ }
215
+ except (ValueError, TypeError):
216
+ return None
217
+
218
+
219
+ def _safe_float(value) -> Optional[float]:
220
+ """Convert to float, return None if empty or invalid."""
221
+ if value is None or str(value).strip() == "":
222
+ return None
223
+ try:
224
+ return float(value)
225
+ except (ValueError, TypeError):
226
+ return None
227
+
228
+
229
+ def _safe_int(value) -> Optional[int]:
230
+ """Convert to int, return None if empty or invalid."""
231
+ if value is None or str(value).strip() == "":
232
+ return None
233
+ try:
234
+ return int(float(value))
235
+ except (ValueError, TypeError):
236
+ return None