marketmyna 0.2.0__py3-none-any.whl

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 (344) hide show
  1. marketmyna/__init__.py +14 -0
  2. marketmyna/agent/__init__.py +8 -0
  3. marketmyna/agent/agents.py +543 -0
  4. marketmyna/agent/context.py +69 -0
  5. marketmyna/agent/dashboard.py +55 -0
  6. marketmyna/agent/orchestrator.py +153 -0
  7. marketmyna/agent/sentiment.py +150 -0
  8. marketmyna/agent/skills.py +119 -0
  9. marketmyna/ai/__init__.py +1 -0
  10. marketmyna/ai/portfolio_optimizer.py +330 -0
  11. marketmyna/analysis/__init__.py +6 -0
  12. marketmyna/analysis/fundamental.py +329 -0
  13. marketmyna/analysis/sip_optimizer.py +81 -0
  14. marketmyna/analysis/technical.py +321 -0
  15. marketmyna/api/__init__.py +1 -0
  16. marketmyna/api/app.py +209 -0
  17. marketmyna/api/deps.py +63 -0
  18. marketmyna/api/middlewares/__init__.py +0 -0
  19. marketmyna/api/middlewares/auth.py +116 -0
  20. marketmyna/api/middlewares/csrf.py +66 -0
  21. marketmyna/api/middlewares/error_handler.py +38 -0
  22. marketmyna/api/middlewares/slashes.py +27 -0
  23. marketmyna/api/v1/__init__.py +0 -0
  24. marketmyna/api/v1/endpoints/__init__.py +0 -0
  25. marketmyna/api/v1/endpoints/account.py +166 -0
  26. marketmyna/api/v1/endpoints/agent.py +114 -0
  27. marketmyna/api/v1/endpoints/bot.py +38 -0
  28. marketmyna/api/v1/endpoints/calendar.py +269 -0
  29. marketmyna/api/v1/endpoints/data_sources.py +174 -0
  30. marketmyna/api/v1/endpoints/health.py +10 -0
  31. marketmyna/api/v1/endpoints/listings.py +564 -0
  32. marketmyna/api/v1/endpoints/macro.py +163 -0
  33. marketmyna/api/v1/endpoints/portfolio.py +530 -0
  34. marketmyna/api/v1/endpoints/push.py +119 -0
  35. marketmyna/api/v1/endpoints/reports.py +49 -0
  36. marketmyna/api/v1/endpoints/scheduler.py +219 -0
  37. marketmyna/api/v1/endpoints/stocks.py +400 -0
  38. marketmyna/api/v1/endpoints/system_config.py +157 -0
  39. marketmyna/api/v1/endpoints/thesis.py +112 -0
  40. marketmyna/api/v1/endpoints/trade.py +709 -0
  41. marketmyna/api/v1/endpoints/updates.py +394 -0
  42. marketmyna/api/v1/endpoints/watchlists.py +179 -0
  43. marketmyna/api/v1/router.py +43 -0
  44. marketmyna/bot/__init__.py +14 -0
  45. marketmyna/bot/commands/__init__.py +0 -0
  46. marketmyna/bot/commands/analyze.py +40 -0
  47. marketmyna/bot/commands/ask.py +51 -0
  48. marketmyna/bot/commands/base.py +35 -0
  49. marketmyna/bot/commands/batch.py +53 -0
  50. marketmyna/bot/commands/common.py +84 -0
  51. marketmyna/bot/commands/help.py +43 -0
  52. marketmyna/bot/commands/markets.py +153 -0
  53. marketmyna/bot/commands/portfolio.py +145 -0
  54. marketmyna/bot/commands/research.py +323 -0
  55. marketmyna/bot/commands/status.py +48 -0
  56. marketmyna/bot/commands/trade.py +176 -0
  57. marketmyna/bot/dispatcher.py +95 -0
  58. marketmyna/bot/formatting.py +25 -0
  59. marketmyna/bot/models.py +27 -0
  60. marketmyna/bot/registry.py +50 -0
  61. marketmyna/bot/telegram_listener.py +227 -0
  62. marketmyna/cli/__init__.py +1 -0
  63. marketmyna/cli/scheduled_run.py +292 -0
  64. marketmyna/core/__init__.py +6 -0
  65. marketmyna/core/compliance.py +53 -0
  66. marketmyna/core/config.py +377 -0
  67. marketmyna/core/constants.py +69 -0
  68. marketmyna/core/home.py +22 -0
  69. marketmyna/core/numbers.py +28 -0
  70. marketmyna/core/secret_store.py +104 -0
  71. marketmyna/core/tenant.py +130 -0
  72. marketmyna/data/__init__.py +8 -0
  73. marketmyna/data/base.py +36 -0
  74. marketmyna/data/categories.py +272 -0
  75. marketmyna/data/china.py +360 -0
  76. marketmyna/data/crypto.py +200 -0
  77. marketmyna/data/ecb_fx.py +131 -0
  78. marketmyna/data/extra_sources.py +569 -0
  79. marketmyna/data/india.py +241 -0
  80. marketmyna/data/india_filings.py +343 -0
  81. marketmyna/data/listings.py +1314 -0
  82. marketmyna/data/mutual_funds.py +34 -0
  83. marketmyna/data/news.py +98 -0
  84. marketmyna/data/news_search.py +346 -0
  85. marketmyna/data/nse_deals.py +138 -0
  86. marketmyna/data/resilience.py +173 -0
  87. marketmyna/data/sec_facts.py +252 -0
  88. marketmyna/data/shariah.py +283 -0
  89. marketmyna/data/social/__init__.py +9 -0
  90. marketmyna/data/social/base.py +100 -0
  91. marketmyna/data/social/browser.py +92 -0
  92. marketmyna/data/social/hackernews.py +53 -0
  93. marketmyna/data/social/reddit.py +397 -0
  94. marketmyna/data/social/stocktwits.py +63 -0
  95. marketmyna/data/social/valuepickr.py +53 -0
  96. marketmyna/data/stocks.py +197 -0
  97. marketmyna/data/yahoo_price.py +48 -0
  98. marketmyna/launcher.py +71 -0
  99. marketmyna/llm/__init__.py +33 -0
  100. marketmyna/llm/backend_factory.py +51 -0
  101. marketmyna/llm/backend_registry.py +19 -0
  102. marketmyna/llm/channels.py +447 -0
  103. marketmyna/llm/json_reply.py +63 -0
  104. marketmyna/llm/litellm_backend.py +197 -0
  105. marketmyna/llm/model_catalog.py +166 -0
  106. marketmyna/llm/model_health.py +193 -0
  107. marketmyna/llm/protocol.py +111 -0
  108. marketmyna/llm/providers.py +128 -0
  109. marketmyna/llm/store.py +128 -0
  110. marketmyna/notifiers/__init__.py +35 -0
  111. marketmyna/notifiers/apprise_sender.py +43 -0
  112. marketmyna/notifiers/astrbot.py +47 -0
  113. marketmyna/notifiers/bark.py +24 -0
  114. marketmyna/notifiers/base.py +80 -0
  115. marketmyna/notifiers/dingtalk.py +54 -0
  116. marketmyna/notifiers/discord.py +39 -0
  117. marketmyna/notifiers/dispatcher.py +153 -0
  118. marketmyna/notifiers/email.py +99 -0
  119. marketmyna/notifiers/feishu.py +57 -0
  120. marketmyna/notifiers/googlechat.py +24 -0
  121. marketmyna/notifiers/gotify.py +36 -0
  122. marketmyna/notifiers/matrix.py +28 -0
  123. marketmyna/notifiers/ntfy.py +36 -0
  124. marketmyna/notifiers/pushbullet.py +24 -0
  125. marketmyna/notifiers/pushover.py +41 -0
  126. marketmyna/notifiers/pushplus.py +41 -0
  127. marketmyna/notifiers/serverchan3.py +40 -0
  128. marketmyna/notifiers/settings_schema.py +119 -0
  129. marketmyna/notifiers/signal_messenger.py +25 -0
  130. marketmyna/notifiers/slack.py +36 -0
  131. marketmyna/notifiers/teams.py +37 -0
  132. marketmyna/notifiers/telegram.py +81 -0
  133. marketmyna/notifiers/twilio.py +33 -0
  134. marketmyna/notifiers/webhook.py +44 -0
  135. marketmyna/notifiers/webpush.py +222 -0
  136. marketmyna/notifiers/wechat.py +40 -0
  137. marketmyna/notifiers/whatsapp.py +27 -0
  138. marketmyna/portfolio/__init__.py +6 -0
  139. marketmyna/portfolio/analytics.py +336 -0
  140. marketmyna/portfolio/manager.py +383 -0
  141. marketmyna/portfolio/models.py +155 -0
  142. marketmyna/portfolio/optimizers.py +88 -0
  143. marketmyna/portfolio/review.py +411 -0
  144. marketmyna/services/__init__.py +1 -0
  145. marketmyna/services/accounts.py +278 -0
  146. marketmyna/services/ai_access.py +94 -0
  147. marketmyna/services/analysis_run.py +491 -0
  148. marketmyna/services/big_trades.py +335 -0
  149. marketmyna/services/brokers/__init__.py +0 -0
  150. marketmyna/services/brokers/angel.py +271 -0
  151. marketmyna/services/brokers/finnhub.py +209 -0
  152. marketmyna/services/byok_detect.py +196 -0
  153. marketmyna/services/calendar.py +748 -0
  154. marketmyna/services/central_banks.py +202 -0
  155. marketmyna/services/commodities.py +134 -0
  156. marketmyna/services/compare.py +307 -0
  157. marketmyna/services/cot.py +148 -0
  158. marketmyna/services/country_macro.py +135 -0
  159. marketmyna/services/crude_sensitivity.py +239 -0
  160. marketmyna/services/crypto_mood.py +135 -0
  161. marketmyna/services/crypto_screens.py +226 -0
  162. marketmyna/services/data_export.py +91 -0
  163. marketmyna/services/dcf.py +220 -0
  164. marketmyna/services/dcf_model.py +194 -0
  165. marketmyna/services/delivery.py +142 -0
  166. marketmyna/services/discovery.py +146 -0
  167. marketmyna/services/dividends.py +95 -0
  168. marketmyna/services/earnings.py +305 -0
  169. marketmyna/services/econ_calendar.py +495 -0
  170. marketmyna/services/eia_stocks.py +115 -0
  171. marketmyna/services/estimates.py +109 -0
  172. marketmyna/services/etf.py +236 -0
  173. marketmyna/services/etf_rotation.py +193 -0
  174. marketmyna/services/etf_screens.py +196 -0
  175. marketmyna/services/financials.py +639 -0
  176. marketmyna/services/fund_ai.py +181 -0
  177. marketmyna/services/fund_analytics.py +371 -0
  178. marketmyna/services/fund_cost.py +147 -0
  179. marketmyna/services/fund_holdings.py +664 -0
  180. marketmyna/services/fund_peers.py +247 -0
  181. marketmyna/services/fund_rotation.py +206 -0
  182. marketmyna/services/fund_style.py +179 -0
  183. marketmyna/services/funds.py +70 -0
  184. marketmyna/services/hot_stocks.py +358 -0
  185. marketmyna/services/india_flows.py +263 -0
  186. marketmyna/services/india_rates.py +258 -0
  187. marketmyna/services/investor_lenses.py +271 -0
  188. marketmyna/services/leadership.py +165 -0
  189. marketmyna/services/live_market.py +497 -0
  190. marketmyna/services/live_quotes.py +295 -0
  191. marketmyna/services/macro.py +182 -0
  192. marketmyna/services/market_calendar.py +399 -0
  193. marketmyna/services/market_mood.py +168 -0
  194. marketmyna/services/market_valuation.py +231 -0
  195. marketmyna/services/mf_holdings.py +199 -0
  196. marketmyna/services/mf_insights.py +132 -0
  197. marketmyna/services/mf_lumpsum.py +587 -0
  198. marketmyna/services/mf_portfolio_ai.py +195 -0
  199. marketmyna/services/nse_events.py +98 -0
  200. marketmyna/services/overview.py +366 -0
  201. marketmyna/services/ownership.py +143 -0
  202. marketmyna/services/quality_scores.py +199 -0
  203. marketmyna/services/rates.py +335 -0
  204. marketmyna/services/reconcile.py +343 -0
  205. marketmyna/services/report.py +584 -0
  206. marketmyna/services/report_i18n.py +678 -0
  207. marketmyna/services/rotation.py +224 -0
  208. marketmyna/services/runtime_scheduler.py +548 -0
  209. marketmyna/services/schedule_config.py +251 -0
  210. marketmyna/services/schedule_time.py +39 -0
  211. marketmyna/services/screens.py +398 -0
  212. marketmyna/services/sec_mdna.py +173 -0
  213. marketmyna/services/shariah.py +246 -0
  214. marketmyna/services/sip.py +148 -0
  215. marketmyna/services/sip_days.py +362 -0
  216. marketmyna/services/smart_file.py +595 -0
  217. marketmyna/services/smart_pdf.py +425 -0
  218. marketmyna/services/smart_report.py +370 -0
  219. marketmyna/services/social_sentiment.py +511 -0
  220. marketmyna/services/thesis.py +328 -0
  221. marketmyna/services/translate.py +146 -0
  222. marketmyna/services/valuation.py +173 -0
  223. marketmyna/services/worker_pool.py +149 -0
  224. marketmyna/services/world_markets.py +208 -0
  225. marketmyna/storage.py +722 -0
  226. marketmyna/strategies/README.md +51 -0
  227. marketmyna/strategies/bottom_volume.yaml +49 -0
  228. marketmyna/strategies/box_oscillation.yaml +69 -0
  229. marketmyna/strategies/bull_trend.yaml +49 -0
  230. marketmyna/strategies/chan_theory.yaml +58 -0
  231. marketmyna/strategies/dragon_head.yaml +44 -0
  232. marketmyna/strategies/emotion_cycle.yaml +79 -0
  233. marketmyna/strategies/event_driven.yaml +54 -0
  234. marketmyna/strategies/expectation_repricing.yaml +54 -0
  235. marketmyna/strategies/growth_quality.yaml +54 -0
  236. marketmyna/strategies/hot_theme.yaml +54 -0
  237. marketmyna/strategies/ma_golden_cross.yaml +46 -0
  238. marketmyna/strategies/one_yang_three_yin.yaml +37 -0
  239. marketmyna/strategies/shrink_pullback.yaml +46 -0
  240. marketmyna/strategies/volume_breakout.yaml +47 -0
  241. marketmyna/strategies/wave_theory.yaml +64 -0
  242. marketmyna/trading/__init__.py +2 -0
  243. marketmyna/trading/alerts.py +356 -0
  244. marketmyna/trading/backtest.py +317 -0
  245. marketmyna/trading/charges.py +82 -0
  246. marketmyna/trading/chart.py +197 -0
  247. marketmyna/trading/indicators.py +303 -0
  248. marketmyna/trading/journal.py +74 -0
  249. marketmyna/trading/knn.py +164 -0
  250. marketmyna/trading/lots.py +109 -0
  251. marketmyna/trading/market_hours.py +66 -0
  252. marketmyna/trading/options.py +554 -0
  253. marketmyna/trading/pairs.py +94 -0
  254. marketmyna/trading/risk.py +170 -0
  255. marketmyna/trading/scanner.py +500 -0
  256. marketmyna/trading/volatility.py +163 -0
  257. marketmyna/utils/__init__.py +1 -0
  258. marketmyna/utils/deadline.py +36 -0
  259. marketmyna/utils/log_redact.py +52 -0
  260. marketmyna/utils/yahoo_gate.py +183 -0
  261. marketmyna/utils/yf_lock.py +12 -0
  262. marketmyna/web/apple-touch-icon.png +0 -0
  263. marketmyna/web/assets/Account-CA9NFDKO.js +1 -0
  264. marketmyna/web/assets/Analyze-uM0srB_U.js +7 -0
  265. marketmyna/web/assets/AreaChart-2i67V6Jf.js +1 -0
  266. marketmyna/web/assets/BarChart-_yngarVl.js +3 -0
  267. marketmyna/web/assets/Bot-E4gpf1qZ.js +1 -0
  268. marketmyna/web/assets/CandleChart-CioqTqMV.js +1 -0
  269. marketmyna/web/assets/Compare-D6_2CPl4.js +1 -0
  270. marketmyna/web/assets/ExportMenu-DaZ1xVmW.js +1 -0
  271. marketmyna/web/assets/LineChart-BaOkD1Ta.js +57 -0
  272. marketmyna/web/assets/Markets-BMw_M1sJ.js +1 -0
  273. marketmyna/web/assets/Portfolio-DQEdHVc2.js +1 -0
  274. marketmyna/web/assets/ReportView-BzhdcqSh.js +1 -0
  275. marketmyna/web/assets/Scheduler-I9PDKxTd.js +1 -0
  276. marketmyna/web/assets/Settings-DpXfHolW.js +3 -0
  277. marketmyna/web/assets/SipDays-C9q3bNSx.js +1 -0
  278. marketmyna/web/assets/SymbolSearch-CBOVPEk1.js +1 -0
  279. marketmyna/web/assets/Trade-_CM7HC4H.js +2 -0
  280. marketmyna/web/assets/TradeChart-ZjsycacC.js +1 -0
  281. marketmyna/web/assets/WatchButton-ComYW56_.js +1 -0
  282. marketmyna/web/assets/Watchlist-DAjLb0jd.js +1 -0
  283. marketmyna/web/assets/app-BP1o7WZS.js +1 -0
  284. marketmyna/web/assets/app-BkIUKdld.js +1 -0
  285. marketmyna/web/assets/app-CKeSYhEt.js +1 -0
  286. marketmyna/web/assets/app-CN1OERYX.js +1 -0
  287. marketmyna/web/assets/app-DIQllha5.js +1 -0
  288. marketmyna/web/assets/app-DeiAW0tX.js +1 -0
  289. marketmyna/web/assets/br-Cu5YU29T.svg +45 -0
  290. marketmyna/web/assets/browser-CR-ks2Fp.js +19 -0
  291. marketmyna/web/assets/browser-D5K_K_gj.js +1 -0
  292. marketmyna/web/assets/common-0wvpAVpd.js +1 -0
  293. marketmyna/web/assets/common-3ghy53Us.js +1 -0
  294. marketmyna/web/assets/common-Bib2unjX.js +1 -0
  295. marketmyna/web/assets/common-BvSJP0w_.js +1 -0
  296. marketmyna/web/assets/common-CSfyC0Sp.js +1 -0
  297. marketmyna/web/assets/common-C_t-iZPf.js +1 -0
  298. marketmyna/web/assets/compare-DMcsx70H.js +1 -0
  299. marketmyna/web/assets/es-d5m8M5h8.svg +544 -0
  300. marketmyna/web/assets/exporter-DiMg2O5d.js +3 -0
  301. marketmyna/web/assets/files-CpgiZRbN.js +1 -0
  302. marketmyna/web/assets/format-d8InM0Zw.js +9 -0
  303. marketmyna/web/assets/html2canvas-Bw0OShmD.js +5 -0
  304. marketmyna/web/assets/index-CKAnq-Or.js +12 -0
  305. marketmyna/web/assets/index-D4U8E6LH.css +2 -0
  306. marketmyna/web/assets/index.es-iBEw7jOv.js +5 -0
  307. marketmyna/web/assets/ir-cCIgaNf6.svg +219 -0
  308. marketmyna/web/assets/jspdf.es.min-C4285IdL.js +79 -0
  309. marketmyna/web/assets/jspdf.plugin.autotable-2eOJeXl-.js +2 -0
  310. marketmyna/web/assets/lightweight-charts.production-BF2noug5.js +1 -0
  311. marketmyna/web/assets/listings-BefgXpb1.js +1 -0
  312. marketmyna/web/assets/liveQuotes-CtSmCUFA.js +1 -0
  313. marketmyna/web/assets/markets-BI4AhI9W.js +1 -0
  314. marketmyna/web/assets/markets-BUHUdYpT.js +1 -0
  315. marketmyna/web/assets/markets-BqvN1Bvf.js +1 -0
  316. marketmyna/web/assets/markets-DPO40TTe.js +1 -0
  317. marketmyna/web/assets/markets-bsiPo-Tk.js +1 -0
  318. marketmyna/web/assets/markets-ff1e792u.js +1 -0
  319. marketmyna/web/assets/montecarlo-BsNlH2qe.js +1 -0
  320. marketmyna/web/assets/options-BYAZnwzW.js +1 -0
  321. marketmyna/web/assets/preload-helper-BLdHbfdN.js +1 -0
  322. marketmyna/web/assets/purify.es-Dqk5qvad.js +3 -0
  323. marketmyna/web/assets/rolldown-runtime-XKyaHtl9.js +1 -0
  324. marketmyna/web/assets/sa-Dh79zbT9.svg +25 -0
  325. marketmyna/web/assets/toast-DDradTZw.js +1 -0
  326. marketmyna/web/assets/trade-fndw1Ql3.js +1 -0
  327. marketmyna/web/assets/ui-gsFb8B2m.js +1 -0
  328. marketmyna/web/assets/watchlists-B6YqwUIk.js +1 -0
  329. marketmyna/web/brand/logo-mark.webp +0 -0
  330. marketmyna/web/favicon.png +0 -0
  331. marketmyna/web/icon-192.png +0 -0
  332. marketmyna/web/icon-512.png +0 -0
  333. marketmyna/web/index.html +34 -0
  334. marketmyna/web/manifest.webmanifest +28 -0
  335. marketmyna/web/robots.txt +2 -0
  336. marketmyna/web/sw.js +40 -0
  337. marketmyna/web/theme-init.js +37 -0
  338. marketmyna-0.2.0.dist-info/METADATA +345 -0
  339. marketmyna-0.2.0.dist-info/RECORD +344 -0
  340. marketmyna-0.2.0.dist-info/WHEEL +5 -0
  341. marketmyna-0.2.0.dist-info/entry_points.txt +2 -0
  342. marketmyna-0.2.0.dist-info/licenses/LICENSE +202 -0
  343. marketmyna-0.2.0.dist-info/licenses/NOTICE +22 -0
  344. marketmyna-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,37 @@
