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,59 @@
1
+ """Tests for Credit Conditions Calculator (HYG/LQD)"""
2
+
3
+ from calculators.credit_conditions_calculator import calculate_credit_conditions
4
+ from test_helpers import make_monthly_history
5
+
6
+
7
+ class TestCalculateCreditConditions:
8
+ def test_insufficient_data_empty(self):
9
+ result = calculate_credit_conditions([], [])
10
+ assert result["score"] == 0
11
+ assert result["data_available"] is False
12
+
13
+ def test_stable_ratio_low_score(self):
14
+ hyg = make_monthly_history([75] * 24, start_year=2024)
15
+ lqd = make_monthly_history([105] * 24, start_year=2024)
16
+ result = calculate_credit_conditions(hyg, lqd)
17
+ assert result["data_available"] is True
18
+ assert result["score"] <= 30 # Small noise from daily variation is expected
19
+
20
+ def test_easing_conditions(self):
21
+ # HYG rising relative to LQD = easing
22
+ hyg_closes = [70 + i * 0.5 for i in range(24)]
23
+ lqd_closes = [105] * 24
24
+ hyg = make_monthly_history(hyg_closes, start_year=2024)
25
+ lqd = make_monthly_history(lqd_closes, start_year=2024)
26
+ result = calculate_credit_conditions(hyg, lqd)
27
+ assert result["data_available"] is True
28
+
29
+ def test_tightening_conditions(self):
30
+ # HYG falling relative to LQD = tightening
31
+ hyg_closes = [80 - i * 0.5 for i in range(24)]
32
+ lqd_closes = [105] * 24
33
+ hyg = make_monthly_history(hyg_closes, start_year=2024)
34
+ lqd = make_monthly_history(lqd_closes, start_year=2024)
35
+ result = calculate_credit_conditions(hyg, lqd)
36
+ assert result["data_available"] is True
37
+
38
+ def test_output_structure(self):
39
+ hyg = make_monthly_history([75 + i * 0.1 for i in range(24)], start_year=2024)
40
+ lqd = make_monthly_history([105] * 24, start_year=2024)
41
+ result = calculate_credit_conditions(hyg, lqd)
42
+
43
+ required_keys = [
44
+ "score",
45
+ "signal",
46
+ "data_available",
47
+ "direction",
48
+ "current_ratio",
49
+ "sma_6m",
50
+ "sma_12m",
51
+ "roc_3m",
52
+ "roc_12m",
53
+ "percentile",
54
+ "crossover",
55
+ "monthly_points",
56
+ ]
57
+ for key in required_keys:
58
+ assert key in result
59
+ assert 0 <= result["score"] <= 100
@@ -0,0 +1,74 @@
1
+ """Tests for Equity-Bond Relationship Calculator (SPY/TLT + correlation)"""
2
+
3
+ from calculators.equity_bond_calculator import calculate_equity_bond
4
+ from test_helpers import make_monthly_history
5
+
6
+
7
+ class TestCalculateEquityBond:
8
+ def test_insufficient_data_empty(self):
9
+ result = calculate_equity_bond([], [])
10
+ assert result["score"] == 0
11
+ assert result["data_available"] is False
12
+
13
+ def test_stable_ratio_low_score(self):
14
+ spy = make_monthly_history([500] * 24, start_year=2024)
15
+ tlt = make_monthly_history([90] * 24, start_year=2024)
16
+ result = calculate_equity_bond(spy, tlt)
17
+ assert result["data_available"] is True
18
+ assert result["score"] <= 30 # Small noise from daily variation is expected
19
+
20
+ def test_risk_on_shift(self):
21
+ # SPY rising, TLT flat = risk-on
22
+ spy_closes = [500 + i * 5 for i in range(24)]
23
+ tlt_closes = [90] * 24
24
+ spy = make_monthly_history(spy_closes, start_year=2024)
25
+ tlt = make_monthly_history(tlt_closes, start_year=2024)
26
+ result = calculate_equity_bond(spy, tlt)
27
+ assert result["data_available"] is True
28
+
29
+ def test_risk_off_shift(self):
30
+ # SPY falling, TLT rising = risk-off
31
+ spy_closes = [600 - i * 5 for i in range(24)]
32
+ tlt_closes = [80 + i * 2 for i in range(24)]
33
+ spy = make_monthly_history(spy_closes, start_year=2024)
34
+ tlt = make_monthly_history(tlt_closes, start_year=2024)
35
+ result = calculate_equity_bond(spy, tlt)
36
+ assert result["data_available"] is True
37
+
38
+ def test_correlation_regime_present(self):
39
+ spy = make_monthly_history([500 + i * 2 for i in range(24)], start_year=2024)
40
+ tlt = make_monthly_history([90 - i * 0.5 for i in range(24)], start_year=2024)
41
+ result = calculate_equity_bond(spy, tlt)
42
+ assert result["correlation_regime"] in (
43
+ "negative_strong",
44
+ "negative_mild",
45
+ "near_zero",
46
+ "positive",
47
+ "unknown",
48
+ )
49
+
50
+ def test_output_structure(self):
51
+ spy = make_monthly_history([500 + i for i in range(24)], start_year=2024)
52
+ tlt = make_monthly_history([90] * 24, start_year=2024)
53
+ result = calculate_equity_bond(spy, tlt)
54
+
55
+ required_keys = [
56
+ "score",
57
+ "signal",
58
+ "data_available",
59
+ "direction",
60
+ "correlation_regime",
61
+ "current_ratio",
62
+ "sma_6m",
63
+ "sma_12m",
64
+ "roc_3m",
65
+ "roc_12m",
66
+ "percentile",
67
+ "correlation_6m",
68
+ "correlation_12m",
69
+ "crossover",
70
+ "monthly_points",
71
+ ]
72
+ for key in required_keys:
73
+ assert key in result
74
+ assert 0 <= result["score"] <= 100
@@ -0,0 +1,90 @@
1
+ """Importable test helpers for Macro Regime Detector tests"""
2
+
3
+
4
+ def make_daily_bar(close, volume=1000000, date="2026-01-15", open_=None, high=None, low=None):
5
+ """Helper to create a daily OHLCV bar dict."""
6
+ if open_ is None:
7
+ open_ = close
8
+ if high is None:
9
+ high = close * 1.005
10
+ if low is None:
11
+ low = close * 0.995
12
+ return {
13
+ "date": date,
14
+ "open": open_,
15
+ "high": high,
16
+ "low": low,
17
+ "close": close,
18
+ "adjClose": close,
19
+ "volume": volume,
20
+ }
21
+
22
+
23
+ def make_history(closes, base_volume=1000000, start_date="2026-01-15"):
24
+ """Create a list of daily bars from a list of closes (most recent first)."""
25
+ bars = []
26
+ for i, close in enumerate(closes):
27
+ vol = base_volume + (i * 1000)
28
+ bars.append(
29
+ make_daily_bar(
30
+ close=close,
31
+ volume=vol,
32
+ date=f"day-{i}",
33
+ )
34
+ )
35
+ return bars
36
+
37
+
38
+ def make_monthly_history(monthly_closes, start_year=2024, start_month=1):
39
+ """
40
+ Create daily history that downsamples to expected monthly points.
41
+
42
+ Each monthly close gets 20 daily bars in its month.
43
+ Monthly_closes should be in chronological order (oldest first).
44
+ Returns bars in most-recent-first order.
45
+ """
46
+ bars = []
47
+ for i, close in enumerate(monthly_closes):
48
+ month = start_month + i
49
+ year = start_year + (month - 1) // 12
50
+ m = ((month - 1) % 12) + 1
51
+
52
+ for day in range(1, 21):
53
+ daily_close = close * (1 + (day - 10) * 0.001)
54
+ date_str = f"{year:04d}-{m:02d}-{day:02d}"
55
+ bars.append(
56
+ make_daily_bar(
57
+ close=daily_close,
58
+ date=date_str,
59
+ volume=1000000,
60
+ )
61
+ )
62
+
63
+ bars.reverse()
64
+ return bars
65
+
66
+
67
+ def make_treasury_rates(spreads, start_year=2024, start_month=1):
68
+ """
69
+ Create treasury rate data from a list of 10Y-2Y spreads.
70
+ """
71
+ entries = []
72
+ for i, spread in enumerate(spreads):
73
+ month = start_month + i
74
+ year = start_year + (month - 1) // 12
75
+ m = ((month - 1) % 12) + 1
76
+
77
+ for day in range(1, 21):
78
+ date_str = f"{year:04d}-{m:02d}-{day:02d}"
79
+ year2 = 4.5 - spread / 2
80
+ year10 = 4.5 + spread / 2
81
+ entries.append(
82
+ {
83
+ "date": date_str,
84
+ "year2": str(round(year2, 2)),
85
+ "year10": str(round(year10, 2)),
86
+ }
87
+ )
88
+
89
+ entries.reverse()
90
+ return entries
@@ -0,0 +1,439 @@
1
+ """Tests for Macro Regime Detector Scorer"""
2
+
3
+ from scorer import (
4
+ calculate_composite_score,
5
+ classify_regime,
6
+ check_regime_consistency,
7
+ COMPONENT_WEIGHTS,
8
+ )
9
+
10
+
11
+ class TestCalculateCompositeScore:
12
+ def test_all_zero_scores(self):
13
+ scores = {
14
+ "concentration": 0,
15
+ "yield_curve": 0,
16
+ "credit_conditions": 0,
17
+ "size_factor": 0,
18
+ "equity_bond": 0,
19
+ "sector_rotation": 0,
20
+ }
21
+ result = calculate_composite_score(scores)
22
+ assert result["composite_score"] == 0.0
23
+ assert "Green" in result["zone"] or "Stable" in result["zone"]
24
+
25
+ def test_all_max_scores(self):
26
+ scores = {
27
+ "concentration": 100,
28
+ "yield_curve": 100,
29
+ "credit_conditions": 100,
30
+ "size_factor": 100,
31
+ "equity_bond": 100,
32
+ "sector_rotation": 100,
33
+ }
34
+ result = calculate_composite_score(scores)
35
+ assert result["composite_score"] == 100.0
36
+
37
+ def test_weighted_calculation(self):
38
+ # Only concentration = 100, rest = 0 -> should be 25%
39
+ scores = {
40
+ "concentration": 100,
41
+ "yield_curve": 0,
42
+ "credit_conditions": 0,
43
+ "size_factor": 0,
44
+ "equity_bond": 0,
45
+ "sector_rotation": 0,
46
+ }
47
+ result = calculate_composite_score(scores)
48
+ assert result["composite_score"] == 25.0
49
+
50
+ def test_data_quality_tracking(self):
51
+ scores = {
52
+ "concentration": 50,
53
+ "yield_curve": 50,
54
+ "credit_conditions": 50,
55
+ "size_factor": 50,
56
+ "equity_bond": 50,
57
+ "sector_rotation": 50,
58
+ }
59
+ availability = {
60
+ "concentration": True,
61
+ "yield_curve": False,
62
+ "credit_conditions": True,
63
+ "size_factor": True,
64
+ "equity_bond": True,
65
+ "sector_rotation": True,
66
+ }
67
+ result = calculate_composite_score(scores, availability)
68
+ assert result["data_quality"]["available_count"] == 5
69
+ assert len(result["data_quality"]["missing_components"]) == 1
70
+
71
+ def test_strongest_weakest_signal(self):
72
+ scores = {
73
+ "concentration": 80,
74
+ "yield_curve": 10,
75
+ "credit_conditions": 50,
76
+ "size_factor": 30,
77
+ "equity_bond": 60,
78
+ "sector_rotation": 40,
79
+ }
80
+ result = calculate_composite_score(scores)
81
+ assert result["strongest_signal"]["component"] == "concentration"
82
+ assert result["weakest_signal"]["component"] == "yield_curve"
83
+
84
+ def test_signaling_count(self):
85
+ scores = {
86
+ "concentration": 50, # signaling (>= 40)
87
+ "yield_curve": 60, # signaling
88
+ "credit_conditions": 10, # not signaling
89
+ "size_factor": 45, # signaling
90
+ "equity_bond": 20, # not signaling
91
+ "sector_rotation": 70, # signaling
92
+ }
93
+ result = calculate_composite_score(scores)
94
+ assert result["signaling_components"] == 4
95
+
96
+ def test_zone_boundaries(self):
97
+ # Test each zone boundary
98
+ for score_val, expected_zone_part in [
99
+ (0, "Stable"),
100
+ (20, "Stable"),
101
+ (21, "Early"),
102
+ (40, "Early"),
103
+ (41, "Transition"),
104
+ (60, "Transition"),
105
+ (61, "Active"),
106
+ (80, "Active"),
107
+ (81, "Confirmed"),
108
+ ]:
109
+ scores = {
110
+ k: score_val
111
+ for k in [
112
+ "concentration",
113
+ "yield_curve",
114
+ "credit_conditions",
115
+ "size_factor",
116
+ "equity_bond",
117
+ "sector_rotation",
118
+ ]
119
+ }
120
+ result = calculate_composite_score(scores)
121
+ assert expected_zone_part in result["zone"], (
122
+ f"Score {score_val}: expected '{expected_zone_part}' in '{result['zone']}'"
123
+ )
124
+
125
+
126
+ class TestClassifyRegime:
127
+ def _make_component(self, score=0, direction="unknown", **kwargs):
128
+ base = {
129
+ "score": score,
130
+ "signal": "test",
131
+ "data_available": True,
132
+ "direction": direction,
133
+ "crossover": {"type": "none", "bars_ago": None},
134
+ }
135
+ base.update(kwargs)
136
+ return base
137
+
138
+ def test_concentration_regime(self):
139
+ components = {
140
+ "concentration": self._make_component(60, "concentrating"),
141
+ "yield_curve": self._make_component(10, "stable"),
142
+ "credit_conditions": self._make_component(10, "stable"),
143
+ "size_factor": self._make_component(50, "large_cap_leading"),
144
+ "equity_bond": self._make_component(10, "neutral"),
145
+ "sector_rotation": self._make_component(10, "neutral"),
146
+ }
147
+ result = classify_regime(components)
148
+ assert result["current_regime"] == "concentration"
149
+
150
+ def test_broadening_regime(self):
151
+ components = {
152
+ "concentration": self._make_component(60, "broadening"),
153
+ "yield_curve": self._make_component(10, "steepening"),
154
+ "credit_conditions": self._make_component(10, "easing"),
155
+ "size_factor": self._make_component(50, "small_cap_leading"),
156
+ "equity_bond": self._make_component(10, "risk_on"),
157
+ "sector_rotation": self._make_component(50, "risk_on"),
158
+ }
159
+ result = classify_regime(components)
160
+ assert result["current_regime"] == "broadening"
161
+
162
+ def test_contraction_regime(self):
163
+ components = {
164
+ "concentration": self._make_component(10, "unknown"),
165
+ "yield_curve": self._make_component(10, "flattening"),
166
+ "credit_conditions": self._make_component(60, "tightening"),
167
+ "size_factor": self._make_component(10, "unknown"),
168
+ "equity_bond": self._make_component(50, "risk_off"),
169
+ "sector_rotation": self._make_component(60, "risk_off"),
170
+ }
171
+ result = classify_regime(components)
172
+ assert result["current_regime"] == "contraction"
173
+
174
+ def test_inflationary_regime(self):
175
+ components = {
176
+ "concentration": self._make_component(10, "unknown"),
177
+ "yield_curve": self._make_component(10, "steepening"),
178
+ "credit_conditions": self._make_component(10, "unknown"),
179
+ "size_factor": self._make_component(10, "unknown"),
180
+ "equity_bond": self._make_component(60, "risk_off", correlation_regime="positive"),
181
+ "sector_rotation": self._make_component(10, "neutral"),
182
+ }
183
+ result = classify_regime(components)
184
+ assert result["current_regime"] == "inflationary"
185
+
186
+ def test_transitional_regime(self):
187
+ # Many components signaling but no clear pattern
188
+ components = {
189
+ "concentration": self._make_component(50, "broadening"),
190
+ "yield_curve": self._make_component(50, "steepening"),
191
+ "credit_conditions": self._make_component(50, "tightening"),
192
+ "size_factor": self._make_component(50, "large_cap_leading"),
193
+ "equity_bond": self._make_component(50, "risk_off"),
194
+ "sector_rotation": self._make_component(50, "risk_off"),
195
+ }
196
+ result = classify_regime(components)
197
+ # With mixed signals, should be transitional or the strongest match
198
+ assert result["current_regime"] in (
199
+ "transitional",
200
+ "contraction",
201
+ "broadening",
202
+ "concentration",
203
+ "inflationary",
204
+ )
205
+
206
+ def test_confidence_levels(self):
207
+ # High confidence: many matching signals
208
+ components = {
209
+ "concentration": self._make_component(60, "concentrating"),
210
+ "yield_curve": self._make_component(10, "stable"),
211
+ "credit_conditions": self._make_component(10, "stable"),
212
+ "size_factor": self._make_component(50, "large_cap_leading"),
213
+ "equity_bond": self._make_component(10, "neutral"),
214
+ "sector_rotation": self._make_component(10, "neutral"),
215
+ }
216
+ result = classify_regime(components)
217
+ assert result["confidence"] in ("high", "moderate", "low", "very_low")
218
+
219
+ def test_transition_probability(self):
220
+ components = {
221
+ "concentration": self._make_component(60, "broadening"),
222
+ "yield_curve": self._make_component(50, "steepening"),
223
+ "credit_conditions": self._make_component(45, "easing"),
224
+ "size_factor": self._make_component(55, "small_cap_leading"),
225
+ "equity_bond": self._make_component(40, "risk_on"),
226
+ "sector_rotation": self._make_component(50, "risk_on"),
227
+ }
228
+ result = classify_regime(components)
229
+ tp = result["transition_probability"]
230
+ assert "level" in tp
231
+ assert "probability_range" in tp
232
+ assert "signaling_count" in tp
233
+ assert tp["level"] in ("high", "moderate", "low", "minimal")
234
+
235
+ def test_evidence_list(self):
236
+ components = {
237
+ "concentration": self._make_component(60, "broadening"),
238
+ "yield_curve": self._make_component(50, "steepening"),
239
+ "credit_conditions": self._make_component(10, "stable"),
240
+ "size_factor": self._make_component(10, "neutral"),
241
+ "equity_bond": self._make_component(10, "neutral"),
242
+ "sector_rotation": self._make_component(10, "neutral"),
243
+ }
244
+ result = classify_regime(components)
245
+ assert len(result["evidence"]) == 2 # Only 2 components >= 40
246
+ # Sorted by score descending
247
+ if len(result["evidence"]) >= 2:
248
+ assert result["evidence"][0]["score"] >= result["evidence"][1]["score"]
249
+
250
+ def test_unknown_credit_no_concentration_bias(self):
251
+ """Missing credit data should NOT bias toward Concentration regime."""
252
+ # All components with data_available=True but direction="unknown"
253
+ # means data exists but direction is unclear
254
+ components = {
255
+ "concentration": self._make_component(10, "unknown"),
256
+ "yield_curve": self._make_component(10, "stable"),
257
+ "credit_conditions": self._make_component(0, "unknown", data_available=False),
258
+ "size_factor": self._make_component(10, "unknown"),
259
+ "equity_bond": self._make_component(10, "neutral"),
260
+ "sector_rotation": self._make_component(10, "neutral"),
261
+ }
262
+ result = classify_regime(components)
263
+ # With no real signals, concentration should NOT get bonus from unknown credit
264
+ assert result["regime_scores"]["concentration"] == 0
265
+
266
+ def test_missing_data_caps_confidence(self):
267
+ """With 3 or fewer available components, confidence capped at very_low."""
268
+ components = {
269
+ "concentration": self._make_component(60, "concentrating"),
270
+ "yield_curve": self._make_component(0, "unknown", data_available=False),
271
+ "credit_conditions": self._make_component(0, "unknown", data_available=False),
272
+ "size_factor": self._make_component(60, "large_cap_leading"),
273
+ "equity_bond": self._make_component(0, "unknown", data_available=False),
274
+ "sector_rotation": self._make_component(0, "unknown", data_available=False),
275
+ }
276
+ result = classify_regime(components)
277
+ # Only 2 components available -> must be very_low confidence
278
+ assert result["confidence"] == "very_low"
279
+
280
+ def test_four_components_caps_at_low(self):
281
+ """With 4 available components, confidence capped at low."""
282
+ components = {
283
+ "concentration": self._make_component(60, "concentrating"),
284
+ "yield_curve": self._make_component(50, "steepening"),
285
+ "credit_conditions": self._make_component(0, "unknown", data_available=False),
286
+ "size_factor": self._make_component(60, "large_cap_leading"),
287
+ "equity_bond": self._make_component(50, "risk_on"),
288
+ "sector_rotation": self._make_component(0, "unknown", data_available=False),
289
+ }
290
+ result = classify_regime(components)
291
+ assert result["confidence"] in ("low", "very_low")
292
+
293
+ def test_unavailable_component_direction_ignored(self):
294
+ """Components with data_available=False should have direction treated as unknown."""
295
+ # Credit has direction="tightening" but data_available=False -> should be ignored
296
+ components = {
297
+ "concentration": self._make_component(10, "unknown"),
298
+ "yield_curve": self._make_component(10, "stable"),
299
+ "credit_conditions": self._make_component(60, "tightening", data_available=False),
300
+ "size_factor": self._make_component(10, "neutral"),
301
+ "equity_bond": self._make_component(10, "neutral"),
302
+ "sector_rotation": self._make_component(10, "neutral"),
303
+ }
304
+ result = classify_regime(components)
305
+ # Contraction should NOT score from unavailable credit data
306
+ assert result["regime_scores"]["contraction"] == 0
307
+
308
+ # --- Improvement 1: Tiebreak → Transitional ---
309
+
310
+ def test_tiebreak_broadening_contraction_becomes_transitional(self):
311
+ """Tied broadening=3, contraction=3, low composite → transitional."""
312
+ # broadening: conc broadening(+2) + credit easing(+1) = 3
313
+ # contraction: sector risk_off(+2) + eb risk_off(+1) = 3
314
+ # concentration: size large_cap? no, size=unknown -> 0
315
+ # inflationary: corr_regime unknown -> 0
316
+ components = {
317
+ "concentration": self._make_component(20, "broadening"),
318
+ "yield_curve": self._make_component(10, "stable"),
319
+ "credit_conditions": self._make_component(20, "easing"),
320
+ "size_factor": self._make_component(20, "unknown"),
321
+ "equity_bond": self._make_component(20, "risk_off"),
322
+ "sector_rotation": self._make_component(20, "risk_off"),
323
+ }
324
+ result = classify_regime(components)
325
+ # Low composite (all scores ~20) < 50 → tied → transitional
326
+ assert result["current_regime"] == "transitional"
327
+ assert result.get("tied_regimes") is not None
328
+ assert set(result["tied_regimes"]) == {"broadening", "contraction"}
329
+
330
+ def test_tiebreak_keeps_winner_high_composite(self):
331
+ """Tied but composite >= 50 → keeps top scorer (not forced transitional)."""
332
+ # Same tie but high individual scores
333
+ # broadening: conc broadening(+2) + credit easing(+1) = 3
334
+ # contraction: sector risk_off(+2) + eb risk_off(+1) = 3
335
+ components = {
336
+ "concentration": self._make_component(70, "broadening"),
337
+ "yield_curve": self._make_component(60, "stable"),
338
+ "credit_conditions": self._make_component(60, "easing"),
339
+ "size_factor": self._make_component(50, "unknown"),
340
+ "equity_bond": self._make_component(50, "risk_off"),
341
+ "sector_rotation": self._make_component(50, "risk_off"),
342
+ }
343
+ result = classify_regime(components)
344
+ # Composite >= 50: even though tied, keep top scorer
345
+ assert result["current_regime"] in ("broadening", "contraction")
346
+ # tied_regimes should still be reported
347
+ assert result.get("tied_regimes") is not None
348
+
349
+ def test_no_tied_regimes_clear_winner(self):
350
+ """Clear winner → tied_regimes is None."""
351
+ components = {
352
+ "concentration": self._make_component(60, "concentrating"),
353
+ "yield_curve": self._make_component(10, "stable"),
354
+ "credit_conditions": self._make_component(10, "stable"),
355
+ "size_factor": self._make_component(50, "large_cap_leading"),
356
+ "equity_bond": self._make_component(10, "neutral"),
357
+ "sector_rotation": self._make_component(10, "neutral"),
358
+ }
359
+ result = classify_regime(components)
360
+ assert result["current_regime"] == "concentration"
361
+ assert result.get("tied_regimes") is None
362
+
363
+ # --- Improvement 3: Transition Direction ---
364
+
365
+ def test_transition_direction_present(self):
366
+ """from_regime and to_regime present when transition probability is high/moderate."""
367
+ components = {
368
+ "concentration": self._make_component(60, "broadening"),
369
+ "yield_curve": self._make_component(50, "steepening"),
370
+ "credit_conditions": self._make_component(45, "easing"),
371
+ "size_factor": self._make_component(55, "small_cap_leading"),
372
+ "equity_bond": self._make_component(40, "risk_on"),
373
+ "sector_rotation": self._make_component(50, "risk_on"),
374
+ }
375
+ result = classify_regime(components)
376
+ tp = result["transition_probability"]
377
+ assert "from_regime" in tp
378
+ assert "to_regime" in tp
379
+
380
+ def test_transition_direction_none_when_minimal(self):
381
+ """from/to are None when transition probability is minimal."""
382
+ components = {
383
+ "concentration": self._make_component(10, "unknown"),
384
+ "yield_curve": self._make_component(5, "stable"),
385
+ "credit_conditions": self._make_component(5, "stable"),
386
+ "size_factor": self._make_component(5, "unknown"),
387
+ "equity_bond": self._make_component(5, "neutral"),
388
+ "sector_rotation": self._make_component(5, "neutral"),
389
+ }
390
+ result = classify_regime(components)
391
+ tp = result["transition_probability"]
392
+ assert tp["from_regime"] is None
393
+ assert tp["to_regime"] is None
394
+
395
+ # --- Improvement 4: Regime Consistency ---
396
+
397
+ def test_check_regime_consistency_broadening(self):
398
+ """Broadening regime with matching components → all consistent."""
399
+ components = {
400
+ "concentration": self._make_component(60, "broadening"),
401
+ "yield_curve": self._make_component(10, "stable"),
402
+ "credit_conditions": self._make_component(20, "easing"),
403
+ "size_factor": self._make_component(50, "small_cap_leading"),
404
+ "equity_bond": self._make_component(30, "risk_on"),
405
+ "sector_rotation": self._make_component(40, "risk_on"),
406
+ }
407
+ result = check_regime_consistency("broadening", components)
408
+ assert result["concentration"] == "consistent"
409
+ assert result["size_factor"] == "consistent"
410
+ assert result["sector_rotation"] == "consistent"
411
+
412
+ def test_check_regime_consistency_contradicting(self):
413
+ """Broadening regime but some components contradicting."""
414
+ components = {
415
+ "concentration": self._make_component(60, "concentrating"), # contradicts
416
+ "yield_curve": self._make_component(10, "stable"),
417
+ "credit_conditions": self._make_component(20, "tightening"), # contradicts
418
+ "size_factor": self._make_component(50, "large_cap_leading"), # contradicts
419
+ "equity_bond": self._make_component(30, "risk_off"), # contradicts
420
+ "sector_rotation": self._make_component(40, "risk_off"), # contradicts
421
+ }
422
+ result = check_regime_consistency("broadening", components)
423
+ assert result["concentration"] == "contradicting"
424
+ assert result["size_factor"] == "contradicting"
425
+ assert result["sector_rotation"] == "contradicting"
426
+
427
+ def test_check_regime_consistency_transitional(self):
428
+ """Transitional regime → all neutral (no expectations)."""
429
+ components = {
430
+ "concentration": self._make_component(60, "broadening"),
431
+ "yield_curve": self._make_component(10, "stable"),
432
+ "credit_conditions": self._make_component(20, "tightening"),
433
+ "size_factor": self._make_component(50, "small_cap_leading"),
434
+ "equity_bond": self._make_component(30, "risk_on"),
435
+ "sector_rotation": self._make_component(40, "risk_on"),
436
+ }
437
+ result = check_regime_consistency("transitional", components)
438
+ for v in result.values():
439
+ assert v == "neutral"