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,92 @@
1
+ """Tests for Distribution Day Calculator"""
2
+
3
+ from calculators.distribution_day_calculator import (
4
+ _count_distribution_days,
5
+ _score_distribution_days,
6
+ calculate_distribution_days,
7
+ )
8
+ from helpers import make_history, make_history_with_volumes
9
+
10
+
11
+ class TestScoreDistributionDays:
12
+ """Boundary tests for scoring thresholds."""
13
+
14
+ def test_zero_days(self):
15
+ assert _score_distribution_days(0) == 0
16
+
17
+ def test_one_day(self):
18
+ assert _score_distribution_days(1) == 15
19
+
20
+ def test_two_days(self):
21
+ assert _score_distribution_days(2) == 30
22
+
23
+ def test_three_days(self):
24
+ assert _score_distribution_days(3) == 55
25
+
26
+ def test_four_days_oneil_threshold(self):
27
+ assert _score_distribution_days(4) == 75
28
+
29
+ def test_five_days(self):
30
+ assert _score_distribution_days(5) == 90
31
+
32
+ def test_six_plus_days(self):
33
+ assert _score_distribution_days(6) == 100
34
+ assert _score_distribution_days(8) == 100
35
+
36
+ def test_fractional_stalling(self):
37
+ """Stalling days count as 0.5"""
38
+ assert _score_distribution_days(3.5) == 55 # 3.5 >= 3
39
+ assert _score_distribution_days(4.5) == 75 # 4.5 >= 4
40
+
41
+
42
+ class TestCountDistributionDays:
43
+ """Tests for distribution day detection logic."""
44
+
45
+ def test_empty_history(self):
46
+ result = _count_distribution_days([], "TEST")
47
+ assert result["distribution_days"] == 0
48
+
49
+ def test_single_day(self):
50
+ result = _count_distribution_days([{"close": 100, "volume": 1000}], "TEST")
51
+ assert result["distribution_days"] == 0
52
+
53
+ def test_distribution_day_detected(self):
54
+ """Price drop >= 0.2% on higher volume = distribution day."""
55
+ history = make_history_with_volumes(
56
+ [
57
+ (99.0, 1200000), # Today: -1% drop, higher volume → distribution
58
+ (100.0, 1000000), # Yesterday
59
+ (100.5, 800000), # Two days ago: lower volume than yesterday
60
+ ]
61
+ )
62
+ result = _count_distribution_days(history, "TEST")
63
+ assert result["distribution_days"] >= 1
64
+
65
+ def test_stalling_day_detected(self):
66
+ """Volume increases but price gain < 0.1% = stalling day."""
67
+ history = make_history_with_volumes(
68
+ [
69
+ (100.05, 1200000), # Today: +0.05%, higher volume
70
+ (100.0, 1000000), # Yesterday
71
+ (99.5, 900000), # Two days ago
72
+ ]
73
+ )
74
+ result = _count_distribution_days(history, "TEST")
75
+ assert result["stalling_days"] >= 1
76
+
77
+
78
+ class TestCalculateDistributionDays:
79
+ """Integration tests."""
80
+
81
+ def test_uses_higher_count(self):
82
+ """Should use the worse of S&P 500 / NASDAQ."""
83
+ # Create histories where NASDAQ has more distribution days
84
+ sp_history = make_history([100] * 30)
85
+ # NASDAQ with distribution pattern
86
+ nasdaq_cv = [(99.0 - i * 0.3, 1200000 + i * 10000) for i in range(15)]
87
+ nasdaq_cv += [(100.0 + i * 0.1, 1000000) for i in range(15)]
88
+ nasdaq_history = make_history_with_volumes(nasdaq_cv)
89
+
90
+ result = calculate_distribution_days(sp_history, nasdaq_history)
91
+ assert "score" in result
92
+ assert "effective_count" in result
@@ -0,0 +1,73 @@
1
+ """Tests for Index Technical Calculator"""
2
+
3
+ from calculators.index_technical_calculator import (
4
+ _evaluate_index,
5
+ calculate_index_technical,
6
+ )
7
+ from helpers import make_history
8
+
9
+
10
+ class TestEvaluateIndex:
11
+ """Test single index evaluation."""
12
+
13
+ def test_insufficient_data(self):
14
+ """Less than 21 days → data_available=False."""
15
+ result = _evaluate_index("TEST", [{"close": 100}] * 10)
16
+ assert result["data_available"] is False
17
+ assert result["raw_score"] == 0
18
+
19
+ def test_sufficient_data(self):
20
+ """21+ days → data_available=True."""
21
+ history = make_history([100 - i * 0.1 for i in range(50)])
22
+ result = _evaluate_index("TEST", history)
23
+ assert result["data_available"] is True
24
+
25
+ def test_below_21ema_adds_8(self):
26
+ """Price significantly below 21 EMA should trigger flag."""
27
+ # Create downtrend: recent prices well below earlier ones
28
+ closes = [90] * 5 + [100 + i * 0.5 for i in range(45)]
29
+ history = make_history(closes)
30
+ result = _evaluate_index("TEST", history)
31
+ assert any("21 EMA" in f for f in result["flags"])
32
+
33
+
34
+ class TestCalculateIndexTechnical:
35
+ """Test composite index technical score."""
36
+
37
+ def test_both_available(self):
38
+ """Both S&P and NASDAQ have data → average them."""
39
+ sp = make_history([100 - i * 0.1 for i in range(50)])
40
+ nq = make_history([200 - i * 0.2 for i in range(50)])
41
+ result = calculate_index_technical(sp, nq)
42
+ assert result["data_available"] is True
43
+ assert result["sp500"]["data_available"] is True
44
+ assert result["nasdaq"]["data_available"] is True
45
+
46
+ def test_nasdaq_missing_uses_sp_only(self):
47
+ """NASDAQ insufficient data → use S&P 500 only, no halving."""
48
+ sp = make_history([100 - i * 0.1 for i in range(50)])
49
+ nq_short = [{"close": 200}] * 5 # Too short
50
+
51
+ calculate_index_technical(sp, sp) # Both = S&P (warm up)
52
+ result_sp_only = calculate_index_technical(sp, nq_short)
53
+
54
+ # S&P-only score should equal S&P's raw_score (not halved)
55
+ sp_raw = result_sp_only["sp500"]["raw_score"]
56
+ assert result_sp_only["score"] == round(min(100, max(0, sp_raw)))
57
+ assert result_sp_only["nasdaq"]["data_available"] is False
58
+
59
+ def test_both_missing_returns_50(self):
60
+ """Both insufficient → score=50, data_available=False."""
61
+ short = [{"close": 100}] * 5
62
+ result = calculate_index_technical(short, short)
63
+ assert result["score"] == 50
64
+ assert result["data_available"] is False
65
+ assert "NO DATA" in result["signal"]
66
+
67
+ def test_sp_missing_nasdaq_available(self):
68
+ """S&P insufficient, NASDAQ available → use NASDAQ only."""
69
+ sp_short = [{"close": 100}] * 5
70
+ nq = make_history([200 - i * 0.2 for i in range(50)])
71
+ result = calculate_index_technical(sp_short, nq)
72
+ nq_raw = result["nasdaq"]["raw_score"]
73
+ assert result["score"] == round(min(100, max(0, nq_raw)))
@@ -0,0 +1,57 @@
1
+ """Tests for Leading Stock Calculator"""
2
+
3
+ from calculators.leading_stock_calculator import (
4
+ _evaluate_etf,
5
+ calculate_leading_stock_health,
6
+ )
7
+
8
+
9
+ class TestEvaluateETF:
10
+ """Test individual ETF evaluation."""
11
+
12
+ def test_near_highs_healthy(self):
13
+ quote = {"price": 100, "yearHigh": 102, "yearLow": 80}
14
+ history = [{"close": 100 - i * 0.1, "volume": 1000000} for i in range(60)]
15
+ result = _evaluate_etf("TEST", quote, history)
16
+ assert result["deterioration_score"] <= 20
17
+
18
+ def test_deep_correction(self):
19
+ quote = {"price": 70, "yearHigh": 100, "yearLow": 65}
20
+ history = [{"close": 70 + i * 0.3, "volume": 1000000} for i in range(60)]
21
+ result = _evaluate_etf("TEST", quote, history)
22
+ assert result["deterioration_score"] >= 30
23
+
24
+
25
+ class TestCalculateLeadingStockHealth:
26
+ """Test composite leading stock calculation."""
27
+
28
+ def test_no_data_returns_50(self):
29
+ """No quotes → score 50 (neutral), data_available=False."""
30
+ result = calculate_leading_stock_health({}, {})
31
+ assert result["score"] == 50
32
+ assert result["etfs_evaluated"] == 0
33
+ assert result["data_available"] is False
34
+
35
+ def test_healthy_leaders(self):
36
+ quotes = {}
37
+ historical = {}
38
+ for sym in ["ARKK", "WCLD", "IGV"]:
39
+ quotes[sym] = {"price": 100, "yearHigh": 102, "yearLow": 80}
40
+ historical[sym] = [{"close": 100 - i * 0.05, "volume": 1000000} for i in range(60)]
41
+ result = calculate_leading_stock_health(quotes, historical)
42
+ assert result["score"] <= 30 # Healthy
43
+ assert result["data_available"] is True
44
+
45
+ def test_amplification_at_60pct(self):
46
+ """60%+ ETFs deteriorating triggers 1.3x amplification."""
47
+ quotes = {}
48
+ historical = {}
49
+ # All ETFs in deep correction: -30% from high, below 50DMA
50
+ for sym in ["ARKK", "WCLD", "IGV", "XBI", "SOXX", "SMH", "KWEB", "TAN"]:
51
+ quotes[sym] = {"price": 70, "yearHigh": 100, "yearLow": 60}
52
+ # Create declining history so price is well below MAs
53
+ historical[sym] = [
54
+ {"close": 70 + i * 0.5, "high": 72 + i * 0.5, "volume": 1000000} for i in range(60)
55
+ ]
56
+ result = calculate_leading_stock_health(quotes, historical)
57
+ assert result["amplified"] is True
@@ -0,0 +1,180 @@
1
+ """Tests for Composite Scorer and FTD Detection"""
2
+
3
+ from scorer import (
4
+ COMPONENT_WEIGHTS,
5
+ calculate_composite_score,
6
+ detect_follow_through_day,
7
+ )
8
+
9
+
10
+ class TestCompositeScore:
11
+ """Test composite scoring."""
12
+
13
+ def test_all_zeros(self):
14
+ scores = {k: 0 for k in COMPONENT_WEIGHTS}
15
+ result = calculate_composite_score(scores)
16
+ assert result["composite_score"] == 0
17
+ assert "Green" in result["zone"]
18
+
19
+ def test_all_100(self):
20
+ scores = {k: 100 for k in COMPONENT_WEIGHTS}
21
+ result = calculate_composite_score(scores)
22
+ assert result["composite_score"] == 100.0
23
+ assert "Critical" in result["zone"]
24
+
25
+ def test_moderate_risk(self):
26
+ """Calibration target: ~48-50 for moderate risk."""
27
+ scores = {
28
+ "distribution_days": 45,
29
+ "leading_stocks": 52,
30
+ "defensive_rotation": 82,
31
+ "breadth_divergence": 20,
32
+ "index_technical": 42,
33
+ "sentiment": 62,
34
+ }
35
+ result = calculate_composite_score(scores)
36
+ assert 40 <= result["composite_score"] <= 55
37
+
38
+ def test_weights_sum_to_1(self):
39
+ total = sum(COMPONENT_WEIGHTS.values())
40
+ assert abs(total - 1.0) < 0.001
41
+
42
+ def test_strongest_weakest(self):
43
+ scores = {
44
+ "distribution_days": 90,
45
+ "leading_stocks": 10,
46
+ "defensive_rotation": 50,
47
+ "breadth_divergence": 50,
48
+ "index_technical": 50,
49
+ "sentiment": 50,
50
+ }
51
+ result = calculate_composite_score(scores)
52
+ assert result["strongest_warning"]["component"] == "distribution_days"
53
+ assert result["weakest_warning"]["component"] == "leading_stocks"
54
+
55
+
56
+ class TestDataQuality:
57
+ """Test data quality tracking."""
58
+
59
+ def test_all_available(self):
60
+ scores = {k: 50 for k in COMPONENT_WEIGHTS}
61
+ avail = {k: True for k in COMPONENT_WEIGHTS}
62
+ result = calculate_composite_score(scores, avail)
63
+ dq = result["data_quality"]
64
+ assert dq["available_count"] == 6
65
+ assert "Complete" in dq["label"]
66
+ assert dq["missing_components"] == []
67
+
68
+ def test_some_missing(self):
69
+ scores = {k: 50 for k in COMPONENT_WEIGHTS}
70
+ avail = {k: True for k in COMPONENT_WEIGHTS}
71
+ avail["breadth_divergence"] = False
72
+ avail["sentiment"] = False
73
+ result = calculate_composite_score(scores, avail)
74
+ dq = result["data_quality"]
75
+ assert dq["available_count"] == 4
76
+ assert "Partial" in dq["label"]
77
+ assert len(dq["missing_components"]) == 2
78
+
79
+ def test_many_missing(self):
80
+ scores = {k: 50 for k in COMPONENT_WEIGHTS}
81
+ avail = {k: False for k in COMPONENT_WEIGHTS}
82
+ avail["distribution_days"] = True
83
+ avail["leading_stocks"] = True
84
+ result = calculate_composite_score(scores, avail)
85
+ dq = result["data_quality"]
86
+ assert dq["available_count"] == 2
87
+ assert "Limited" in dq["label"]
88
+
89
+ def test_backward_compat_no_availability(self):
90
+ """Without data_availability, all assumed available."""
91
+ scores = {k: 50 for k in COMPONENT_WEIGHTS}
92
+ result = calculate_composite_score(scores)
93
+ dq = result["data_quality"]
94
+ assert dq["available_count"] == 6
95
+ assert "Complete" in dq["label"]
96
+
97
+
98
+ class TestFollowThroughDay:
99
+ """Test O'Neil-strict FTD detection."""
100
+
101
+ def test_not_applicable_below_40(self):
102
+ result = detect_follow_through_day([], 30.0)
103
+ assert result["applicable"] is False
104
+
105
+ def test_insufficient_data(self):
106
+ result = detect_follow_through_day([{"close": 100}] * 5, 50.0)
107
+ assert result["ftd_detected"] is False
108
+
109
+ def test_no_swing_low(self):
110
+ """Flat market → no swing low found."""
111
+ history = [{"close": 100, "volume": 1000000, "date": f"day-{i}"} for i in range(30)]
112
+ result = detect_follow_through_day(history, 50.0)
113
+ assert result["ftd_detected"] is False
114
+
115
+ def test_ftd_detected_on_clear_pattern(self):
116
+ """
117
+ Create a clear pattern:
118
+ - Days 0-9: prices rise from 100 to 109 (establishing high)
119
+ - Days 10-14: decline from 109 to 100 (5+ down days, ~8% drop)
120
+ - Day 15: swing low at 100
121
+ - Day 16: rally day 1 (up from 100 to 101)
122
+ - Day 17-18: rally continues (101.5, 102)
123
+ - Day 19 (rally day 4): FTD - big gain +2% on higher volume
124
+
125
+ History is most-recent-first, so reversed.
126
+ """
127
+ bars = []
128
+ # Build chronologically, then reverse
129
+ # Days 0-9: uptrend
130
+ for i in range(10):
131
+ bars.append({"close": 100 + i, "volume": 1000000, "date": f"2026-01-{i + 1:02d}"})
132
+
133
+ # Days 10-14: decline (5 down days from 109)
134
+ decline_prices = [107, 105, 103, 101, 100]
135
+ for i, p in enumerate(decline_prices):
136
+ bars.append({"close": p, "volume": 1100000, "date": f"2026-01-{11 + i:02d}"})
137
+
138
+ # Day 15: swing low
139
+ bars.append({"close": 99.5, "volume": 1200000, "date": "2026-01-16"})
140
+
141
+ # Day 16: rally day 1
142
+ bars.append({"close": 101, "volume": 1000000, "date": "2026-01-17"})
143
+
144
+ # Day 17-18: rally continues
145
+ bars.append({"close": 101.5, "volume": 1000000, "date": "2026-01-18"})
146
+ bars.append({"close": 102, "volume": 1000000, "date": "2026-01-19"})
147
+
148
+ # Day 19: FTD - +2% gain on higher volume
149
+ bars.append({"close": 104.04, "volume": 1500000, "date": "2026-01-20"})
150
+
151
+ # Reverse to most-recent-first
152
+ history = list(reversed(bars))
153
+
154
+ result = detect_follow_through_day(history, 55.0)
155
+ assert result["ftd_detected"] is True
156
+ assert result["rally_day_count"] >= 4
157
+
158
+ def test_rally_reset_below_swing_low(self):
159
+ """Rally resets if price drops below swing low."""
160
+ bars = []
161
+ # Uptrend
162
+ for i in range(10):
163
+ bars.append({"close": 100 + i, "volume": 1000000, "date": f"d-{i}"})
164
+ # Decline
165
+ for i, p in enumerate([107, 105, 103, 101, 100]):
166
+ bars.append({"close": p, "volume": 1100000, "date": f"d-{10 + i}"})
167
+ # Swing low
168
+ bars.append({"close": 99, "volume": 1200000, "date": "d-15"})
169
+ # Brief rally
170
+ bars.append({"close": 100, "volume": 1000000, "date": "d-16"})
171
+ # Drop below swing low → reset
172
+ bars.append({"close": 98, "volume": 1000000, "date": "d-17"})
173
+ # New rally from lower base
174
+ bars.append({"close": 99, "volume": 1000000, "date": "d-18"})
175
+ bars.append({"close": 99.5, "volume": 1000000, "date": "d-19"})
176
+
177
+ history = list(reversed(bars))
178
+ result = detect_follow_through_day(history, 55.0)
179
+ # FTD should NOT be detected (rally reset, new rally too short)
180
+ assert result["ftd_detected"] is False
@@ -0,0 +1,64 @@
1
+ """Tests for Sentiment Calculator"""
2
+
3
+ from calculators.sentiment_calculator import calculate_sentiment
4
+
5
+
6
+ class TestSentimentMissingData:
7
+ """Test missing data handling."""
8
+
9
+ def test_all_none_returns_50(self):
10
+ """All inputs None → score=50, data_available=False."""
11
+ result = calculate_sentiment()
12
+ assert result["score"] == 50
13
+ assert result["data_available"] is False
14
+ assert "NO DATA" in result["signal"]
15
+
16
+ def test_partial_data_is_available(self):
17
+ """At least one input → data_available=True."""
18
+ result = calculate_sentiment(vix_level=15.0)
19
+ assert result["data_available"] is True
20
+
21
+ def test_vix_only(self):
22
+ result = calculate_sentiment(vix_level=15.0)
23
+ assert result["score"] == 10 # VIX 15 → +10pt
24
+
25
+ def test_put_call_only(self):
26
+ result = calculate_sentiment(put_call_ratio=0.65)
27
+ assert result["score"] == 30 # PC < 0.70 → +30pt
28
+
29
+
30
+ class TestSentimentScoring:
31
+ """Boundary tests for sentiment scoring."""
32
+
33
+ def test_extreme_complacency(self):
34
+ """Low VIX + low P/C + steep contango = max score."""
35
+ result = calculate_sentiment(
36
+ vix_level=11.0,
37
+ put_call_ratio=0.55,
38
+ vix_term_structure="steep_contango",
39
+ )
40
+ assert result["score"] == 100 # 30+40+30=100
41
+
42
+ def test_high_fear(self):
43
+ """High VIX + high P/C + backwardation = 0 or negative."""
44
+ result = calculate_sentiment(
45
+ vix_level=30.0,
46
+ put_call_ratio=0.95,
47
+ vix_term_structure="backwardation",
48
+ )
49
+ assert result["score"] == 0 # -10+0+(-10) = -20 → clamped to 0
50
+
51
+ def test_moderate_sentiment(self):
52
+ result = calculate_sentiment(
53
+ vix_level=15.0,
54
+ put_call_ratio=0.75,
55
+ vix_term_structure="contango",
56
+ )
57
+ # VIX 15→10, PC 0.75→15, contango→15 = 40
58
+ assert result["score"] == 40
59
+
60
+ def test_margin_debt_not_scored(self):
61
+ """Margin debt is context only, should not affect score."""
62
+ without = calculate_sentiment(vix_level=15.0)
63
+ with_margin = calculate_sentiment(vix_level=15.0, margin_debt_yoy_pct=40.0)
64
+ assert without["score"] == with_margin["score"]