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,574 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Macro Regime Detector - Composite Scoring & Regime Classification
4
+
5
+ Combines 6 component transition signals into:
6
+ 1. Weighted composite transition score (0-100)
7
+ 2. Regime classification (5 regimes)
8
+ 3. Transition probability assessment
9
+
10
+ Component Weights:
11
+ 1. Market Concentration (RSP/SPY): 25%
12
+ 2. Yield Curve (10Y-2Y): 20%
13
+ 3. Credit Conditions (HYG/LQD): 15%
14
+ 4. Size Factor (IWM/SPY): 15%
15
+ 5. Equity-Bond (SPY/TLT+corr): 15%
16
+ 6. Sector Rotation (XLY/XLP): 10%
17
+ Total: 100%
18
+
19
+ 5 Regimes:
20
+ - Concentration: RSP/SPY↓, IWM/SPY↓, credit stable
21
+ - Broadening: RSP/SPY↑, IWM/SPY↑, credit stable/easing
22
+ - Contraction: Credit tight, XLY/XLP↓, SPY/TLT↓
23
+ - Inflationary: Stock-Bond positive corr, SPY/TLT↓
24
+ - Transitional: 3+ components signaling, unclear pattern
25
+ """
26
+
27
+ from typing import Dict, List, Optional
28
+
29
+
30
+ COMPONENT_WEIGHTS = {
31
+ "concentration": 0.25,
32
+ "yield_curve": 0.20,
33
+ "credit_conditions": 0.15,
34
+ "size_factor": 0.15,
35
+ "equity_bond": 0.15,
36
+ "sector_rotation": 0.10,
37
+ }
38
+
39
+ COMPONENT_LABELS = {
40
+ "concentration": "Market Concentration (RSP/SPY)",
41
+ "yield_curve": "Yield Curve (10Y-2Y)",
42
+ "credit_conditions": "Credit Conditions (HYG/LQD)",
43
+ "size_factor": "Size Factor (IWM/SPY)",
44
+ "equity_bond": "Equity-Bond (SPY/TLT)",
45
+ "sector_rotation": "Sector Rotation (XLY/XLP)",
46
+ }
47
+
48
+
49
+ def calculate_composite_score(component_scores: Dict[str, float],
50
+ data_availability: Optional[Dict[str, bool]] = None) -> Dict:
51
+ """
52
+ Calculate weighted composite transition signal score.
53
+
54
+ Args:
55
+ component_scores: Dict with keys matching COMPONENT_WEIGHTS, each 0-100
56
+ data_availability: Optional dict mapping component key -> bool
57
+
58
+ Returns:
59
+ Dict with composite_score, zone, guidance, component breakdown, data_quality
60
+ """
61
+ if data_availability is None:
62
+ data_availability = {}
63
+
64
+ composite = 0.0
65
+ for key, weight in COMPONENT_WEIGHTS.items():
66
+ score = component_scores.get(key, 0)
67
+ composite += score * weight
68
+
69
+ composite = round(composite, 1)
70
+
71
+ # Identify strongest and weakest signals
72
+ valid_scores = {k: v for k, v in component_scores.items()
73
+ if k in COMPONENT_WEIGHTS}
74
+
75
+ if valid_scores:
76
+ strongest_signal = max(valid_scores, key=valid_scores.get)
77
+ weakest_signal = min(valid_scores, key=valid_scores.get)
78
+ else:
79
+ strongest_signal = "N/A"
80
+ weakest_signal = "N/A"
81
+
82
+ # Zone interpretation
83
+ zone_info = _interpret_zone(composite)
84
+
85
+ # Data quality
86
+ available_count = sum(
87
+ 1 for k in COMPONENT_WEIGHTS
88
+ if data_availability.get(k, True)
89
+ )
90
+ total_components = len(COMPONENT_WEIGHTS)
91
+ missing_components = [
92
+ COMPONENT_LABELS[k] for k in COMPONENT_WEIGHTS
93
+ if not data_availability.get(k, True)
94
+ ]
95
+
96
+ if available_count == total_components:
97
+ quality_label = f"Complete ({available_count}/{total_components} components)"
98
+ elif available_count >= total_components - 2:
99
+ quality_label = (f"Partial ({available_count}/{total_components} components)"
100
+ " - interpret with caution")
101
+ else:
102
+ quality_label = (f"Limited ({available_count}/{total_components} components)"
103
+ " - low confidence")
104
+
105
+ # Count components with significant signals
106
+ signaling_count = sum(1 for v in valid_scores.values() if v >= 40)
107
+
108
+ return {
109
+ "composite_score": composite,
110
+ "zone": zone_info["zone"],
111
+ "zone_color": zone_info["color"],
112
+ "guidance": zone_info["guidance"],
113
+ "actions": zone_info["actions"],
114
+ "signaling_components": signaling_count,
115
+ "strongest_signal": {
116
+ "component": strongest_signal,
117
+ "label": COMPONENT_LABELS.get(strongest_signal, strongest_signal),
118
+ "score": valid_scores.get(strongest_signal, 0),
119
+ },
120
+ "weakest_signal": {
121
+ "component": weakest_signal,
122
+ "label": COMPONENT_LABELS.get(weakest_signal, weakest_signal),
123
+ "score": valid_scores.get(weakest_signal, 0),
124
+ },
125
+ "data_quality": {
126
+ "available_count": available_count,
127
+ "total_components": total_components,
128
+ "label": quality_label,
129
+ "missing_components": missing_components,
130
+ },
131
+ "component_scores": {
132
+ k: {
133
+ "score": component_scores.get(k, 0),
134
+ "weight": w,
135
+ "weighted_contribution": round(component_scores.get(k, 0) * w, 1),
136
+ "label": COMPONENT_LABELS[k],
137
+ }
138
+ for k, w in COMPONENT_WEIGHTS.items()
139
+ },
140
+ }
141
+
142
+
143
+ def classify_regime(component_results: Dict[str, Dict]) -> Dict:
144
+ """
145
+ Classify current macro regime based on component directions and signals.
146
+
147
+ Only components with data_available=True contribute to regime scoring.
148
+ Confidence is capped when data availability is limited.
149
+
150
+ Args:
151
+ component_results: Dict of component key -> full result dict from each calculator
152
+
153
+ Returns:
154
+ Dict with current_regime, confidence, evidence, transition_probability
155
+ """
156
+ # Extract directions and scores, treating unavailable data as neutral
157
+ conc = component_results.get("concentration", {})
158
+ yc = component_results.get("yield_curve", {})
159
+ credit = component_results.get("credit_conditions", {})
160
+ size = component_results.get("size_factor", {})
161
+ eb = component_results.get("equity_bond", {})
162
+ sector = component_results.get("sector_rotation", {})
163
+
164
+ # Count available components for confidence adjustment
165
+ available_count = sum(
166
+ 1 for comp in [conc, yc, credit, size, eb, sector]
167
+ if comp.get("data_available", False)
168
+ )
169
+
170
+ # Only use direction from components with available data;
171
+ # unavailable components default to "unknown" (neutral, no scoring)
172
+ def _dir(comp: Dict) -> str:
173
+ if not comp.get("data_available", False):
174
+ return "unknown"
175
+ return comp.get("direction", "unknown")
176
+
177
+ conc_dir = _dir(conc)
178
+ credit_dir = _dir(credit)
179
+ size_dir = _dir(size)
180
+ eb_dir = _dir(eb)
181
+ sector_dir = _dir(sector)
182
+ corr_regime = eb.get("correlation_regime", "unknown") if eb.get("data_available", False) else "unknown"
183
+
184
+ # Score each regime hypothesis
185
+ regime_scores = {
186
+ "concentration": _score_concentration_regime(conc_dir, size_dir, credit_dir),
187
+ "broadening": _score_broadening_regime(conc_dir, size_dir, credit_dir, sector_dir),
188
+ "contraction": _score_contraction_regime(credit_dir, sector_dir, eb_dir),
189
+ "inflationary": _score_inflationary_regime(corr_regime, eb_dir),
190
+ "transitional": 0, # Computed below
191
+ }
192
+
193
+ # Count signaling components
194
+ all_scores = [
195
+ conc.get("score", 0), yc.get("score", 0), credit.get("score", 0),
196
+ size.get("score", 0), eb.get("score", 0), sector.get("score", 0),
197
+ ]
198
+ signaling = sum(1 for s in all_scores if s >= 40)
199
+
200
+ # Find best matching regime (excluding transitional for ranking)
201
+ scored = {k: v for k, v in regime_scores.items() if k != "transitional"}
202
+ sorted_regimes = sorted(scored.items(), key=lambda x: x[1], reverse=True)
203
+
204
+ best_regime = sorted_regimes[0][0]
205
+ best_score = sorted_regimes[0][1]
206
+
207
+ # Tiebreak detection: top 2 regimes within 1 point
208
+ is_tied = (len(sorted_regimes) >= 2
209
+ and best_score > 0
210
+ and (best_score - sorted_regimes[1][1]) <= 1)
211
+ tied_regimes = ([sorted_regimes[0][0], sorted_regimes[1][0]]
212
+ if is_tied else None)
213
+
214
+ # Quick composite for tiebreak resolution
215
+ quick_composite = sum(
216
+ comp.get("score", 0) * COMPONENT_WEIGHTS[k]
217
+ for k, comp in component_results.items()
218
+ if k in COMPONENT_WEIGHTS
219
+ )
220
+
221
+ # Tiebreak + low composite → transitional
222
+ if is_tied and quick_composite < 50:
223
+ best_regime = "transitional"
224
+ regime_scores["transitional"] = best_score
225
+
226
+ # Fallback: multiple signals but no clear regime pattern
227
+ elif signaling >= 3 and best_score < 3:
228
+ best_regime = "transitional"
229
+ best_score = signaling
230
+
231
+ # Confidence level (capped by data availability)
232
+ if best_score >= 4:
233
+ confidence = "high"
234
+ elif best_score >= 3:
235
+ confidence = "moderate"
236
+ elif best_score >= 2:
237
+ confidence = "low"
238
+ else:
239
+ confidence = "very_low"
240
+
241
+ # Cap confidence when data is limited
242
+ if available_count <= 3:
243
+ # With 3 or fewer components, never report above "very_low"
244
+ confidence = "very_low"
245
+ elif available_count <= 4:
246
+ # With 4 components, cap at "low"
247
+ if confidence in ("high", "moderate"):
248
+ confidence = "low"
249
+
250
+ # Transition probability
251
+ transition_prob = _calculate_transition_probability(
252
+ signaling, all_scores, regime_scores, best_regime, sorted_regimes
253
+ )
254
+
255
+ # Evidence summary
256
+ evidence = _build_evidence(component_results)
257
+
258
+ # Regime description
259
+ regime_info = REGIME_DESCRIPTIONS.get(best_regime, {})
260
+
261
+ return {
262
+ "current_regime": best_regime,
263
+ "regime_label": regime_info.get("label", best_regime),
264
+ "regime_description": regime_info.get("description", ""),
265
+ "confidence": confidence,
266
+ "regime_scores": regime_scores,
267
+ "signaling_components": signaling,
268
+ "transition_probability": transition_prob,
269
+ "portfolio_posture": regime_info.get("portfolio_posture", ""),
270
+ "evidence": evidence,
271
+ "tied_regimes": tied_regimes,
272
+ }
273
+
274
+
275
+ REGIME_DESCRIPTIONS = {
276
+ "concentration": {
277
+ "label": "Concentration",
278
+ "description": "Market leadership concentrated in mega-cap stocks. RSP/SPY declining, "
279
+ "small-caps underperforming. Credit conditions stable.",
280
+ "portfolio_posture": "Focus on mega-cap tech/growth leaders. "
281
+ "Underweight small-caps and value.",
282
+ },
283
+ "broadening": {
284
+ "label": "Broadening",
285
+ "description": "Market participation expanding. RSP/SPY rising, small-caps catching up. "
286
+ "Credit easing or stable.",
287
+ "portfolio_posture": "Add small-cap/mid-cap exposure. Equal-weight strategies. "
288
+ "Value and cyclical rotation.",
289
+ },
290
+ "contraction": {
291
+ "label": "Contraction",
292
+ "description": "Credit tightening, defensive rotation, equity-bond shift to risk-off. "
293
+ "Classic late-cycle deterioration.",
294
+ "portfolio_posture": "Raise cash. Increase Treasury allocation. "
295
+ "Defensive sectors (Staples, Utilities, Healthcare).",
296
+ },
297
+ "inflationary": {
298
+ "label": "Inflationary",
299
+ "description": "Positive stock-bond correlation regime. Both equities and bonds under "
300
+ "pressure. Traditional hedging breaks down.",
301
+ "portfolio_posture": "Real assets, commodities, energy. Short-duration bonds. "
302
+ "TIPS. Reduce long-duration exposure.",
303
+ },
304
+ "transitional": {
305
+ "label": "Transitional",
306
+ "description": "Multiple components signaling change but no clear regime pattern. "
307
+ "Market in flux between regimes.",
308
+ "portfolio_posture": "Increase diversification. Gradual repositioning. "
309
+ "Avoid concentrated bets. Monitor weekly for regime clarity.",
310
+ },
311
+ }
312
+
313
+
314
+ def _score_concentration_regime(conc_dir: str, size_dir: str, credit_dir: str) -> int:
315
+ """Score evidence for Concentration regime.
316
+
317
+ Only scores when direction is known. 'unknown' (missing data) is neutral.
318
+ """
319
+ score = 0
320
+ if conc_dir == "concentrating":
321
+ score += 2
322
+ if size_dir == "large_cap_leading":
323
+ score += 2
324
+ if credit_dir in ("stable", "easing"):
325
+ score += 1
326
+ return score
327
+
328
+
329
+ def _score_broadening_regime(conc_dir: str, size_dir: str,
330
+ credit_dir: str, sector_dir: str) -> int:
331
+ """Score evidence for Broadening regime."""
332
+ score = 0
333
+ if conc_dir == "broadening":
334
+ score += 2
335
+ if size_dir == "small_cap_leading":
336
+ score += 2
337
+ if credit_dir in ("stable", "easing"):
338
+ score += 1
339
+ if sector_dir == "risk_on":
340
+ score += 1
341
+ return score
342
+
343
+
344
+ def _score_contraction_regime(credit_dir: str, sector_dir: str, eb_dir: str) -> int:
345
+ """Score evidence for Contraction regime."""
346
+ score = 0
347
+ if credit_dir == "tightening":
348
+ score += 2
349
+ if sector_dir == "risk_off":
350
+ score += 2
351
+ if eb_dir == "risk_off":
352
+ score += 1
353
+ return score
354
+
355
+
356
+ def _score_inflationary_regime(corr_regime: str, eb_dir: str) -> int:
357
+ """Score evidence for Inflationary regime."""
358
+ score = 0
359
+ if corr_regime == "positive":
360
+ score += 3
361
+ elif corr_regime == "near_zero":
362
+ score += 1
363
+ if eb_dir == "risk_off":
364
+ score += 1
365
+ return score
366
+
367
+
368
+ def _calculate_transition_probability(signaling: int, all_scores: List[float],
369
+ regime_scores: Dict[str, int],
370
+ current_regime: str,
371
+ sorted_regimes: Optional[List] = None) -> Dict:
372
+ """
373
+ Calculate probability that a regime transition is underway.
374
+
375
+ Returns dict with probability level, supporting metrics, and transition direction.
376
+ """
377
+ avg_score = sum(all_scores) / len(all_scores) if all_scores else 0
378
+
379
+ # High probability: many components signaling + high average score
380
+ if signaling >= 4 and avg_score >= 50:
381
+ prob_level = "high"
382
+ prob_pct = "70-90%"
383
+ elif signaling >= 3 and avg_score >= 40:
384
+ prob_level = "moderate"
385
+ prob_pct = "40-60%"
386
+ elif signaling >= 2 and avg_score >= 30:
387
+ prob_level = "low"
388
+ prob_pct = "20-40%"
389
+ else:
390
+ prob_level = "minimal"
391
+ prob_pct = "<20%"
392
+
393
+ # Check if second-best regime is close (ambiguity)
394
+ if sorted_regimes is None:
395
+ scored = {k: v for k, v in regime_scores.items() if k != "transitional"}
396
+ sorted_regimes = sorted(scored.items(), key=lambda x: x[1], reverse=True)
397
+
398
+ ambiguity = False
399
+ if len(sorted_regimes) >= 2:
400
+ best_val = sorted_regimes[0][1]
401
+ second_val = sorted_regimes[1][1]
402
+ if best_val > 0 and second_val >= best_val - 1:
403
+ ambiguity = True
404
+
405
+ # Transition direction
406
+ from_regime = None
407
+ to_regime = None
408
+ if prob_level in ("high", "moderate") and sorted_regimes:
409
+ top = sorted_regimes[0][0]
410
+ if current_regime == "transitional" and len(sorted_regimes) >= 2:
411
+ to_regime = top
412
+ from_regime = (sorted_regimes[1][0]
413
+ if sorted_regimes[1][1] > 0 else None)
414
+ elif top != current_regime:
415
+ from_regime = current_regime
416
+ to_regime = top
417
+
418
+ return {
419
+ "level": prob_level,
420
+ "probability_range": prob_pct,
421
+ "signaling_count": signaling,
422
+ "avg_component_score": round(avg_score, 1),
423
+ "ambiguous": ambiguity,
424
+ "from_regime": from_regime,
425
+ "to_regime": to_regime,
426
+ }
427
+
428
+
429
+ REGIME_CONSISTENCY = {
430
+ "broadening": {
431
+ "concentration": "broadening",
432
+ "size_factor": "small_cap_leading",
433
+ "credit_conditions": ["stable", "easing"],
434
+ "sector_rotation": "risk_on",
435
+ "equity_bond": "risk_on",
436
+ },
437
+ "concentration": {
438
+ "concentration": "concentrating",
439
+ "size_factor": "large_cap_leading",
440
+ "credit_conditions": ["stable", "easing"],
441
+ },
442
+ "contraction": {
443
+ "credit_conditions": "tightening",
444
+ "sector_rotation": "risk_off",
445
+ "equity_bond": "risk_off",
446
+ },
447
+ "inflationary": {
448
+ "equity_bond": "risk_off",
449
+ },
450
+ "transitional": {},
451
+ }
452
+
453
+
454
+ def check_regime_consistency(current_regime: str,
455
+ component_results: Dict[str, Dict]) -> Dict[str, str]:
456
+ """
457
+ Check whether each component's direction is consistent with the classified regime.
458
+
459
+ Returns:
460
+ Dict mapping component key -> "consistent" | "contradicting" | "neutral"
461
+ """
462
+ expected = REGIME_CONSISTENCY.get(current_regime, {})
463
+ result = {}
464
+
465
+ for key in COMPONENT_WEIGHTS:
466
+ comp = component_results.get(key, {})
467
+ direction = comp.get("direction", "unknown")
468
+
469
+ if key not in expected:
470
+ result[key] = "neutral"
471
+ continue
472
+
473
+ exp_dir = expected[key]
474
+ if isinstance(exp_dir, list):
475
+ if direction in exp_dir:
476
+ result[key] = "consistent"
477
+ elif direction == "unknown":
478
+ result[key] = "neutral"
479
+ else:
480
+ result[key] = "contradicting"
481
+ else:
482
+ if direction == exp_dir:
483
+ result[key] = "consistent"
484
+ elif direction == "unknown":
485
+ result[key] = "neutral"
486
+ else:
487
+ result[key] = "contradicting"
488
+
489
+ return result
490
+
491
+
492
+ def _build_evidence(component_results: Dict[str, Dict]) -> List[Dict]:
493
+ """Build evidence list from component results."""
494
+ evidence = []
495
+ for key in COMPONENT_WEIGHTS:
496
+ comp = component_results.get(key, {})
497
+ if not comp:
498
+ continue
499
+ score = comp.get("score", 0)
500
+ if score >= 40:
501
+ evidence.append({
502
+ "component": COMPONENT_LABELS.get(key, key),
503
+ "score": score,
504
+ "signal": comp.get("signal", ""),
505
+ "direction": comp.get("direction", "unknown"),
506
+ })
507
+ # Sort by score descending
508
+ evidence.sort(key=lambda x: x["score"], reverse=True)
509
+ return evidence
510
+
511
+
512
+ def _interpret_zone(composite: float) -> Dict:
513
+ """Map composite score to transition signal zone."""
514
+ if composite <= 20:
515
+ return {
516
+ "zone": "Stable (No Transition)",
517
+ "color": "green",
518
+ "guidance": "All indicators stable. Current regime well-established.",
519
+ "actions": [
520
+ "Maintain current portfolio posture",
521
+ "Standard rebalancing schedule",
522
+ "Monitor monthly for early signals",
523
+ ],
524
+ }
525
+ elif composite <= 40:
526
+ return {
527
+ "zone": "Early Signal (Monitoring)",
528
+ "color": "yellow",
529
+ "guidance": "Minor shifts detected. Worth monitoring but not actionable yet.",
530
+ "actions": [
531
+ "Increase monitoring frequency to bi-weekly",
532
+ "Review portfolio for regime sensitivity",
533
+ "Identify potential adjustment triggers",
534
+ "No position changes needed yet",
535
+ ],
536
+ }
537
+ elif composite <= 60:
538
+ return {
539
+ "zone": "Transition Zone (Preparing)",
540
+ "color": "orange",
541
+ "guidance": "Multiple indicators in transition. Begin planning portfolio adjustment.",
542
+ "actions": [
543
+ "Develop regime-change response plan",
544
+ "Begin gradual diversification shifts",
545
+ "Reduce concentrated bets",
546
+ "Identify new regime beneficiaries",
547
+ "Weekly monitoring",
548
+ ],
549
+ }
550
+ elif composite <= 80:
551
+ return {
552
+ "zone": "Active Transition (Repositioning)",
553
+ "color": "red",
554
+ "guidance": "Clear regime transition underway. Execute repositioning plan.",
555
+ "actions": [
556
+ "Execute planned portfolio adjustments",
557
+ "Rotate toward new regime beneficiaries",
558
+ "Reduce exposure to old regime leaders",
559
+ "Increase hedging if contracting/inflationary",
560
+ "Daily monitoring of confirmation signals",
561
+ ],
562
+ }
563
+ else:
564
+ return {
565
+ "zone": "Confirmed Transition (Completing)",
566
+ "color": "critical",
567
+ "guidance": "Strong confirmed transition. Complete repositioning.",
568
+ "actions": [
569
+ "Finalize portfolio repositioning",
570
+ "Full allocation to new regime posture",
571
+ "Monitor for transition exhaustion signals",
572
+ "Prepare for new regime stability",
573
+ ],
574
+ }
@@ -0,0 +1,9 @@
1
+ """Shared fixtures for Macro Regime Detector tests"""
2
+
3
+ import os
4
+ import sys
5
+
6
+ # Add scripts directory to path so calculators can be imported
7
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
8
+ # Add tests directory to path so test helpers can be imported
9
+ sys.path.insert(0, os.path.dirname(__file__))
@@ -0,0 +1,78 @@
1
+ """Tests for Market Concentration Calculator (RSP/SPY)"""
2
+
3
+ from calculators.concentration_calculator import calculate_concentration
4
+ from test_helpers import make_monthly_history
5
+
6
+
7
+ class TestCalculateConcentration:
8
+ def test_insufficient_data_empty(self):
9
+ result = calculate_concentration([], [])
10
+ assert result["score"] == 0
11
+ assert result["data_available"] is False
12
+
13
+ def test_insufficient_data_too_short(self):
14
+ rsp = make_monthly_history([100] * 6, start_year=2025)
15
+ spy = make_monthly_history([100] * 6, start_year=2025)
16
+ result = calculate_concentration(rsp, spy)
17
+ assert result["data_available"] is False
18
+
19
+ def test_stable_ratio_low_score(self):
20
+ # Flat ratio = no transition signal
21
+ rsp = make_monthly_history([100] * 24, start_year=2024)
22
+ spy = make_monthly_history([100] * 24, start_year=2024)
23
+ result = calculate_concentration(rsp, spy)
24
+ assert result["data_available"] is True
25
+ assert result["score"] <= 30 # Small noise from daily variation is expected
26
+ assert result["current_ratio"] is not None
27
+
28
+ def test_rising_rsp_spy_broadening(self):
29
+ # RSP rising faster than SPY = broadening
30
+ # Create a clear uptrend in RSP relative to SPY
31
+ rsp_closes = [100 + i * 2 for i in range(24)] # Rises from 100 to 146
32
+ spy_closes = [100 + i * 0.5 for i in range(24)] # Rises from 100 to 111.5
33
+ rsp = make_monthly_history(rsp_closes, start_year=2024)
34
+ spy = make_monthly_history(spy_closes, start_year=2024)
35
+ result = calculate_concentration(rsp, spy)
36
+ assert result["data_available"] is True
37
+ assert result["monthly_points"] >= 12
38
+
39
+ def test_declining_rsp_spy_concentrating(self):
40
+ # SPY rising faster than RSP = concentrating
41
+ rsp_closes = [100 + i * 0.5 for i in range(24)]
42
+ spy_closes = [100 + i * 2 for i in range(24)]
43
+ rsp = make_monthly_history(rsp_closes, start_year=2024)
44
+ spy = make_monthly_history(spy_closes, start_year=2024)
45
+ result = calculate_concentration(rsp, spy)
46
+ assert result["data_available"] is True
47
+
48
+ def test_crossover_detected(self):
49
+ # Create a ratio that crosses over (death_cross then reverses)
50
+ # First 12 months: RSP/SPY declining, next 12 months: recovering
51
+ rsp_closes = [120 - i for i in range(12)] + [108 + i * 2 for i in range(12)]
52
+ spy_closes = [100] * 24
53
+ rsp = make_monthly_history(rsp_closes, start_year=2024)
54
+ spy = make_monthly_history(spy_closes, start_year=2024)
55
+ result = calculate_concentration(rsp, spy)
56
+ assert result["data_available"] is True
57
+ assert result["crossover"]["type"] in ("golden_cross", "death_cross", "converging", "none")
58
+
59
+ def test_output_structure(self):
60
+ rsp = make_monthly_history([100 + i for i in range(24)], start_year=2024)
61
+ spy = make_monthly_history([100] * 24, start_year=2024)
62
+ result = calculate_concentration(rsp, spy)
63
+
64
+ # Verify required keys
65
+ assert "score" in result
66
+ assert "signal" in result
67
+ assert "data_available" in result
68
+ assert "direction" in result
69
+ assert "current_ratio" in result
70
+ assert "sma_6m" in result
71
+ assert "sma_12m" in result
72
+ assert "roc_3m" in result
73
+ assert "roc_12m" in result
74
+ assert "percentile" in result
75
+ assert "crossover" in result
76
+ assert "monthly_points" in result
77
+
78
+ assert 0 <= result["score"] <= 100