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,473 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Market Top Detector - Composite Scoring Engine
4
+
5
+ Combines 6 component scores into a weighted composite (0-100).
6
+
7
+ Component Weights:
8
+ 1. Distribution Day Count: 25%
9
+ 2. Leading Stock Health: 20%
10
+ 3. Defensive Sector Rotation: 15%
11
+ 4. Market Breadth Divergence: 15%
12
+ 5. Index Technical Condition: 15%
13
+ 6. Sentiment & Speculation: 10%
14
+ Total: 100%
15
+
16
+ Risk Zone Mapping:
17
+ 0-20: Green (Normal) - Risk Budget: 100%
18
+ 21-40: Yellow (Early Warning) - Risk Budget: 80-90%
19
+ 41-60: Orange (Elevated Risk) - Risk Budget: 60-75%
20
+ 61-80: Red (High Probability Top)- Risk Budget: 40-55%
21
+ 81-100:Critical(Top Formation) - Risk Budget: 20-35%
22
+ """
23
+
24
+ from typing import Dict, List, Optional
25
+
26
+
27
+ COMPONENT_WEIGHTS = {
28
+ "distribution_days": 0.25,
29
+ "leading_stocks": 0.20,
30
+ "defensive_rotation": 0.15,
31
+ "breadth_divergence": 0.15,
32
+ "index_technical": 0.15,
33
+ "sentiment": 0.10,
34
+ }
35
+
36
+ COMPONENT_LABELS = {
37
+ "distribution_days": "Distribution Day Count",
38
+ "leading_stocks": "Leading Stock Health",
39
+ "defensive_rotation": "Defensive Sector Rotation",
40
+ "breadth_divergence": "Market Breadth Divergence",
41
+ "index_technical": "Index Technical Condition",
42
+ "sentiment": "Sentiment & Speculation",
43
+ }
44
+
45
+
46
+ def calculate_composite_score(component_scores: Dict[str, float],
47
+ data_availability: Optional[Dict[str, bool]] = None) -> Dict:
48
+ """
49
+ Calculate weighted composite market top probability score.
50
+
51
+ Args:
52
+ component_scores: Dict with keys matching COMPONENT_WEIGHTS,
53
+ each value 0-100
54
+ data_availability: Optional dict mapping component key -> bool indicating
55
+ if data was actually available (vs neutral default)
56
+
57
+ Returns:
58
+ Dict with composite_score, zone, risk_budget, guidance,
59
+ weakest/strongest components, component breakdown, and data_quality
60
+ """
61
+ if data_availability is None:
62
+ data_availability = {}
63
+
64
+ # Calculate weighted composite
65
+ composite = 0.0
66
+ for key, weight in COMPONENT_WEIGHTS.items():
67
+ score = component_scores.get(key, 0)
68
+ composite += score * weight
69
+
70
+ composite = round(composite, 1)
71
+
72
+ # Identify strongest and weakest warning signals
73
+ valid_scores = {k: v for k, v in component_scores.items()
74
+ if k in COMPONENT_WEIGHTS}
75
+
76
+ if valid_scores:
77
+ strongest_warning = max(valid_scores, key=valid_scores.get)
78
+ weakest_warning = min(valid_scores, key=valid_scores.get)
79
+ else:
80
+ strongest_warning = "N/A"
81
+ weakest_warning = "N/A"
82
+
83
+ # Get zone interpretation
84
+ zone_info = _interpret_zone(composite)
85
+
86
+ # Calculate data quality
87
+ available_count = sum(
88
+ 1 for k in COMPONENT_WEIGHTS
89
+ if data_availability.get(k, True) # Default True for backward compat
90
+ )
91
+ total_components = len(COMPONENT_WEIGHTS)
92
+ missing_components = [
93
+ COMPONENT_LABELS[k] for k in COMPONENT_WEIGHTS
94
+ if not data_availability.get(k, True)
95
+ ]
96
+
97
+ if available_count == total_components:
98
+ quality_label = f"Complete ({available_count}/{total_components} components)"
99
+ elif available_count >= total_components - 2:
100
+ quality_label = (f"Partial ({available_count}/{total_components} components)"
101
+ " - interpret with caution")
102
+ else:
103
+ quality_label = (f"Limited ({available_count}/{total_components} components)"
104
+ " - low confidence")
105
+
106
+ data_quality = {
107
+ "available_count": available_count,
108
+ "total_components": total_components,
109
+ "label": quality_label,
110
+ "missing_components": missing_components,
111
+ }
112
+
113
+ return {
114
+ "composite_score": composite,
115
+ "zone": zone_info["zone"],
116
+ "zone_color": zone_info["color"],
117
+ "risk_budget": zone_info["risk_budget"],
118
+ "guidance": zone_info["guidance"],
119
+ "actions": zone_info["actions"],
120
+ "strongest_warning": {
121
+ "component": strongest_warning,
122
+ "label": COMPONENT_LABELS.get(strongest_warning, strongest_warning),
123
+ "score": valid_scores.get(strongest_warning, 0),
124
+ },
125
+ "weakest_warning": {
126
+ "component": weakest_warning,
127
+ "label": COMPONENT_LABELS.get(weakest_warning, weakest_warning),
128
+ "score": valid_scores.get(weakest_warning, 0),
129
+ },
130
+ "data_quality": data_quality,
131
+ "component_scores": {
132
+ k: {
133
+ "score": component_scores.get(k, 0),
134
+ "weight": w,
135
+ "weighted_contribution": round(component_scores.get(k, 0) * w, 1),
136
+ "label": COMPONENT_LABELS[k],
137
+ }
138
+ for k, w in COMPONENT_WEIGHTS.items()
139
+ },
140
+ }
141
+
142
+
143
+ def _interpret_zone(composite: float) -> Dict:
144
+ """Map composite score to risk zone"""
145
+ if composite <= 20:
146
+ return {
147
+ "zone": "Green (Normal)",
148
+ "color": "green",
149
+ "risk_budget": "100%",
150
+ "guidance": "Normal market conditions. Maintain standard position management.",
151
+ "actions": [
152
+ "Normal position sizing",
153
+ "Standard stop-loss levels",
154
+ "New position entries allowed",
155
+ ],
156
+ }
157
+ elif composite <= 40:
158
+ return {
159
+ "zone": "Yellow (Early Warning)",
160
+ "color": "yellow",
161
+ "risk_budget": "80-90%",
162
+ "guidance": "Early warning signs detected. Tighten stops and reduce new entries.",
163
+ "actions": [
164
+ "Tighten stop-losses by 10-20%",
165
+ "Reduce new position sizes by 25-50%",
166
+ "Review weakest positions for exits",
167
+ "Monitor distribution days closely",
168
+ ],
169
+ }
170
+ elif composite <= 60:
171
+ return {
172
+ "zone": "Orange (Elevated Risk)",
173
+ "color": "orange",
174
+ "risk_budget": "60-75%",
175
+ "guidance": "Elevated risk of correction. Begin profit-taking on weaker positions.",
176
+ "actions": [
177
+ "Take profits on weakest 25-30% of positions",
178
+ "No new momentum entries",
179
+ "Only quality stocks near support",
180
+ "Raise cash allocation",
181
+ "Watch for Follow-Through Day if market pulls back",
182
+ ],
183
+ }
184
+ elif composite <= 80:
185
+ return {
186
+ "zone": "Red (High Probability Top)",
187
+ "color": "red",
188
+ "risk_budget": "40-55%",
189
+ "guidance": "High probability of market top. Aggressive profit-taking recommended.",
190
+ "actions": [
191
+ "Aggressive profit-taking (sell 40-50% of positions)",
192
+ "Maximum cash allocation",
193
+ "Only hold strongest leaders",
194
+ "Consider hedges (put options, inverse ETFs)",
195
+ "Prepare short watchlist",
196
+ ],
197
+ }
198
+ else:
199
+ return {
200
+ "zone": "Critical (Top Formation)",
201
+ "color": "critical",
202
+ "risk_budget": "20-35%",
203
+ "guidance": "Top formation in progress. Maximum defensive posture.",
204
+ "actions": [
205
+ "Sell most positions (keep only 20-35% invested)",
206
+ "Full hedge implementation",
207
+ "Short positions on weakest leaders",
208
+ "Preserve capital as primary objective",
209
+ "Watch for capitulation/Follow-Through Day for re-entry",
210
+ ],
211
+ }
212
+
213
+
214
+ def detect_follow_through_day(index_history: List[Dict],
215
+ composite_score: float) -> Dict:
216
+ """
217
+ Detect Follow-Through Day (FTD) signal for bottom confirmation.
218
+ Only relevant when composite > 40 (Orange zone or worse).
219
+
220
+ O'Neil's Strict FTD Rules:
221
+ 1. Identify swing low: significant decline (3+ down days, -3%+ from recent high)
222
+ 2. Rally Day 1: first up day (close > previous close) after swing low
223
+ 3. FTD: Day 4-7 of rally, gain >= 1.5% on volume higher than previous day
224
+ 4. Rally resets if price closes below swing low
225
+
226
+ Args:
227
+ index_history: Daily OHLCV (most recent first)
228
+ composite_score: Current composite score
229
+
230
+ Returns:
231
+ Dict with ftd_detected, rally_day_count, details
232
+ """
233
+ if composite_score < 40:
234
+ return {
235
+ "ftd_detected": False,
236
+ "applicable": False,
237
+ "reason": "Composite < 40 (Green/Yellow zone) - FTD monitoring not needed",
238
+ }
239
+
240
+ if not index_history or len(index_history) < 10:
241
+ return {
242
+ "ftd_detected": False,
243
+ "applicable": True,
244
+ "reason": "Insufficient data for FTD analysis",
245
+ }
246
+
247
+ # Work in chronological order (oldest first)
248
+ history = list(reversed(index_history))
249
+ n = len(history)
250
+
251
+ # Only look at the most recent 40 trading days
252
+ lookback = min(40, n)
253
+ history = history[n - lookback:]
254
+ n = len(history)
255
+
256
+ # Step 1: Find swing low within the lookback window
257
+ # Swing low = lowest close after a decline of 3%+ from a recent high
258
+ swing_low_idx = None
259
+ swing_low_price = None
260
+
261
+ # Scan for the most recent swing low
262
+ for i in range(n - 1, 2, -1): # from recent to old
263
+ low_close = history[i].get("close", 0)
264
+ if low_close <= 0:
265
+ continue
266
+
267
+ # Look back from this point for a recent high (within 20 days)
268
+ search_start = max(0, i - 20)
269
+ recent_high = 0
270
+ for j in range(search_start, i):
271
+ c = history[j].get("close", 0)
272
+ if c > recent_high:
273
+ recent_high = c
274
+
275
+ if recent_high <= 0:
276
+ continue
277
+
278
+ decline_pct = (low_close - recent_high) / recent_high * 100
279
+
280
+ # Check for 3%+ decline from recent high
281
+ if decline_pct <= -3.0:
282
+ # Verify at least 3 down days in the decline
283
+ down_days = 0
284
+ for j in range(search_start + 1, i + 1):
285
+ prev_c = history[j - 1].get("close", 0)
286
+ curr_c = history[j].get("close", 0)
287
+ if prev_c > 0 and curr_c < prev_c:
288
+ down_days += 1
289
+
290
+ if down_days >= 3:
291
+ # Check this is actually a local low
292
+ is_local_low = True
293
+ if i > 0 and history[i - 1].get("close", 0) < low_close:
294
+ is_local_low = False
295
+ if i + 1 < n and history[i + 1].get("close", 0) < low_close:
296
+ is_local_low = False
297
+
298
+ if is_local_low:
299
+ swing_low_idx = i
300
+ swing_low_price = low_close
301
+ break
302
+
303
+ if swing_low_idx is None:
304
+ return {
305
+ "ftd_detected": False,
306
+ "applicable": True,
307
+ "reason": "No qualifying swing low found (need 3%+ decline with 3+ down days)",
308
+ "rally_day_count": 0,
309
+ }
310
+
311
+ # Step 2: Find Rally Day 1 (first up day after swing low)
312
+ rally_day_1_idx = None
313
+ for i in range(swing_low_idx + 1, n):
314
+ curr_close = history[i].get("close", 0)
315
+ prev_close = history[i - 1].get("close", 0)
316
+ if prev_close > 0 and curr_close > prev_close:
317
+ rally_day_1_idx = i
318
+ break
319
+
320
+ if rally_day_1_idx is None:
321
+ return {
322
+ "ftd_detected": False,
323
+ "applicable": True,
324
+ "reason": "No rally attempt started after swing low",
325
+ "rally_day_count": 0,
326
+ "swing_low_date": history[swing_low_idx].get("date", "N/A"),
327
+ "swing_low_price": swing_low_price,
328
+ }
329
+
330
+ # Step 3: Count rally days and check for FTD / reset
331
+ rally_day_count = 0
332
+ ftd_detected = False
333
+ ftd_day = None
334
+ rally_reset = False
335
+
336
+ for i in range(rally_day_1_idx, n):
337
+ curr_close = history[i].get("close", 0)
338
+
339
+ # Check for rally reset: price closes below swing low
340
+ if curr_close < swing_low_price:
341
+ rally_reset = True
342
+ # Try to find a new swing low from here
343
+ swing_low_idx = i
344
+ swing_low_price = curr_close
345
+ rally_day_count = 0
346
+ ftd_detected = False
347
+ rally_reset = False
348
+ # Look for new rally day 1
349
+ continue
350
+
351
+ prev_close = history[i - 1].get("close", 0) if i > 0 else 0
352
+
353
+ if prev_close > 0 and curr_close > prev_close:
354
+ rally_day_count += 1
355
+ elif prev_close > 0 and curr_close <= prev_close:
356
+ # Down day during rally - still count toward rally days
357
+ rally_day_count += 1
358
+
359
+ # Check FTD on days 4-7
360
+ if 4 <= rally_day_count <= 7 and prev_close > 0:
361
+ gain_pct = (curr_close - prev_close) / prev_close * 100
362
+ curr_volume = history[i].get("volume", 0)
363
+ prev_volume = history[i - 1].get("volume", 0)
364
+
365
+ if gain_pct >= 1.5 and prev_volume > 0 and curr_volume > prev_volume:
366
+ ftd_detected = True
367
+ ftd_day = history[i].get("date", f"day-{i}")
368
+ break
369
+
370
+ # Cap rally_day_count for reporting
371
+ days_since_rally_start = n - rally_day_1_idx
372
+ rally_day_count = min(rally_day_count, days_since_rally_start)
373
+
374
+ swing_low_date = history[swing_low_idx].get("date", "N/A")
375
+
376
+ if ftd_detected:
377
+ reason = f"Follow-Through Day detected on {ftd_day} (Day {rally_day_count} of rally from {swing_low_date} low)"
378
+ elif rally_day_count >= 7:
379
+ reason = (f"Rally attempt: Day {rally_day_count} from {swing_low_date} low - "
380
+ "FTD window (Day 4-7) passed without qualifying day")
381
+ else:
382
+ reason = (f"Rally attempt: Day {rally_day_count} from {swing_low_date} low "
383
+ f"(FTD requires Day 4-7 with >= 1.5% gain on higher volume)")
384
+
385
+ return {
386
+ "ftd_detected": ftd_detected,
387
+ "applicable": True,
388
+ "rally_day_count": rally_day_count,
389
+ "ftd_day": ftd_day,
390
+ "swing_low_date": swing_low_date,
391
+ "swing_low_price": swing_low_price,
392
+ "reason": reason,
393
+ }
394
+
395
+
396
+ # Testing
397
+ if __name__ == "__main__":
398
+ print("Testing Market Top Scorer...\n")
399
+
400
+ # Test 1: Moderate risk scenario (calibration target: ~50)
401
+ test_scores = {
402
+ "distribution_days": 45,
403
+ "leading_stocks": 52,
404
+ "defensive_rotation": 82,
405
+ "breadth_divergence": 20,
406
+ "index_technical": 42,
407
+ "sentiment": 62,
408
+ }
409
+
410
+ result = calculate_composite_score(test_scores)
411
+ print(f"Test 1 - Moderate Risk:")
412
+ print(f" Composite: {result['composite_score']}/100")
413
+ print(f" Zone: {result['zone']}")
414
+ print(f" Risk Budget: {result['risk_budget']}")
415
+ print(f" Strongest Warning: {result['strongest_warning']['label']} "
416
+ f"({result['strongest_warning']['score']})")
417
+ print()
418
+
419
+ # Test 2: Healthy market
420
+ healthy = {
421
+ "distribution_days": 0,
422
+ "leading_stocks": 10,
423
+ "defensive_rotation": 0,
424
+ "breadth_divergence": 10,
425
+ "index_technical": 5,
426
+ "sentiment": 15,
427
+ }
428
+ result2 = calculate_composite_score(healthy)
429
+ print(f"Test 2 - Healthy Market:")
430
+ print(f" Composite: {result2['composite_score']}/100")
431
+ print(f" Zone: {result2['zone']}")
432
+ print()
433
+
434
+ # Test 3: Crisis
435
+ crisis = {
436
+ "distribution_days": 100,
437
+ "leading_stocks": 90,
438
+ "defensive_rotation": 85,
439
+ "breadth_divergence": 80,
440
+ "index_technical": 75,
441
+ "sentiment": 70,
442
+ }
443
+ result3 = calculate_composite_score(crisis)
444
+ print(f"Test 3 - Crisis:")
445
+ print(f" Composite: {result3['composite_score']}/100")
446
+ print(f" Zone: {result3['zone']}")
447
+ print()
448
+
449
+ # Test 4: Data quality tracking
450
+ partial_scores = {
451
+ "distribution_days": 45,
452
+ "leading_stocks": 52,
453
+ "defensive_rotation": 50,
454
+ "breadth_divergence": 50,
455
+ "index_technical": 42,
456
+ "sentiment": 50,
457
+ }
458
+ partial_availability = {
459
+ "distribution_days": True,
460
+ "leading_stocks": True,
461
+ "defensive_rotation": False,
462
+ "breadth_divergence": False,
463
+ "index_technical": True,
464
+ "sentiment": False,
465
+ }
466
+ result4 = calculate_composite_score(partial_scores, partial_availability)
467
+ print(f"Test 4 - Partial Data:")
468
+ print(f" Composite: {result4['composite_score']}/100")
469
+ print(f" Data Quality: {result4['data_quality']['label']}")
470
+ print(f" Missing: {result4['data_quality']['missing_components']}")
471
+ print()
472
+
473
+ print("All tests completed.")
@@ -0,0 +1,9 @@
1
+ """Shared fixtures for Market Top Detector tests"""
2
+
3
+ import os
4
+ import sys
5
+
6
+ # Add scripts directory to path so calculators can be imported
7
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
8
+ # Add tests directory to path so helpers can be imported
9
+ sys.path.insert(0, os.path.dirname(__file__))
@@ -0,0 +1,49 @@
1
+ """Importable test helpers for Market Top 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) # Slight volume variation
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_history_with_volumes(close_volume_pairs, start_date="2026-01-15"):
39
+ """Create bars from list of (close, volume) tuples (most recent first)."""
40
+ bars = []
41
+ for i, (close, volume) in enumerate(close_volume_pairs):
42
+ bars.append(
43
+ make_daily_bar(
44
+ close=close,
45
+ volume=volume,
46
+ date=f"day-{i}",
47
+ )
48
+ )
49
+ return bars
@@ -0,0 +1,62 @@
1
+ """Tests for Breadth Calculator"""
2
+
3
+ from calculators.breadth_calculator import (
4
+ _score_200dma_breadth,
5
+ calculate_breadth_divergence,
6
+ )
7
+
8
+
9
+ class TestScore200dmaBreadth:
10
+ """Boundary tests for 200DMA scoring."""
11
+
12
+ def test_critical_below_40(self):
13
+ assert _score_200dma_breadth(35) == 100
14
+
15
+ def test_healthy_above_70(self):
16
+ assert _score_200dma_breadth(75) == 5
17
+
18
+ def test_calibration_62_26(self):
19
+ """62.26% breadth should score ~24 (healthy)."""
20
+ score = _score_200dma_breadth(62.26)
21
+ assert 18 <= score <= 30 # Reasonable range around 24
22
+
23
+ def test_boundary_50(self):
24
+ score = _score_200dma_breadth(50)
25
+ assert score == 55
26
+
27
+ def test_boundary_60(self):
28
+ score = _score_200dma_breadth(60)
29
+ assert score == 30
30
+
31
+
32
+ class TestCalculateBreadthDivergence:
33
+ """Integration tests."""
34
+
35
+ def test_missing_breadth_returns_50(self):
36
+ """None 200DMA → score 50, data_available=False."""
37
+ result = calculate_breadth_divergence(None, None, -2.0)
38
+ assert result["score"] == 50
39
+ assert result["data_available"] is False
40
+
41
+ def test_available_data_has_flag(self):
42
+ """Valid data → data_available=True."""
43
+ result = calculate_breadth_divergence(65.0, 55.0, -1.0)
44
+ assert result["data_available"] is True
45
+
46
+ def test_near_highs_divergence(self):
47
+ """Index near highs + weak breadth → high score."""
48
+ result = calculate_breadth_divergence(45.0, 30.0, -2.0)
49
+ assert result["score"] >= 70
50
+ assert result["divergence_detected"] is True
51
+
52
+ def test_not_near_highs_halved(self):
53
+ """Index NOT near highs → score halved."""
54
+ near = calculate_breadth_divergence(45.0, None, -2.0)
55
+ far = calculate_breadth_divergence(45.0, None, -8.0)
56
+ assert far["score"] < near["score"]
57
+
58
+ def test_50dma_supplement_boost(self):
59
+ """Low 50DMA breadth at highs adds +10."""
60
+ base = calculate_breadth_divergence(55.0, 80.0, -2.0)
61
+ boosted = calculate_breadth_divergence(55.0, 25.0, -2.0)
62
+ assert boosted["score"] > base["score"]
@@ -0,0 +1,56 @@
1
+ """Tests for Defensive Rotation Calculator"""
2
+
3
+ from calculators.defensive_rotation_calculator import (
4
+ _score_rotation,
5
+ calculate_defensive_rotation,
6
+ )
7
+
8
+
9
+ class TestScoreRotation:
10
+ """Boundary tests for rotation scoring."""
11
+
12
+ def test_growth_leading(self):
13
+ """Negative relative = growth leading = healthy."""
14
+ assert _score_rotation(-3.0) == 0
15
+ assert _score_rotation(-1.0) <= 15
16
+
17
+ def test_slight_defensive_tilt(self):
18
+ assert 20 <= _score_rotation(0.3) <= 40
19
+
20
+ def test_strong_defensive_rotation(self):
21
+ assert _score_rotation(5.0) == 100
22
+
23
+ def test_moderate_rotation(self):
24
+ assert 60 <= _score_rotation(2.0) <= 80
25
+
26
+
27
+ class TestCalculateDefensiveRotation:
28
+ """Integration tests."""
29
+
30
+ def test_missing_data_returns_50(self):
31
+ """No ETF data → score 50 (neutral), data_available=False."""
32
+ result = calculate_defensive_rotation({})
33
+ assert result["score"] == 50
34
+ assert result["data_available"] is False
35
+
36
+ def test_partial_data_still_computes(self):
37
+ """At least 1 defensive + 1 offensive ETF should compute."""
38
+ historical = {
39
+ "XLU": [{"close": 70 + i * 0.1, "volume": 500000} for i in range(25)],
40
+ "XLK": [{"close": 200 - i * 0.5, "volume": 2000000} for i in range(25)],
41
+ }
42
+ result = calculate_defensive_rotation(historical)
43
+ assert result["data_available"] is True
44
+ assert "score" in result
45
+
46
+ def test_growth_leading_scenario(self):
47
+ """Offensive outperforming defensive → low score."""
48
+ historical = {}
49
+ for sym in ["XLU", "XLP", "XLV", "VNQ"]:
50
+ # Defensive: flat
51
+ historical[sym] = [{"close": 50, "volume": 500000} for _ in range(25)]
52
+ for sym in ["XLK", "XLC", "XLY", "QQQ"]:
53
+ # Offensive: +5% over 20 days
54
+ historical[sym] = [{"close": 105 - i * 0.25, "volume": 2000000} for i in range(25)]
55
+ result = calculate_defensive_rotation(historical)
56
+ assert result["score"] <= 20