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,347 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Shared utility functions for macro regime calculators.
4
+
5
+ Provides monthly downsampling, ratio calculation, moving averages,
6
+ crossover detection, momentum computation, and transition scoring.
7
+ """
8
+
9
+ from typing import Dict, List, Optional, Tuple
10
+ from datetime import datetime
11
+
12
+
13
+ def downsample_to_monthly(daily_history: List[Dict]) -> List[Dict]:
14
+ """
15
+ Downsample daily OHLCV to monthly (last business day of each month).
16
+
17
+ Args:
18
+ daily_history: Daily bars, most recent first.
19
+
20
+ Returns:
21
+ List of monthly bars (most recent first), each with 'date', 'close'.
22
+ """
23
+ if not daily_history:
24
+ return []
25
+
26
+ # Group by year-month, pick the most recent bar per month
27
+ monthly = {}
28
+ for bar in daily_history:
29
+ date_str = bar.get("date", "")
30
+ close = bar.get("adjClose", bar.get("close", 0))
31
+ if not date_str or close == 0:
32
+ continue
33
+
34
+ # Extract year-month key
35
+ ym = date_str[:7] # "YYYY-MM"
36
+ if ym not in monthly:
37
+ monthly[ym] = {"date": date_str, "close": close}
38
+ else:
39
+ # daily_history is most recent first, so first occurrence is the latest in that month
40
+ # Keep the first (most recent) bar for each month
41
+ pass
42
+
43
+ # Sort by date descending (most recent first)
44
+ result = sorted(monthly.values(), key=lambda x: x["date"], reverse=True)
45
+ return result
46
+
47
+
48
+ def calculate_ratio(numerator_monthly: List[Dict],
49
+ denominator_monthly: List[Dict]) -> List[Dict]:
50
+ """
51
+ Calculate ratio of two monthly series aligned by date.
52
+
53
+ Args:
54
+ numerator_monthly: Monthly bars for numerator (most recent first)
55
+ denominator_monthly: Monthly bars for denominator (most recent first)
56
+
57
+ Returns:
58
+ List of {'date': str, 'value': float} (most recent first)
59
+ """
60
+ # Build lookup by year-month
61
+ denom_lookup = {}
62
+ for bar in denominator_monthly:
63
+ ym = bar["date"][:7]
64
+ denom_lookup[ym] = bar["close"]
65
+
66
+ result = []
67
+ for bar in numerator_monthly:
68
+ ym = bar["date"][:7]
69
+ if ym in denom_lookup and denom_lookup[ym] != 0:
70
+ ratio = bar["close"] / denom_lookup[ym]
71
+ result.append({"date": bar["date"], "value": ratio})
72
+
73
+ return result
74
+
75
+
76
+ def compute_sma(values: List[float], period: int) -> Optional[float]:
77
+ """
78
+ Compute Simple Moving Average from a list of values (most recent first).
79
+
80
+ Returns SMA of the most recent `period` values, or None if insufficient data.
81
+ """
82
+ if len(values) < period:
83
+ return None
84
+ return sum(values[:period]) / period
85
+
86
+
87
+ def detect_crossover(values: List[float], short_period: int = 6,
88
+ long_period: int = 12) -> Dict:
89
+ """
90
+ Detect SMA crossover between short and long periods.
91
+
92
+ Args:
93
+ values: Series of values (most recent first)
94
+ short_period: Short-term SMA period (default 6)
95
+ long_period: Long-term SMA period (default 12)
96
+
97
+ Returns:
98
+ Dict with 'type' ('golden_cross', 'death_cross', 'converging', 'none'),
99
+ 'bars_ago' (how many months ago the crossover occurred),
100
+ 'gap_pct' (current gap between short and long SMA as %)
101
+ """
102
+ if len(values) < long_period + 3:
103
+ return {"type": "none", "bars_ago": None, "gap_pct": None}
104
+
105
+ # Compute SMAs at each point
106
+ max_lookback = min(len(values), long_period + 12) # Check up to 12 months back
107
+ sma_pairs = []
108
+ for offset in range(max_lookback - long_period + 1):
109
+ subset = values[offset:]
110
+ short_sma = compute_sma(subset, short_period)
111
+ long_sma = compute_sma(subset, long_period)
112
+ if short_sma is not None and long_sma is not None:
113
+ sma_pairs.append((short_sma, long_sma))
114
+
115
+ if len(sma_pairs) < 2:
116
+ return {"type": "none", "bars_ago": None, "gap_pct": None}
117
+
118
+ # Current gap
119
+ current_short, current_long = sma_pairs[0]
120
+ if current_long != 0:
121
+ gap_pct = (current_short - current_long) / current_long * 100
122
+ else:
123
+ gap_pct = 0
124
+
125
+ # Look for crossover
126
+ for i in range(1, len(sma_pairs)):
127
+ prev_short, prev_long = sma_pairs[i]
128
+ curr_short, curr_long = sma_pairs[i - 1]
129
+
130
+ # Golden cross: short crosses above long
131
+ if prev_short <= prev_long and curr_short > curr_long:
132
+ return {
133
+ "type": "golden_cross",
134
+ "bars_ago": i - 1,
135
+ "gap_pct": round(gap_pct, 3),
136
+ }
137
+ # Death cross: short crosses below long
138
+ if prev_short >= prev_long and curr_short < curr_long:
139
+ return {
140
+ "type": "death_cross",
141
+ "bars_ago": i - 1,
142
+ "gap_pct": round(gap_pct, 3),
143
+ }
144
+
145
+ # No crossover found, check if converging
146
+ if abs(gap_pct) < 1.0:
147
+ return {
148
+ "type": "converging",
149
+ "bars_ago": None,
150
+ "gap_pct": round(gap_pct, 3),
151
+ }
152
+
153
+ return {
154
+ "type": "none",
155
+ "bars_ago": None,
156
+ "gap_pct": round(gap_pct, 3),
157
+ }
158
+
159
+
160
+ def compute_roc(values: List[float], period: int) -> Optional[float]:
161
+ """
162
+ Compute Rate of Change (%) over `period` data points.
163
+
164
+ Args:
165
+ values: Series (most recent first)
166
+ period: Number of periods back to compare
167
+
168
+ Returns:
169
+ ROC as percentage, or None if insufficient data
170
+ """
171
+ if len(values) <= period:
172
+ return None
173
+ current = values[0]
174
+ past = values[period]
175
+ if past == 0:
176
+ return None
177
+ return (current - past) / past * 100
178
+
179
+
180
+ def compute_percentile(values: List[float], current: float) -> Optional[float]:
181
+ """
182
+ Compute percentile rank of current value within the series.
183
+
184
+ Returns percentile (0-100), or None if insufficient data.
185
+ """
186
+ if not values:
187
+ return None
188
+ below = sum(1 for v in values if v < current)
189
+ return below / len(values) * 100
190
+
191
+
192
+ def compute_rolling_correlation(series_a: List[float], series_b: List[float],
193
+ window: int) -> Optional[float]:
194
+ """
195
+ Compute rolling Pearson correlation between two series over a window.
196
+
197
+ Args:
198
+ series_a: First series (most recent first)
199
+ series_b: Second series (most recent first)
200
+ window: Number of data points for correlation window
201
+
202
+ Returns:
203
+ Correlation coefficient (-1 to 1), or None if insufficient data
204
+ """
205
+ if len(series_a) < window or len(series_b) < window:
206
+ return None
207
+
208
+ a = series_a[:window]
209
+ b = series_b[:window]
210
+
211
+ n = window
212
+ mean_a = sum(a) / n
213
+ mean_b = sum(b) / n
214
+
215
+ cov = sum((a[i] - mean_a) * (b[i] - mean_b) for i in range(n)) / n
216
+ std_a = (sum((x - mean_a) ** 2 for x in a) / n) ** 0.5
217
+ std_b = (sum((x - mean_b) ** 2 for x in b) / n) ** 0.5
218
+
219
+ if std_a == 0 or std_b == 0:
220
+ return 0.0
221
+
222
+ return cov / (std_a * std_b)
223
+
224
+
225
+ STALE_CROSSOVER_MONTHS = 3
226
+
227
+
228
+ def determine_direction(crossover: Dict, roc_3m: Optional[float],
229
+ positive_label: str, negative_label: str,
230
+ neutral_label: str = "neutral") -> Tuple[str, str]:
231
+ """
232
+ Determine direction from crossover and momentum, accounting for stale crossovers.
233
+
234
+ When a crossover is old (>= STALE_CROSSOVER_MONTHS) and momentum contradicts it,
235
+ momentum takes priority ("reversing"). Recent crossovers always win.
236
+
237
+ Args:
238
+ crossover: Dict with 'type' and 'bars_ago'
239
+ roc_3m: 3-month rate of change (%)
240
+ positive_label: Label for positive direction (e.g., "risk_on")
241
+ negative_label: Label for negative direction (e.g., "risk_off")
242
+ neutral_label: Label when no signal (default "neutral")
243
+
244
+ Returns:
245
+ Tuple of (direction, momentum_qualifier)
246
+ momentum_qualifier: "confirmed" | "fading" | "reversing" | "N/A"
247
+ """
248
+ cross_type = crossover.get("type", "none")
249
+ bars_ago = crossover.get("bars_ago")
250
+ is_stale = bars_ago is not None and bars_ago >= STALE_CROSSOVER_MONTHS
251
+
252
+ # Crossover direction
253
+ cross_dir = (positive_label if cross_type == "golden_cross"
254
+ else negative_label if cross_type == "death_cross"
255
+ else None)
256
+
257
+ # Momentum direction
258
+ mom_dir = (positive_label if roc_3m is not None and roc_3m > 0
259
+ else negative_label if roc_3m is not None and roc_3m < 0
260
+ else None)
261
+
262
+ if cross_dir:
263
+ if is_stale and mom_dir and mom_dir != cross_dir:
264
+ return mom_dir, "reversing"
265
+ qualifier = ("confirmed" if mom_dir == cross_dir
266
+ else "fading" if mom_dir and mom_dir != cross_dir
267
+ else "N/A")
268
+ return cross_dir, qualifier
269
+ elif mom_dir:
270
+ return mom_dir, "N/A"
271
+ else:
272
+ return neutral_label, "N/A"
273
+
274
+
275
+ def score_transition_signal(crossover: Dict,
276
+ roc_short: Optional[float],
277
+ roc_long: Optional[float],
278
+ sma_short: Optional[float],
279
+ sma_long: Optional[float]) -> int:
280
+ """
281
+ Score transition signal strength (0-100) from crossover, momentum, and MA data.
282
+
283
+ Scoring layers:
284
+ 1. MA Crossover (0-40 base): Recent crossover = high, converging = moderate
285
+ 2. Momentum Shift (0-30): Short ROC reversal against long trend = early warning
286
+ 3. Confirmation (0-30): Multiple signals aligning = strong confirmation
287
+
288
+ Returns:
289
+ int score 0-100
290
+ """
291
+ score = 0
292
+
293
+ # Layer 1: Crossover detection (0-40)
294
+ cross_type = crossover.get("type", "none")
295
+ bars_ago = crossover.get("bars_ago")
296
+ gap_pct = crossover.get("gap_pct", 0) or 0
297
+
298
+ if cross_type in ("golden_cross", "death_cross"):
299
+ if bars_ago is not None and bars_ago <= 2:
300
+ score += 40 # Very recent crossover
301
+ elif bars_ago is not None and bars_ago <= 5:
302
+ score += 30 # Recent crossover
303
+ else:
304
+ score += 20 # Older crossover
305
+ elif cross_type == "converging":
306
+ # MAs getting close, crossover possible
307
+ closeness = max(0, 1.0 - abs(gap_pct)) * 25
308
+ score += int(closeness)
309
+ # "none" = stable, no points
310
+
311
+ # Layer 2: Momentum shift (0-30)
312
+ if roc_short is not None and roc_long is not None:
313
+ # Reversal: short-term momentum opposite to long-term trend
314
+ if roc_long < 0 and roc_short > 0:
315
+ # Declining long-term but short-term bouncing = early reversal
316
+ strength = min(abs(roc_short), 5.0) / 5.0 * 30
317
+ score += int(strength)
318
+ elif roc_long > 0 and roc_short < 0:
319
+ # Rising long-term but short-term declining = early reversal
320
+ strength = min(abs(roc_short), 5.0) / 5.0 * 30
321
+ score += int(strength)
322
+ elif abs(roc_short) > 3.0:
323
+ # Strong short-term momentum in same direction = acceleration
324
+ score += 10
325
+
326
+ # Layer 3: Confirmation / alignment (0-30)
327
+ signals_aligned = 0
328
+
329
+ # Signal 1: Crossover present
330
+ if cross_type in ("golden_cross", "death_cross"):
331
+ signals_aligned += 1
332
+
333
+ # Signal 2: Short ROC confirms direction
334
+ if cross_type == "golden_cross" and roc_short is not None and roc_short > 0:
335
+ signals_aligned += 1
336
+ elif cross_type == "death_cross" and roc_short is not None and roc_short < 0:
337
+ signals_aligned += 1
338
+
339
+ # Signal 3: SMA gap widening (momentum)
340
+ if sma_short is not None and sma_long is not None and sma_long != 0:
341
+ current_gap = abs(sma_short - sma_long) / sma_long * 100
342
+ if current_gap > 0.5:
343
+ signals_aligned += 1
344
+
345
+ score += signals_aligned * 10
346
+
347
+ return min(100, max(0, score))
@@ -0,0 +1,279 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Component 2: Yield Curve (Weight: 20%)
4
+
5
+ Analyzes 10Y-2Y Treasury spread to detect interest rate cycle transitions.
6
+
7
+ Primary data: Treasury API (10Y-2Y spread directly)
8
+ Fallback: SHY/TLT ratio as proxy for yield curve shape
9
+
10
+ Transition signals:
11
+ - Inversion → Normalization: Often precedes recession end, risk-on shift
12
+ - Steepening from flat: Economic recovery signal
13
+ - Flattening from steep: Late-cycle tightening signal
14
+ - Deep inversion: Recession warning
15
+
16
+ Scoring (0-100 = Transition Signal Strength):
17
+ 0-20: Stable yield curve regime
18
+ 20-40: Minor changes in spread
19
+ 40-60: Transition zone (curve shape changing)
20
+ 60-80: Clear transition (inversion/normalization crossover)
21
+ 80-100: Strong confirmed transition
22
+ """
23
+
24
+ from typing import Dict, List, Optional
25
+ from .utils import (
26
+ downsample_to_monthly,
27
+ calculate_ratio,
28
+ compute_sma,
29
+ detect_crossover,
30
+ compute_roc,
31
+ compute_percentile,
32
+ score_transition_signal,
33
+ )
34
+
35
+
36
+ def calculate_yield_curve(treasury_rates: Optional[List[Dict]] = None,
37
+ shy_history: Optional[List[Dict]] = None,
38
+ tlt_history: Optional[List[Dict]] = None) -> Dict:
39
+ """
40
+ Calculate yield curve transition signal.
41
+
42
+ Args:
43
+ treasury_rates: Treasury rate data from FMP stable API (most recent first)
44
+ shy_history: SHY daily OHLCV (fallback, most recent first)
45
+ tlt_history: TLT daily OHLCV (fallback, most recent first)
46
+
47
+ Returns:
48
+ Dict with score, signal, spread details, curve state
49
+ """
50
+ # Try Treasury API first
51
+ if treasury_rates:
52
+ result = _analyze_treasury_spread(treasury_rates)
53
+ if result is not None:
54
+ return result
55
+
56
+ # Fallback to SHY/TLT ratio
57
+ if shy_history and tlt_history:
58
+ return _analyze_shy_tlt_proxy(shy_history, tlt_history)
59
+
60
+ return _insufficient_data("No treasury rates or SHY/TLT data available")
61
+
62
+
63
+ def _analyze_treasury_spread(treasury_rates: List[Dict]) -> Optional[Dict]:
64
+ """Analyze 10Y-2Y spread from Treasury API data."""
65
+ # Extract 10Y-2Y spread series
66
+ spread_monthly = {}
67
+ for entry in treasury_rates:
68
+ date_str = entry.get("date", "")
69
+ year10 = entry.get("year10")
70
+ year2 = entry.get("year2")
71
+
72
+ if not date_str or year10 is None or year2 is None:
73
+ continue
74
+
75
+ try:
76
+ y10 = float(year10)
77
+ y2 = float(year2)
78
+ except (ValueError, TypeError):
79
+ continue
80
+
81
+ ym = date_str[:7]
82
+ if ym not in spread_monthly:
83
+ spread_monthly[ym] = {
84
+ "date": date_str,
85
+ "spread": y10 - y2,
86
+ "year10": y10,
87
+ "year2": y2,
88
+ }
89
+
90
+ if len(spread_monthly) < 12:
91
+ return None
92
+
93
+ # Sort most recent first
94
+ spread_series = sorted(spread_monthly.values(), key=lambda x: x["date"], reverse=True)
95
+ spread_values = [s["spread"] for s in spread_series]
96
+
97
+ current_spread = spread_values[0]
98
+ current_date = spread_series[0]["date"]
99
+ current_10y = spread_series[0]["year10"]
100
+ current_2y = spread_series[0]["year2"]
101
+
102
+ # Compute SMAs
103
+ sma_6m = compute_sma(spread_values, 6)
104
+ sma_12m = compute_sma(spread_values, 12)
105
+
106
+ # Crossover detection
107
+ crossover = detect_crossover(spread_values, short_period=6, long_period=12)
108
+
109
+ # Momentum
110
+ roc_3m = compute_roc(spread_values, 3)
111
+ roc_12m = compute_roc(spread_values, 12)
112
+
113
+ # Percentile
114
+ percentile = compute_percentile(spread_values, current_spread)
115
+
116
+ # Score
117
+ score = score_transition_signal(
118
+ crossover=crossover,
119
+ roc_short=roc_3m,
120
+ roc_long=roc_12m,
121
+ sma_short=sma_6m,
122
+ sma_long=sma_12m,
123
+ )
124
+
125
+ # Curve state
126
+ curve_state = _classify_curve_state(current_spread, sma_6m, roc_3m)
127
+
128
+ # Direction of transition
129
+ if roc_3m is not None and roc_3m > 0:
130
+ direction = "steepening"
131
+ elif roc_3m is not None and roc_3m < 0:
132
+ direction = "flattening"
133
+ else:
134
+ direction = "stable"
135
+
136
+ signal = _describe_signal(score, curve_state, current_spread, direction)
137
+
138
+ return {
139
+ "score": score,
140
+ "signal": signal,
141
+ "data_available": True,
142
+ "data_source": "treasury_api",
143
+ "direction": direction,
144
+ "curve_state": curve_state,
145
+ "current_spread": round(current_spread, 3),
146
+ "current_10y": current_10y,
147
+ "current_2y": current_2y,
148
+ "current_date": current_date,
149
+ "sma_6m": round(sma_6m, 3) if sma_6m is not None else None,
150
+ "sma_12m": round(sma_12m, 3) if sma_12m is not None else None,
151
+ "roc_3m": round(roc_3m, 2) if roc_3m is not None else None,
152
+ "roc_12m": round(roc_12m, 2) if roc_12m is not None else None,
153
+ "percentile": round(percentile, 1) if percentile is not None else None,
154
+ "crossover": crossover,
155
+ "monthly_points": len(spread_series),
156
+ }
157
+
158
+
159
+ def _analyze_shy_tlt_proxy(shy_history: List[Dict],
160
+ tlt_history: List[Dict]) -> Dict:
161
+ """Fallback: Use SHY/TLT ratio as yield curve proxy."""
162
+ shy_monthly = downsample_to_monthly(shy_history)
163
+ tlt_monthly = downsample_to_monthly(tlt_history)
164
+
165
+ if len(shy_monthly) < 12 or len(tlt_monthly) < 12:
166
+ return _insufficient_data("Insufficient SHY/TLT monthly data")
167
+
168
+ # SHY/TLT ratio: rising = curve flattening/inverting, falling = steepening
169
+ ratio_series = calculate_ratio(shy_monthly, tlt_monthly)
170
+ if len(ratio_series) < 12:
171
+ return _insufficient_data("Insufficient SHY/TLT ratio data")
172
+
173
+ ratio_values = [r["value"] for r in ratio_series]
174
+ current_ratio = ratio_values[0]
175
+ current_date = ratio_series[0]["date"]
176
+
177
+ sma_6m = compute_sma(ratio_values, 6)
178
+ sma_12m = compute_sma(ratio_values, 12)
179
+ crossover = detect_crossover(ratio_values, short_period=6, long_period=12)
180
+ roc_3m = compute_roc(ratio_values, 3)
181
+ roc_12m = compute_roc(ratio_values, 12)
182
+ percentile = compute_percentile(ratio_values, current_ratio)
183
+
184
+ score = score_transition_signal(
185
+ crossover=crossover,
186
+ roc_short=roc_3m,
187
+ roc_long=roc_12m,
188
+ sma_short=sma_6m,
189
+ sma_long=sma_12m,
190
+ )
191
+
192
+ # For SHY/TLT: rising ratio = flattening, falling = steepening
193
+ if roc_3m is not None and roc_3m < 0:
194
+ direction = "steepening"
195
+ elif roc_3m is not None and roc_3m > 0:
196
+ direction = "flattening"
197
+ else:
198
+ direction = "stable"
199
+
200
+ signal = f"PROXY (SHY/TLT): {direction} signal, score={score}"
201
+
202
+ return {
203
+ "score": score,
204
+ "signal": signal,
205
+ "data_available": True,
206
+ "data_source": "shy_tlt_proxy",
207
+ "direction": direction,
208
+ "curve_state": "proxy_only",
209
+ "current_spread": None,
210
+ "current_10y": None,
211
+ "current_2y": None,
212
+ "current_date": current_date,
213
+ "proxy_ratio": round(current_ratio, 4),
214
+ "sma_6m": round(sma_6m, 4) if sma_6m is not None else None,
215
+ "sma_12m": round(sma_12m, 4) if sma_12m is not None else None,
216
+ "roc_3m": round(roc_3m, 2) if roc_3m is not None else None,
217
+ "roc_12m": round(roc_12m, 2) if roc_12m is not None else None,
218
+ "percentile": round(percentile, 1) if percentile is not None else None,
219
+ "crossover": crossover,
220
+ "monthly_points": len(ratio_series),
221
+ }
222
+
223
+
224
+ def _classify_curve_state(spread: float, sma_6m: Optional[float],
225
+ roc_3m: Optional[float]) -> str:
226
+ """Classify current yield curve state."""
227
+ if spread < -0.5:
228
+ return "deeply_inverted"
229
+ elif spread < 0:
230
+ return "inverted"
231
+ elif spread < 0.5:
232
+ if roc_3m is not None and roc_3m > 0:
233
+ return "normalizing"
234
+ return "flat"
235
+ elif spread < 1.5:
236
+ return "normal"
237
+ else:
238
+ return "steep"
239
+
240
+
241
+ def _describe_signal(score: int, state: str, spread: float, direction: str) -> str:
242
+ state_labels = {
243
+ "deeply_inverted": "Deeply Inverted",
244
+ "inverted": "Inverted",
245
+ "normalizing": "Normalizing",
246
+ "flat": "Flat",
247
+ "normal": "Normal",
248
+ "steep": "Steep",
249
+ }
250
+ state_label = state_labels.get(state, state)
251
+
252
+ if score >= 60:
253
+ return f"TRANSITION: Yield curve {direction} ({state_label}, spread={spread:+.3f}%)"
254
+ elif score >= 40:
255
+ return f"SHIFTING: Yield curve {direction} ({state_label}, spread={spread:+.3f}%)"
256
+ else:
257
+ return f"STABLE: Yield curve {state_label} (spread={spread:+.3f}%)"
258
+
259
+
260
+ def _insufficient_data(reason: str) -> Dict:
261
+ return {
262
+ "score": 0,
263
+ "signal": f"INSUFFICIENT DATA: {reason}",
264
+ "data_available": False,
265
+ "data_source": "none",
266
+ "direction": "unknown",
267
+ "curve_state": "unknown",
268
+ "current_spread": None,
269
+ "current_10y": None,
270
+ "current_2y": None,
271
+ "current_date": None,
272
+ "sma_6m": None,
273
+ "sma_12m": None,
274
+ "roc_3m": None,
275
+ "roc_12m": None,
276
+ "percentile": None,
277
+ "crossover": {"type": "none", "bars_ago": None},
278
+ "monthly_points": 0,
279
+ }