1
+ # One Yang Three Yin Strategy / 一阳夹三阴
2
+ # K-line pattern: bullish → 3 small bearish → bullish. Consolidation end signal.
3
+
4
+ name: one_yang_three_yin
5
+ display_name: 一阳夹三阴
6
+ description: 检测一阳夹三阴K线整理形态,趋势延续入场信号。
7
+ category: pattern
8
+ core_rules: [2, 4]
9
+ required_tools:
10
+ - fetch_history
11
+ - technical_summary
12
+ aliases: [一阳穿三阴, 一阳夹三阴]
13
+ default_priority: 110
14
+
15
+ instructions: |
16
+ **一阳夹三阴(One Yang Three Yin Strategy)**
17
+
18
+ 形态定义(最近 5 个交易日):
19
+
20
+ 1. **第1日**:大阳线(收盘价 > 开盘价,实体 > 股价的 2%)。
21
+ 2. **第2-4日**:连续三根阴线或小K线:
22
+ - 每根K线最低价不跌破第1日开盘价。
23
+ - 成交量应逐步萎缩(量比 < 0.8)。
24
+ - 三根K线应收在第1日实体范围内。
25
+ 3. **第5日**:又一根阳线,收盘价突破第1日收盘价。
26
+
27
+ 如何使用工具评估:
28
+
29
+ 1. 使用 `get_daily_history` 获取最近 10 日数据。
30
+ 2. 检查最后 5 根K线是否符合上述形态。
31
+ 3. 使用 `analyze_trend` 确认多头排列(关联理念2:趋势交易,MA5 > MA10 > MA20)。
32
+
33
+ 评分调整:
34
+ - 形态成立 + 趋势看多:sentiment_score +15
35
+ - 形态成立但趋势不明:sentiment_score +5
36
+ - 在 `pattern_analysis` 和 `buy_reason` 中注明"一阳夹三阴"。
37
+ - 理想买点设在第5日收盘价附近,止损设在第1日开盘价下方。
@@ -0,0 +1,46 @@
1
+ # Shrink Volume Pullback Strategy / 缩量回踩
2
+ # Volume shrinks during pullback to MA5/MA10, then price bounces.
3
+
4
+ name: shrink_pullback
5
+ display_name: 缩量回踩
6
+ description: 检测缩量回踩均线支撑信号,趋势延续的理想入场点。
7
+ category: trend
8
+ core_rules: [1, 2, 4]
9
+ required_tools:
10
+ - fetch_history
11
+ - technical_summary
12
+ - fetch_quote
13
+ aliases: [缩量回踩, 回踩]
14
+ default_router: true
15
+ default_priority: 40
16
+ market_regimes: [trending_down, sideways]
17
+
18
+ instructions: |
19
+ **缩量回踩(Shrink Volume Pullback Strategy)**
20
+
21
+ 入场判定标准:
22
+
23
+ 1. **前提条件**(关联理念2:趋势交易):
24
+ - 股票必须处于上升趋势(MA5 > MA10 > MA20)。
25
+ - 使用 `analyze_trend` 确认多头排列。
26
+
27
+ 2. **回踩检测**(关联理念4:买点偏好):
28
+ - 使用 `get_daily_history` 和 `get_realtime_quote`。
29
+ - 价格回踩至 MA5 附近(误差 1% 以内)或 MA10 附近(误差 2% 以内)。
30
+ - 回调期间成交量 < 5 日均量的 70%(缩量特征)。
31
+ - `analyze_trend` → volume_status 应显示缩量。
32
+
33
+ 3. **反弹信号**(关联理念1:严进策略):
34
+ - 当前价格守住均线支撑位。
35
+ - MA5 乖离率 < 2% — 最佳买入区间。
36
+
37
+ 4. **确认条件**(关联理念5:风险排查):
38
+ - `search_stock_news` 无利空消息。
39
+ - 筹码分布健康(获利比例 50-80%)。
40
+
41
+ 评分调整:
42
+ - 缩量回踩 MA5:sentiment_score +10
43
+ - 缩量回踩 MA10 且量能 < 0.6 倍均量:sentiment_score +8
44
+ - 理想买点设在 MA5 水平,次优买点设在 MA10。
45
+ - 止损设在 MA20 水平。
46
+ - 在 `buy_reason` 中注明"缩量回踩"。
@@ -0,0 +1,47 @@
1
+ # Volume Breakout Strategy / 放量突破
2
+ # Price breaks above resistance on heavy volume (>2x average).
3
+
4
+ name: volume_breakout
5
+ display_name: 放量突破
6
+ description: 检测放量突破阻力位信号。适用于股价接近已知阻力位时。
7
+ category: trend
8
+ core_rules: [1, 2, 3]
9
+ required_tools:
10
+ - fetch_history
11
+ - technical_summary
12
+ - fetch_quote
13
+ aliases: [放量突破, 突破]
14
+ default_priority: 30
15
+ market_regimes: [trending_up]
16
+
17
+ instructions: |
18
+ **放量突破(Volume Breakout Strategy)**
19
+
20
+ 突破判定标准:
21
+
22
+ 1. **阻力位识别**:
23
+ - 使用 `analyze_trend` → resistance_levels 获取阻力位。
24
+ - 通常为 20 日高点或前期震荡平台顶部。
25
+
26
+ 2. **量能确认**(关联理念3:效率优先):
27
+ - 当日成交量 > 5 日均量的 2 倍。
28
+ - 使用 `get_realtime_quote` → volume_ratio > 2.0 确认。
29
+ - 使用 `get_daily_history` 计算均量进行交叉验证。
30
+
31
+ 3. **价格确认**(关联理念1:严进策略):
32
+ - 收盘价必须站上阻力位。
33
+ - 收盘应在当日振幅上方 30%(强势收盘)。
34
+ - 突破后乖离率检查:仍需 < 5%,避免追高。
35
+
36
+ 4. **后续验证**(如有数据):
37
+ - 次日开盘应在突破位之上,区分真突破与假突破。
38
+
39
+ 5. **风险过滤**(关联理念5:风险排查):
40
+ - 通过 `search_stock_news` 检查无重大利空。
41
+ - PE 不应过高(避免泡沫型突破)。
42
+
43
+ 评分调整:
44
+ - 放量突破确认:sentiment_score +12
45
+ - 突破伴随板块共振(板块也走强):额外 +5
46
+ - 理想买点设在突破位附近,止损设在突破位下方 3%。
47
+ - 在 `buy_reason` 和 `volume_analysis` 中注明"放量突破"。
@@ -0,0 +1,64 @@
1
+ # Elliott Wave Theory Strategy / 波浪理论
2
+ # 5-wave impulse + 3-wave corrective pattern analysis for trend and position sizing.
3
+
4
+ name: wave_theory
5
+ display_name: 波浪理论
6
+ description: 基于艾略特波浪理论的推动浪与调整浪结构,判断当前所处浪型与潜在目标价。
7
+ category: framework
8
+ core_rules: [1, 2, 3, 4]
9
+ required_tools:
10
+ - fetch_history
11
+ - technical_summary
12
+ - fetch_quote
13
+ aliases: [波浪, 波浪理论, 艾略特]
14
+ default_priority: 80
15
+ market_regimes: [volatile]
16
+
17
+ instructions: |
18
+ **波浪理论(Elliott Wave Theory)**
19
+
20
+ 核心原则:市场按照 5 浪推进 + 3 浪调整的循环结构运行。
21
+
22
+ ## 分析步骤
23
+
24
+ ### 1. 识别当前浪型
25
+ 使用 `get_daily_history` 获取近 120 日数据,结合 `analyze_trend` 趋势数据:
26
+
27
+ **推动浪(1-3-5)识别特征**:
28
+ - 第1浪:趋势反转的第一波,成交量温和放大。
29
+ - 第3浪:最强劲的推动浪,通常放大量,MACD 强势;绝不是最短浪。
30
+ - 第5浪:量能往往弱于第3浪,出现顶背离则走高后即将结束。
31
+
32
+ **调整浪(A-B-C)识别特征**:
33
+ - A 浪:第一次下跌,成交量较大,多数人以为是回调。
34
+ - B 浪:反弹,力度弱于前期涨幅,成交量萎缩,陷阱风险高。
35
+ - C 浪:第二次下跌,力度往往超过 A 浪,完成调整。
36
+
37
+ ### 2. 黄金位置判断
38
+ - 第2浪回调通常在第1浪的 38.2%~61.8%。
39
+ - 第3浪目标通常是第1浪的 1.618~2.618 倍延伸。
40
+ - 第4浪不得进入第1浪价格区域(违反波浪规则)。
41
+ - C浪目标:A 浪顶端起算,等于或超过 A 浪长度。
42
+
43
+ ### 3. 最优买点
44
+ - **第2浪回调企稳**(黄金坑):最安全买点,止损第1浪起点。
45
+ - **第4浪回调企稳**:次优,止损第1浪顶部。
46
+ - **第3浪初期突破**:放量突破第1浪高点时。
47
+ - 避免在第5浪末端追高(顶背离风险)。
48
+
49
+ ### 4. 风险提示
50
+ - B浪反弹不宜重仓(陷阱性质)。
51
+ - 波浪计数存在主观性,需结合其他技术指标验证。
52
+ - 若波浪规则被违反(如第4浪侵入第1浪),需重新归数。
53
+
54
+ ### 5. 输出要求
55
+ - 给出当前可能的浪型位置(如:"处于第3浪中段"或"疑似第4浪调整")。
56
+ - 给出关键斐波那契支撑/阻力位(0.382/0.618/1.618)。
57
+ - 说明当前是买入时机、等待机会还是规避。
58
+ - 标注波浪计数的置信度(高/中/低)。
59
+
60
+ 评分调整建议:
61
+ - 第2浪底部企稳(黄金坑):`sentiment_score +15`
62
+ - 第3浪突破确认:`sentiment_score +12`
63
+ - 第5浪末端/顶背离:`sentiment_score -10`
64
+ - C浪下跌中:`sentiment_score -12`
@@ -0,0 +1,2 @@
1
+ """Trade section: intraday charts, price alerts, position sizing and the
2
+ trade journal. Read-only - nothing here places orders."""
@@ -0,0 +1,356 @@
1
+ """Price / indicator alerts for traders.
2
+
3
+ AlertMonitor polls quotes for symbols with active alerts (only while their
4
+ market is open), evaluates each alert and sends the ones that fire through
5
+ the NotificationDispatcher's "alert" route (Telegram, push, ...).
6
+
7
+ Firing rules:
8
+ - one-shot alert: fires once, then status "triggered";
9
+ - repeating alert: fires, then re-arms only after the condition has been
10
+ false again (so a price hovering on a level doesn't spam), and never
11
+ more often than its cooldown.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ import threading
18
+ from datetime import datetime, timedelta
19
+ from typing import Callable, Optional
20
+ from zoneinfo import ZoneInfo
21
+
22
+ import pandas as pd
23
+
24
+ from ..utils.deadline import call_with_deadline
25
+ from . import indicators as ind
26
+ from .market_hours import is_open, timezone_of
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ # kind -> (label, value unit, needs daily history)
31
+ KINDS: dict[str, tuple[str, str, bool]] = {
32
+ "price_above": ("Price rises to or above", "price", False),
33
+ "price_below": ("Price falls to or below", "price", False),
34
+ "pct_up": ("Up today by at least", "%", False),
35
+ "pct_down": ("Down today by at least", "%", False),
36
+ "rsi_above": ("Daily RSI(14) at or above", "rsi", True),
37
+ "rsi_below": ("Daily RSI(14) at or below", "rsi", True),
38
+ "volume_spike": ("Volume today at least", "x avg", True),
39
+ "high_breakout": ("Breaks above the N-day high", "days", True),
40
+ "low_breakdown": ("Breaks below the N-day low", "days", True),
41
+ }
42
+
43
+ DEFAULT_POLL_SECONDS = 60
44
+ DAILY_TTL = timedelta(minutes=30)
45
+
46
+
47
+ def _completed_days(daily: pd.DataFrame, market: str, now: datetime) -> pd.DataFrame:
48
+ """Daily bars without today's (still forming) bar."""
49
+ if daily.empty:
50
+ return daily
51
+ last = pd.DatetimeIndex(daily.index)[-1]
52
+ today = now.astimezone(ZoneInfo(timezone_of(market))).date()
53
+ return daily.iloc[:-1] if last.date() >= today else daily
54
+
55
+
56
+ def measure(kind: str, value: float, quote: dict, daily: Optional[pd.DataFrame], market: str,
57
+ now: datetime) -> tuple[Optional[float], bool, str]:
58
+ """(observed number, condition met, human description of the observation)."""
59
+ price = quote.get("price")
60
+ if price is None:
61
+ return None, False, "no price"
62
+ if kind == "price_above":
63
+ return price, price >= value, f"price {price:,.2f}"
64
+ if kind == "price_below":
65
+ return price, price <= value, f"price {price:,.2f}"
66
+ change = quote.get("change_percent")
67
+ if kind in ("pct_up", "pct_down"):
68
+ if change is None:
69
+ return None, False, "no day change"
70
+ met = change >= abs(value) if kind == "pct_up" else change <= -abs(value)
71
+ return change, met, f"{change:+.2f}% today"
72
+ if daily is None or daily.empty:
73
+ return None, False, "no history"
74
+ past = _completed_days(daily, market, now)
75
+ if kind in ("rsi_above", "rsi_below"):
76
+ closes = pd.concat([past["close"].astype(float), pd.Series([float(price)])], ignore_index=True)
77
+ r = ind.rsi(closes).iloc[-1]
78
+ if pd.isna(r):
79
+ return None, False, "not enough history"
80
+ met = r >= value if kind == "rsi_above" else r <= value
81
+ return float(r), met, f"RSI {r:.1f}"
82
+ if kind == "volume_spike":
83
+ avg = float(past["volume"].tail(20).mean()) if "volume" in past else 0.0
84
+ vol = quote.get("volume")
85
+ if not avg or vol is None:
86
+ return None, False, "no volume data"
87
+ ratio = float(vol) / avg
88
+ return ratio, ratio >= value, f"volume {ratio:.1f}x the 20-day average"
89
+ if kind in ("high_breakout", "low_breakdown"):
90
+ n = max(2, int(value))
91
+ window = past.tail(n)
92
+ if len(window) < n:
93
+ return None, False, "not enough history"
94
+ if kind == "high_breakout":
95
+ level = float(window["high"].max())
96
+ return price, price > level, f"price {price:,.2f} vs {n}-day high {level:,.2f}"
97
+ level = float(window["low"].min())
98
+ return price, price < level, f"price {price:,.2f} vs {n}-day low {level:,.2f}"
99
+ return None, False, f"unknown alert kind {kind}"
100
+
101
+
102
+ def describe(kind: str, value: float) -> str:
103
+ label, unit, _ = KINDS.get(kind, (kind, "", False))
104
+ if unit == "price":
105
+ return f"{label} {value:,.2f}"
106
+ if unit == "%":
107
+ return f"{label} {abs(value):g}%"
108
+ if unit == "x avg":
109
+ return f"{label} {value:g}x its 20-day average"
110
+ if unit == "days":
111
+ return label.replace("N-day", f"{int(value)}-day")
112
+ return f"{label} {value:g}"
113
+
114
+
115
+ def step(alert: dict, met: bool, now: datetime) -> tuple[bool, dict]:
116
+ """Pure state transition -> (fire?, field updates)."""
117
+ updates: dict = {}
118
+ if alert.get("expires_at") and now >= alert["expires_at"]:
119
+ return False, {"status": "expired"}
120
+ if met and alert.get("armed", True):
121
+ last = alert.get("last_triggered_at")
122
+ cooldown = timedelta(minutes=alert.get("cooldown_minutes") or 0)
123
+ if alert.get("repeat") and last and now - last < cooldown:
124
+ return False, updates
125
+ updates.update(last_triggered_at=now, trigger_count=(alert.get("trigger_count") or 0) + 1)
126
+ if alert.get("repeat"):
127
+ updates["armed"] = False
128
+ else:
129
+ updates["status"] = "triggered"
130
+ return True, updates
131
+ if not met and alert.get("repeat") and not alert.get("armed", True):
132
+ updates["armed"] = True
133
+ return False, updates
134
+
135
+
136
+ def _default_quote(symbol: str, market: str) -> dict:
137
+ from ..agent.agents import _resolve_fetcher
138
+
139
+ return _resolve_fetcher(market).get_quote(symbol) or {}
140
+
141
+
142
+ def _default_daily(symbol: str, market: str) -> pd.DataFrame:
143
+ from ..agent.agents import _resolve_fetcher
144
+
145
+ df = _resolve_fetcher(market).get_history(symbol, period="6mo")
146
+ return df.rename(columns=str.lower)
147
+
148
+
149
+ class AlertMonitor:
150
+ def __init__(
151
+ self,
152
+ config,
153
+ poll_seconds: int = DEFAULT_POLL_SECONDS,
154
+ quote_fn: Callable[[str, str], dict] = _default_quote,
155
+ daily_fn: Callable[[str, str], pd.DataFrame] = _default_daily,
156
+ dispatcher=None,
157
+ ):
158
+ self._config = config
159
+ self._poll = max(15, int(poll_seconds))
160
+ self._quote_fn = quote_fn
161
+ self._daily_fn = daily_fn
162
+ self._dispatcher = dispatcher
163
+ self._daily_cache: dict[tuple[str, str], tuple[datetime, pd.DataFrame]] = {}
164
+ self._stop = threading.Event()
165
+ self._thread: Optional[threading.Thread] = None
166
+ self._lock = threading.Lock()
167
+
168
+ # ---- lifecycle
169
+ def start(self) -> None:
170
+ if self._thread and self._thread.is_alive():
171
+ return
172
+ self._stop.clear()
173
+ self._thread = threading.Thread(target=self._loop, name="alert-monitor", daemon=True)
174
+ self._thread.start()
175
+
176
+ def stop(self) -> None:
177
+ self._stop.set()
178
+ if self._thread:
179
+ self._thread.join(timeout=5)
180
+
181
+ def is_running(self) -> bool:
182
+ return bool(self._thread and self._thread.is_alive())
183
+
184
+ def _loop(self) -> None:
185
+ from ..core import tenant
186
+ from ..services.accounts import for_each_user
187
+
188
+ while not self._stop.wait(self._poll):
189
+ try:
190
+ # One pass over every user's alerts (quotes fetched once per
191
+ # symbol); each fired alert is sent in its owner's scope.
192
+ with tenant.system_scope():
193
+ self.tick()
194
+ except Exception:
195
+ logger.exception("Alert check failed")
196
+ # Per-user settings and state: run once per user in accounts mode.
197
+ try:
198
+ from .scanner import run_saved_scans
199
+
200
+ for_each_user(self._config, lambda: run_saved_scans(self._config, dispatcher=self._dispatcher),
201
+ "Saved scan run")
202
+ except Exception:
203
+ logger.exception("Saved scan run failed")
204
+ try:
205
+ from .risk import check_daily_limit
206
+
207
+ for_each_user(self._config, lambda: check_daily_limit(self._config, dispatcher=self._dispatcher),
208
+ "Daily risk check")
209
+ except Exception:
210
+ logger.exception("Daily risk check failed")
211
+ try:
212
+ from ..services.calendar import check_reminders
213
+
214
+ for_each_user(self._config, lambda: check_reminders(self._config, dispatcher=self._dispatcher),
215
+ "Calendar reminder")
216
+ from ..services.calendar import (
217
+ check_earnings_results,
218
+ check_event_alerts,
219
+ check_results,
220
+ )
221
+ from ..services.thesis import check_alerts as check_theses
222
+
223
+ for_each_user(self._config, lambda: check_theses(self._config, dispatcher=self._dispatcher),
224
+ "Thesis check")
225
+
226
+ for_each_user(self._config, lambda: check_earnings_results(self._config, dispatcher=self._dispatcher),
227
+ "Results are out")
228
+
229
+ for_each_user(self._config, lambda: check_event_alerts(self._config, dispatcher=self._dispatcher),
230
+ "Event alerts")
231
+
232
+ for_each_user(self._config, lambda: check_results(self._config, dispatcher=self._dispatcher),
233
+ "Release results")
234
+ except Exception:
235
+ logger.exception("Calendar reminder failed")
236
+ try:
237
+ from ..data.nse_deals import maybe_refresh
238
+
239
+ maybe_refresh()
240
+ except Exception:
241
+ logger.exception("NSE deals refresh failed")
242
+
243
+ # ---- one pass
244
+ def _db(self):
245
+ from ..storage import DatabaseManager, get_db_url
246
+
247
+ return DatabaseManager.instance(get_db_url(config=self._config))
248
+
249
+ def _daily(self, symbol: str, market: str, now: datetime) -> Optional[pd.DataFrame]:
250
+ key = (symbol, market)
251
+ cached = self._daily_cache.get(key)
252
+ if cached and now - cached[0] < DAILY_TTL:
253
+ return cached[1]
254
+ try:
255
+ df = call_with_deadline(self._daily_fn, 30, symbol, market)
256
+ except Exception as exc:
257
+ logger.warning("Daily history for %s unavailable: %s", symbol, exc)
258
+ return cached[1] if cached else None
259
+ self._daily_cache[key] = (now, df)
260
+ return df
261
+
262
+ def tick(self, now: Optional[datetime] = None, force: bool = False) -> dict:
263
+ """Check every active alert once. `force` ignores market hours (the
264
+ "Check now" button). Returns {"checked": n, "fired": [...], "skipped_closed": n}."""
265
+ from ..storage import PriceAlertEventRecord, PriceAlertRecord
266
+
267
+ with self._lock:
268
+ now = now or datetime.now()
269
+ with self._db().get_session() as session:
270
+ alerts = [
271
+ {c.name: getattr(r, c.name) for c in PriceAlertRecord.__table__.columns}
272
+ for r in session.query(PriceAlertRecord).filter(PriceAlertRecord.status == "active")
273
+ ]
274
+ groups: dict[tuple[str, str], list[dict]] = {}
275
+ for a in alerts:
276
+ groups.setdefault((a["symbol"], a["market"]), []).append(a)
277
+
278
+ fired, updates, skipped = [], {}, 0
279
+ aware_now = now.astimezone() if now.tzinfo is None else now
280
+ for (symbol, market), items in groups.items():
281
+ if not force and not is_open(market, aware_now):
282
+ skipped += len(items)
283
+ continue
284
+ try:
285
+ quote = call_with_deadline(self._quote_fn, 20, symbol, market) or {}
286
+ except Exception as exc:
287
+ logger.warning("Quote for %s (%s) failed: %s", symbol, market, exc)
288
+ continue
289
+ daily = None
290
+ if any(KINDS.get(a["kind"], ("", "", False))[2] for a in items):
291
+ daily = self._daily(symbol, market, now)
292
+ for a in items:
293
+ observed, met, text = measure(a["kind"], a["value"], quote, daily, market, aware_now)
294
+ fire, change = step(a, met, now)
295
+ change.update(last_checked_at=now, last_value=observed)
296
+ updates[a["id"]] = change
297
+ if fire:
298
+ fired.append({"alert": a, "quote": quote, "observed": text})
299
+
300
+ deliveries = [self._notify_as_owner(f) for f in fired]
301
+ with self._db().get_session() as session:
302
+ for alert_id, change in updates.items():
303
+ rec = session.get(PriceAlertRecord, alert_id)
304
+ if rec is not None:
305
+ for k, v in change.items():
306
+ setattr(rec, k, v)
307
+ for f, (message, delivery) in zip(fired, deliveries):
308
+ a = f["alert"]
309
+ session.add(PriceAlertEventRecord(
310
+ user_id=a.get("user_id"), alert_id=a["id"], symbol=a["symbol"], market=a["market"], kind=a["kind"],
311
+ message=message, price=f["quote"].get("price"), delivery=delivery, created_at=now,
312
+ ))
313
+ return {
314
+ "checked": sum(len(v) for v in groups.values()) - skipped,
315
+ "skipped_closed": skipped,
316
+ "fired": [{"id": f["alert"]["id"], "symbol": f["alert"]["symbol"], "observed": f["observed"]} for f in fired],
317
+ }
318
+
319
+ def _notify_as_owner(self, f: dict) -> tuple[str, dict]:
320
+ """In accounts mode, deliver as the alert's owner so it reaches only
321
+ their channels (the monitor itself runs in the system scope)."""
322
+ from ..core import tenant
323
+
324
+ user_id = f["alert"].get("user_id")
325
+ if not tenant.enabled() or user_id is None:
326
+ return self._notify(f)
327
+ with tenant.user_scope(user_id):
328
+ return self._notify(f)
329
+
330
+ def _notify(self, f: dict) -> tuple[str, dict]:
331
+ from ..services.report import CURRENCY_SYMBOLS
332
+
333
+ a, q = f["alert"], f["quote"]
334
+ cur = CURRENCY_SYMBOLS.get(a["market"], "")
335
+ price = q.get("price")
336
+ change = q.get("change_percent")
337
+ head = f"🔔 {a['symbol']}"
338
+ if price is not None:
339
+ head += f" {cur}{price:,.2f}"
340
+ if change is not None:
341
+ head += f" ({change:+.2f}%)"
342
+ lines = [head, f"{describe(a['kind'], a['value'])}: {f['observed']}"]
343
+ if a.get("note"):
344
+ lines.append(f"📝 {a['note']}")
345
+ if a.get("repeat"):
346
+ lines.append("Repeating alert: re-arms once the condition clears.")
347
+ message = "\n".join(lines)
348
+ try:
349
+ from ..notifiers.dispatcher import NotificationDispatcher
350
+
351
+ dispatcher = self._dispatcher or NotificationDispatcher(self._config)
352
+ delivery = dispatcher.send_all(message, title=f"Alert: {a['symbol']}", purpose="alert")
353
+ except Exception as exc:
354
+ logger.error("Alert notification failed: %s", exc)
355
+ delivery = {}
356
+ return message, delivery