opencandle 0.4.0 → 0.6.0

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 (564) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +186 -117
  3. package/dist/analysts/contracts.d.ts +1 -3
  4. package/dist/analysts/contracts.js +1 -11
  5. package/dist/analysts/contracts.js.map +1 -1
  6. package/dist/analysts/orchestrator.d.ts +1 -3
  7. package/dist/analysts/orchestrator.js +1 -26
  8. package/dist/analysts/orchestrator.js.map +1 -1
  9. package/dist/cli.js +32 -8
  10. package/dist/cli.js.map +1 -1
  11. package/dist/config.d.ts +19 -3
  12. package/dist/config.js +69 -3
  13. package/dist/config.js.map +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/infra/browser.d.ts +1 -3
  18. package/dist/infra/browser.js +4 -2
  19. package/dist/infra/browser.js.map +1 -1
  20. package/dist/infra/cache.d.ts +8 -11
  21. package/dist/infra/cache.js +17 -15
  22. package/dist/infra/cache.js.map +1 -1
  23. package/dist/infra/http-client.d.ts +4 -1
  24. package/dist/infra/http-client.js +59 -6
  25. package/dist/infra/http-client.js.map +1 -1
  26. package/dist/infra/index.d.ts +3 -3
  27. package/dist/infra/index.js +3 -3
  28. package/dist/infra/index.js.map +1 -1
  29. package/dist/infra/native-dependencies.js +2 -2
  30. package/dist/infra/native-dependencies.js.map +1 -1
  31. package/dist/infra/node-version.js.map +1 -1
  32. package/dist/infra/opencandle-paths.d.ts +0 -3
  33. package/dist/infra/opencandle-paths.js +4 -11
  34. package/dist/infra/opencandle-paths.js.map +1 -1
  35. package/dist/infra/rate-limiter.d.ts +4 -0
  36. package/dist/infra/rate-limiter.js +17 -10
  37. package/dist/infra/rate-limiter.js.map +1 -1
  38. package/dist/market-state/alert-conditions.d.ts +34 -0
  39. package/dist/market-state/alert-conditions.js +23 -0
  40. package/dist/market-state/alert-conditions.js.map +1 -0
  41. package/dist/market-state/alert-runner.d.ts +55 -0
  42. package/dist/market-state/alert-runner.js +634 -0
  43. package/dist/market-state/alert-runner.js.map +1 -0
  44. package/dist/market-state/daily-report.d.ts +26 -0
  45. package/dist/market-state/daily-report.js +179 -0
  46. package/dist/market-state/daily-report.js.map +1 -0
  47. package/dist/market-state/local-automation-service.d.ts +25 -0
  48. package/dist/market-state/local-automation-service.js +119 -0
  49. package/dist/market-state/local-automation-service.js.map +1 -0
  50. package/dist/market-state/notification-delivery.d.ts +14 -0
  51. package/dist/market-state/notification-delivery.js +139 -0
  52. package/dist/market-state/notification-delivery.js.map +1 -0
  53. package/dist/market-state/resolve-for-mutation.d.ts +10 -0
  54. package/dist/market-state/resolve-for-mutation.js +15 -0
  55. package/dist/market-state/resolve-for-mutation.js.map +1 -0
  56. package/dist/market-state/resolve.d.ts +14 -0
  57. package/dist/market-state/resolve.js +89 -0
  58. package/dist/market-state/resolve.js.map +1 -0
  59. package/dist/market-state/service.d.ts +527 -0
  60. package/dist/market-state/service.js +1099 -0
  61. package/dist/market-state/service.js.map +1 -0
  62. package/dist/memory/index.d.ts +7 -7
  63. package/dist/memory/index.js +6 -6
  64. package/dist/memory/index.js.map +1 -1
  65. package/dist/memory/manager.d.ts +9 -0
  66. package/dist/memory/manager.js +39 -22
  67. package/dist/memory/manager.js.map +1 -1
  68. package/dist/memory/retrieval.js +7 -4
  69. package/dist/memory/retrieval.js.map +1 -1
  70. package/dist/memory/sqlite.js +385 -3
  71. package/dist/memory/sqlite.js.map +1 -1
  72. package/dist/memory/storage.d.ts +3 -2
  73. package/dist/memory/storage.js +1 -2
  74. package/dist/memory/storage.js.map +1 -1
  75. package/dist/memory/tool-defaults.js +64 -28
  76. package/dist/memory/tool-defaults.js.map +1 -1
  77. package/dist/memory/types.js +4 -0
  78. package/dist/memory/types.js.map +1 -1
  79. package/dist/monitor.d.ts +2 -0
  80. package/dist/monitor.js +104 -0
  81. package/dist/monitor.js.map +1 -0
  82. package/dist/onboarding/connect.js +4 -6
  83. package/dist/onboarding/connect.js.map +1 -1
  84. package/dist/onboarding/credential-interceptor.js +1 -1
  85. package/dist/onboarding/credential-interceptor.js.map +1 -1
  86. package/dist/onboarding/degradation-accumulator.js +1 -3
  87. package/dist/onboarding/degradation-accumulator.js.map +1 -1
  88. package/dist/onboarding/providers.js +3 -16
  89. package/dist/onboarding/providers.js.map +1 -1
  90. package/dist/onboarding/state.js.map +1 -1
  91. package/dist/onboarding/tool-helpers.js +1 -1
  92. package/dist/onboarding/tool-helpers.js.map +1 -1
  93. package/dist/onboarding/tool-tags.js +6 -4
  94. package/dist/onboarding/tool-tags.js.map +1 -1
  95. package/dist/onboarding/validation.js +1 -1
  96. package/dist/onboarding/validation.js.map +1 -1
  97. package/dist/pi/opencandle-extension.d.ts +8 -0
  98. package/dist/pi/opencandle-extension.js +637 -59
  99. package/dist/pi/opencandle-extension.js.map +1 -1
  100. package/dist/pi/session.d.ts +1 -1
  101. package/dist/pi/session.js +3 -1
  102. package/dist/pi/session.js.map +1 -1
  103. package/dist/pi/setup.js +17 -2
  104. package/dist/pi/setup.js.map +1 -1
  105. package/dist/pi/tool-adapter.js +5 -2
  106. package/dist/pi/tool-adapter.js.map +1 -1
  107. package/dist/prompts/context-builder.d.ts +18 -3
  108. package/dist/prompts/context-builder.js +117 -18
  109. package/dist/prompts/context-builder.js.map +1 -1
  110. package/dist/prompts/disclaimer.js +1 -1
  111. package/dist/prompts/disclaimer.js.map +1 -1
  112. package/dist/prompts/policy-cards.d.ts +13 -0
  113. package/dist/prompts/policy-cards.js +197 -0
  114. package/dist/prompts/policy-cards.js.map +1 -0
  115. package/dist/prompts/sections.d.ts +1 -1
  116. package/dist/prompts/sections.js +3 -3
  117. package/dist/prompts/sections.js.map +1 -1
  118. package/dist/prompts/symbol-preflight.d.ts +20 -0
  119. package/dist/prompts/symbol-preflight.js +49 -0
  120. package/dist/prompts/symbol-preflight.js.map +1 -0
  121. package/dist/prompts/workflow-prompts.d.ts +1 -1
  122. package/dist/prompts/workflow-prompts.js +209 -19
  123. package/dist/prompts/workflow-prompts.js.map +1 -1
  124. package/dist/providers/alpha-vantage.d.ts +1 -1
  125. package/dist/providers/alpha-vantage.js +49 -8
  126. package/dist/providers/alpha-vantage.js.map +1 -1
  127. package/dist/providers/coingecko.js +1 -1
  128. package/dist/providers/coingecko.js.map +1 -1
  129. package/dist/providers/errors.d.ts +5 -0
  130. package/dist/providers/errors.js +11 -0
  131. package/dist/providers/errors.js.map +1 -0
  132. package/dist/providers/exa-search.d.ts +2 -2
  133. package/dist/providers/exa-search.js +19 -11
  134. package/dist/providers/exa-search.js.map +1 -1
  135. package/dist/providers/fear-greed.js +1 -1
  136. package/dist/providers/fear-greed.js.map +1 -1
  137. package/dist/providers/finnhub.js +3 -5
  138. package/dist/providers/finnhub.js.map +1 -1
  139. package/dist/providers/fred.js +2 -2
  140. package/dist/providers/fred.js.map +1 -1
  141. package/dist/providers/index.d.ts +7 -6
  142. package/dist/providers/index.js +6 -5
  143. package/dist/providers/index.js.map +1 -1
  144. package/dist/providers/reddit.js +2 -2
  145. package/dist/providers/reddit.js.map +1 -1
  146. package/dist/providers/sec-edgar.d.ts +9 -1
  147. package/dist/providers/sec-edgar.js +181 -6
  148. package/dist/providers/sec-edgar.js.map +1 -1
  149. package/dist/providers/tradingview.d.ts +47 -0
  150. package/dist/providers/tradingview.js +275 -0
  151. package/dist/providers/tradingview.js.map +1 -0
  152. package/dist/providers/twitter.js +6 -8
  153. package/dist/providers/twitter.js.map +1 -1
  154. package/dist/providers/web-search.js +26 -12
  155. package/dist/providers/web-search.js.map +1 -1
  156. package/dist/providers/with-fallback.js +4 -2
  157. package/dist/providers/with-fallback.js.map +1 -1
  158. package/dist/providers/wrap-provider.d.ts +2 -3
  159. package/dist/providers/wrap-provider.js +14 -8
  160. package/dist/providers/wrap-provider.js.map +1 -1
  161. package/dist/providers/yahoo-finance.d.ts +3 -1
  162. package/dist/providers/yahoo-finance.js +226 -11
  163. package/dist/providers/yahoo-finance.js.map +1 -1
  164. package/dist/routing/classify-intent.d.ts +9 -0
  165. package/dist/routing/classify-intent.js +153 -3
  166. package/dist/routing/classify-intent.js.map +1 -1
  167. package/dist/routing/defaults.d.ts +1 -1
  168. package/dist/routing/defaults.js +3 -3
  169. package/dist/routing/defaults.js.map +1 -1
  170. package/dist/routing/entity-extractor.d.ts +2 -0
  171. package/dist/routing/entity-extractor.js +377 -26
  172. package/dist/routing/entity-extractor.js.map +1 -1
  173. package/dist/routing/fund-symbols.d.ts +2 -0
  174. package/dist/routing/fund-symbols.js +55 -0
  175. package/dist/routing/fund-symbols.js.map +1 -0
  176. package/dist/routing/horizon.d.ts +1 -0
  177. package/dist/routing/horizon.js +10 -0
  178. package/dist/routing/horizon.js.map +1 -0
  179. package/dist/routing/index.d.ts +12 -6
  180. package/dist/routing/index.js +8 -4
  181. package/dist/routing/index.js.map +1 -1
  182. package/dist/routing/legacy-rule-router.d.ts +9 -0
  183. package/dist/routing/legacy-rule-router.js +12 -0
  184. package/dist/routing/legacy-rule-router.js.map +1 -0
  185. package/dist/routing/planning.d.ts +54 -0
  186. package/dist/routing/planning.js +562 -0
  187. package/dist/routing/planning.js.map +1 -0
  188. package/dist/routing/route-manifest.d.ts +35 -0
  189. package/dist/routing/route-manifest.js +242 -0
  190. package/dist/routing/route-manifest.js.map +1 -0
  191. package/dist/routing/router-llm-client.js.map +1 -1
  192. package/dist/routing/router-prompt.js +46 -45
  193. package/dist/routing/router-prompt.js.map +1 -1
  194. package/dist/routing/router-types.d.ts +10 -0
  195. package/dist/routing/router.d.ts +1 -0
  196. package/dist/routing/router.js +572 -13
  197. package/dist/routing/router.js.map +1 -1
  198. package/dist/routing/slot-resolver.d.ts +1 -1
  199. package/dist/routing/slot-resolver.js +45 -7
  200. package/dist/routing/slot-resolver.js.map +1 -1
  201. package/dist/routing/symbol-disambiguator.d.ts +11 -0
  202. package/dist/routing/symbol-disambiguator.js +52 -0
  203. package/dist/routing/symbol-disambiguator.js.map +1 -0
  204. package/dist/routing/turn-context.d.ts +44 -0
  205. package/dist/routing/turn-context.js +45 -0
  206. package/dist/routing/turn-context.js.map +1 -0
  207. package/dist/routing/types.d.ts +15 -1
  208. package/dist/runtime/answer-contracts.d.ts +82 -0
  209. package/dist/runtime/answer-contracts.js +442 -0
  210. package/dist/runtime/answer-contracts.js.map +1 -0
  211. package/dist/runtime/artifact-contracts.d.ts +14 -0
  212. package/dist/runtime/artifact-contracts.js +57 -0
  213. package/dist/runtime/artifact-contracts.js.map +1 -0
  214. package/dist/runtime/planning-evidence.d.ts +99 -0
  215. package/dist/runtime/planning-evidence.js +466 -0
  216. package/dist/runtime/planning-evidence.js.map +1 -0
  217. package/dist/runtime/prompt-step.d.ts +1 -9
  218. package/dist/runtime/prompt-step.js +0 -10
  219. package/dist/runtime/prompt-step.js.map +1 -1
  220. package/dist/runtime/run-context.d.ts +5 -2
  221. package/dist/runtime/run-context.js +8 -1
  222. package/dist/runtime/run-context.js.map +1 -1
  223. package/dist/runtime/session-coordinator.d.ts +29 -3
  224. package/dist/runtime/session-coordinator.js +204 -31
  225. package/dist/runtime/session-coordinator.js.map +1 -1
  226. package/dist/runtime/session-title.d.ts +14 -0
  227. package/dist/runtime/session-title.js +50 -0
  228. package/dist/runtime/session-title.js.map +1 -0
  229. package/dist/runtime/tool-defaults-wrapper.js +1 -3
  230. package/dist/runtime/tool-defaults-wrapper.js.map +1 -1
  231. package/dist/runtime/validation.js.map +1 -1
  232. package/dist/runtime/workflow-events.js.map +1 -1
  233. package/dist/runtime/workflow-runner.d.ts +3 -3
  234. package/dist/runtime/workflow-runner.js +1 -1
  235. package/dist/runtime/workflow-runner.js.map +1 -1
  236. package/dist/sentiment/adapters/finnhub.d.ts +1 -1
  237. package/dist/sentiment/adapters/finnhub.js +6 -1
  238. package/dist/sentiment/adapters/finnhub.js.map +1 -1
  239. package/dist/sentiment/adapters/reddit.d.ts +2 -2
  240. package/dist/sentiment/adapters/twitter.d.ts +1 -1
  241. package/dist/sentiment/adapters/web.d.ts +1 -1
  242. package/dist/sentiment/index.d.ts +9 -11
  243. package/dist/sentiment/index.js +9 -20
  244. package/dist/sentiment/index.js.map +1 -1
  245. package/dist/sentiment/keywords.js +26 -4
  246. package/dist/sentiment/keywords.js.map +1 -1
  247. package/dist/sentiment/pipeline.d.ts +2 -2
  248. package/dist/sentiment/pipeline.js +1 -1
  249. package/dist/sentiment/pipeline.js.map +1 -1
  250. package/dist/sentiment/scorer.js +1 -1
  251. package/dist/sentiment/store.d.ts +1 -1
  252. package/dist/sentiment/store.js +1 -1
  253. package/dist/sentiment/store.js.map +1 -1
  254. package/dist/sentiment/trends.d.ts +1 -1
  255. package/dist/sentiment/trends.js.map +1 -1
  256. package/dist/sentiment/types.js.map +1 -1
  257. package/dist/system-prompt.js +7 -3
  258. package/dist/system-prompt.js.map +1 -1
  259. package/dist/tool-kit.d.ts +7 -7
  260. package/dist/tool-kit.js +4 -4
  261. package/dist/tool-kit.js.map +1 -1
  262. package/dist/tools/fundamentals/company-overview.js +12 -7
  263. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  264. package/dist/tools/fundamentals/comps.js +19 -10
  265. package/dist/tools/fundamentals/comps.js.map +1 -1
  266. package/dist/tools/fundamentals/dcf.js +24 -12
  267. package/dist/tools/fundamentals/dcf.js.map +1 -1
  268. package/dist/tools/fundamentals/earnings.js +9 -4
  269. package/dist/tools/fundamentals/earnings.js.map +1 -1
  270. package/dist/tools/fundamentals/financials.js +9 -4
  271. package/dist/tools/fundamentals/financials.js.map +1 -1
  272. package/dist/tools/fundamentals/sec-filings.d.ts +1 -0
  273. package/dist/tools/fundamentals/sec-filings.js +36 -4
  274. package/dist/tools/fundamentals/sec-filings.js.map +1 -1
  275. package/dist/tools/index.d.ts +23 -18
  276. package/dist/tools/index.js +53 -38
  277. package/dist/tools/index.js.map +1 -1
  278. package/dist/tools/interaction/ask-user.js +15 -3
  279. package/dist/tools/interaction/ask-user.js.map +1 -1
  280. package/dist/tools/interaction/twitter-login.js +13 -3
  281. package/dist/tools/interaction/twitter-login.js.map +1 -1
  282. package/dist/tools/macro/fear-greed.js +1 -1
  283. package/dist/tools/macro/fear-greed.js.map +1 -1
  284. package/dist/tools/macro/fred-data.d.ts +1 -1
  285. package/dist/tools/macro/fred-data.js +44 -9
  286. package/dist/tools/macro/fred-data.js.map +1 -1
  287. package/dist/tools/market/crypto-history.js +21 -3
  288. package/dist/tools/market/crypto-history.js.map +1 -1
  289. package/dist/tools/market/crypto-price.js +4 -2
  290. package/dist/tools/market/crypto-price.js.map +1 -1
  291. package/dist/tools/market/screen-stocks.d.ts +18 -0
  292. package/dist/tools/market/screen-stocks.js +252 -0
  293. package/dist/tools/market/screen-stocks.js.map +1 -0
  294. package/dist/tools/market/search-ticker.js +161 -9
  295. package/dist/tools/market/search-ticker.js.map +1 -1
  296. package/dist/tools/market/stock-history.d.ts +2 -2
  297. package/dist/tools/market/stock-history.js +27 -8
  298. package/dist/tools/market/stock-history.js.map +1 -1
  299. package/dist/tools/market/stock-quote.js +6 -4
  300. package/dist/tools/market/stock-quote.js.map +1 -1
  301. package/dist/tools/options/greeks.js +1 -2
  302. package/dist/tools/options/greeks.js.map +1 -1
  303. package/dist/tools/options/option-chain.js +27 -9
  304. package/dist/tools/options/option-chain.js.map +1 -1
  305. package/dist/tools/portfolio/alerts.d.ts +15 -0
  306. package/dist/tools/portfolio/alerts.js +357 -0
  307. package/dist/tools/portfolio/alerts.js.map +1 -0
  308. package/dist/tools/portfolio/correlation.d.ts +1 -1
  309. package/dist/tools/portfolio/correlation.js +34 -14
  310. package/dist/tools/portfolio/correlation.js.map +1 -1
  311. package/dist/tools/portfolio/daily-report.d.ts +8 -0
  312. package/dist/tools/portfolio/daily-report.js +83 -0
  313. package/dist/tools/portfolio/daily-report.js.map +1 -0
  314. package/dist/tools/portfolio/holdings-overlap.d.ts +8 -0
  315. package/dist/tools/portfolio/holdings-overlap.js +112 -0
  316. package/dist/tools/portfolio/holdings-overlap.js.map +1 -0
  317. package/dist/tools/portfolio/notifications.d.ts +7 -0
  318. package/dist/tools/portfolio/notifications.js +43 -0
  319. package/dist/tools/portfolio/notifications.js.map +1 -0
  320. package/dist/tools/portfolio/predictions.d.ts +12 -6
  321. package/dist/tools/portfolio/predictions.js +338 -88
  322. package/dist/tools/portfolio/predictions.js.map +1 -1
  323. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  324. package/dist/tools/portfolio/risk-analysis.js +46 -7
  325. package/dist/tools/portfolio/risk-analysis.js.map +1 -1
  326. package/dist/tools/portfolio/tracker.d.ts +4 -3
  327. package/dist/tools/portfolio/tracker.js +247 -102
  328. package/dist/tools/portfolio/tracker.js.map +1 -1
  329. package/dist/tools/portfolio/watchlist.d.ts +6 -4
  330. package/dist/tools/portfolio/watchlist.js +209 -101
  331. package/dist/tools/portfolio/watchlist.js.map +1 -1
  332. package/dist/tools/sentiment/reddit-sentiment.js +24 -11
  333. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  334. package/dist/tools/sentiment/sentiment-summary.js +71 -14
  335. package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
  336. package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
  337. package/dist/tools/sentiment/sentiment-trend.js +12 -2
  338. package/dist/tools/sentiment/sentiment-trend.js.map +1 -1
  339. package/dist/tools/sentiment/twitter-sentiment.js +13 -6
  340. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  341. package/dist/tools/sentiment/untrusted-text.d.ts +2 -0
  342. package/dist/tools/sentiment/untrusted-text.js +17 -0
  343. package/dist/tools/sentiment/untrusted-text.js.map +1 -0
  344. package/dist/tools/sentiment/web-search.js +37 -12
  345. package/dist/tools/sentiment/web-search.js.map +1 -1
  346. package/dist/tools/sentiment/web-sentiment.js +16 -4
  347. package/dist/tools/sentiment/web-sentiment.js.map +1 -1
  348. package/dist/tools/technical/backtest.d.ts +3 -3
  349. package/dist/tools/technical/backtest.js +65 -44
  350. package/dist/tools/technical/backtest.js.map +1 -1
  351. package/dist/tools/technical/indicators.js +24 -8
  352. package/dist/tools/technical/indicators.js.map +1 -1
  353. package/dist/types/index.d.ts +3 -3
  354. package/dist/types/index.js.map +1 -1
  355. package/dist/types/market.d.ts +1 -0
  356. package/dist/types/options.d.ts +10 -0
  357. package/dist/types/portfolio.d.ts +41 -4
  358. package/dist/workflows/compare-assets.d.ts +0 -3
  359. package/dist/workflows/compare-assets.js +55 -10
  360. package/dist/workflows/compare-assets.js.map +1 -1
  361. package/dist/workflows/index.d.ts +3 -4
  362. package/dist/workflows/index.js +3 -3
  363. package/dist/workflows/index.js.map +1 -1
  364. package/dist/workflows/options-screener.d.ts +0 -3
  365. package/dist/workflows/options-screener.js +88 -14
  366. package/dist/workflows/options-screener.js.map +1 -1
  367. package/dist/workflows/portfolio-builder.d.ts +0 -3
  368. package/dist/workflows/portfolio-builder.js +7 -11
  369. package/dist/workflows/portfolio-builder.js.map +1 -1
  370. package/gui/server/ask-user-bridge.ts +82 -0
  371. package/gui/server/automation-heartbeat.ts +97 -0
  372. package/gui/server/background-quotes.ts +97 -1
  373. package/gui/server/chat-event-adapter.ts +32 -10
  374. package/gui/server/chat-run-session.ts +16 -0
  375. package/gui/server/gui-session-manager.ts +5 -0
  376. package/gui/server/invoke-tool.ts +144 -1
  377. package/gui/server/live-chat-event-adapter.ts +21 -6
  378. package/gui/server/market-state-api.ts +315 -0
  379. package/gui/server/model-setup.ts +149 -2
  380. package/gui/server/private-api-access.ts +62 -0
  381. package/gui/server/projector.ts +58 -11
  382. package/gui/server/prompt-observation.ts +58 -0
  383. package/gui/server/quote-snapshot-store.ts +50 -0
  384. package/gui/server/server.ts +236 -376
  385. package/gui/server/session-actions.ts +186 -1
  386. package/gui/server/session-entry-wait.ts +81 -0
  387. package/gui/server/shutdown.ts +47 -0
  388. package/gui/server/tool-invoke-ack.ts +49 -0
  389. package/gui/server/tool-metadata.ts +23 -10
  390. package/gui/server/websocket.ts +13 -3
  391. package/gui/server/writer-lock.ts +6 -2
  392. package/gui/server/ws-hub.ts +292 -0
  393. package/gui/shared/chat-events.ts +16 -1
  394. package/gui/shared/event-reducer.ts +24 -6
  395. package/gui/web/dist/assets/CatalogOverlay-eJ2cBk33.js +1 -0
  396. package/gui/web/dist/assets/index-2KZtKBmu.css +1 -0
  397. package/gui/web/dist/assets/index-CveNgtDg.js +69 -0
  398. package/gui/web/dist/index.html +2 -2
  399. package/package.json +22 -12
  400. package/src/analysts/contracts.ts +10 -23
  401. package/src/analysts/orchestrator.ts +8 -43
  402. package/src/cli.ts +37 -13
  403. package/src/config.ts +99 -7
  404. package/src/index.ts +1 -1
  405. package/src/infra/browser.ts +4 -2
  406. package/src/infra/cache.ts +41 -30
  407. package/src/infra/http-client.ts +72 -6
  408. package/src/infra/index.ts +7 -10
  409. package/src/infra/native-dependencies.ts +8 -3
  410. package/src/infra/node-version.ts +3 -1
  411. package/src/infra/opencandle-paths.ts +3 -14
  412. package/src/infra/rate-limiter.ts +32 -20
  413. package/src/market-state/alert-conditions.ts +82 -0
  414. package/src/market-state/alert-runner.ts +863 -0
  415. package/src/market-state/daily-report.ts +247 -0
  416. package/src/market-state/local-automation-service.ts +162 -0
  417. package/src/market-state/notification-delivery.ts +158 -0
  418. package/src/market-state/resolve-for-mutation.ts +24 -0
  419. package/src/market-state/resolve.ts +112 -0
  420. package/src/market-state/service.ts +2344 -0
  421. package/src/memory/index.ts +7 -7
  422. package/src/memory/manager.ts +57 -26
  423. package/src/memory/retrieval.ts +8 -7
  424. package/src/memory/sqlite.ts +407 -6
  425. package/src/memory/storage.ts +8 -17
  426. package/src/memory/tool-defaults.ts +60 -39
  427. package/src/memory/types.ts +7 -3
  428. package/src/monitor.ts +121 -0
  429. package/src/onboarding/connect.ts +10 -33
  430. package/src/onboarding/credential-interceptor.ts +3 -15
  431. package/src/onboarding/degradation-accumulator.ts +1 -3
  432. package/src/onboarding/providers.ts +9 -40
  433. package/src/onboarding/state.ts +4 -15
  434. package/src/onboarding/tool-helpers.ts +2 -9
  435. package/src/onboarding/tool-tags.ts +6 -6
  436. package/src/onboarding/validation.ts +14 -20
  437. package/src/pi/opencandle-extension.ts +795 -120
  438. package/src/pi/session.ts +7 -5
  439. package/src/pi/setup.ts +61 -33
  440. package/src/pi/tool-adapter.ts +5 -2
  441. package/src/prompts/context-builder.ts +143 -21
  442. package/src/prompts/disclaimer.ts +1 -1
  443. package/src/prompts/policy-cards.ts +220 -0
  444. package/src/prompts/sections.ts +4 -4
  445. package/src/prompts/symbol-preflight.ts +80 -0
  446. package/src/prompts/workflow-prompts.ts +231 -28
  447. package/src/providers/alpha-vantage.ts +82 -40
  448. package/src/providers/coingecko.ts +2 -5
  449. package/src/providers/errors.ts +9 -0
  450. package/src/providers/exa-search.ts +24 -22
  451. package/src/providers/fear-greed.ts +1 -1
  452. package/src/providers/finnhub.ts +7 -6
  453. package/src/providers/fred.ts +3 -3
  454. package/src/providers/index.ts +14 -6
  455. package/src/providers/reddit.ts +17 -6
  456. package/src/providers/sec-edgar.ts +235 -5
  457. package/src/providers/tradingview.ts +399 -0
  458. package/src/providers/twitter.ts +6 -8
  459. package/src/providers/web-search.ts +30 -20
  460. package/src/providers/with-fallback.ts +8 -7
  461. package/src/providers/wrap-provider.ts +15 -10
  462. package/src/providers/yahoo-finance.ts +292 -20
  463. package/src/routing/classify-intent.ts +186 -4
  464. package/src/routing/defaults.ts +4 -4
  465. package/src/routing/entity-extractor.ts +428 -28
  466. package/src/routing/fund-symbols.ts +58 -0
  467. package/src/routing/horizon.ts +7 -0
  468. package/src/routing/index.ts +60 -16
  469. package/src/routing/legacy-rule-router.ts +13 -0
  470. package/src/routing/planning.ts +823 -0
  471. package/src/routing/route-manifest.ts +309 -0
  472. package/src/routing/router-llm-client.ts +4 -4
  473. package/src/routing/router-prompt.ts +52 -52
  474. package/src/routing/router-types.ts +18 -0
  475. package/src/routing/router.ts +717 -20
  476. package/src/routing/slot-resolver.ts +75 -14
  477. package/src/routing/symbol-disambiguator.ts +72 -0
  478. package/src/routing/turn-context.ts +108 -0
  479. package/src/routing/types.ts +15 -1
  480. package/src/runtime/answer-contracts.ts +672 -0
  481. package/src/runtime/artifact-contracts.ts +77 -0
  482. package/src/runtime/planning-evidence.ts +682 -0
  483. package/src/runtime/prompt-step.ts +1 -16
  484. package/src/runtime/run-context.ts +12 -2
  485. package/src/runtime/session-coordinator.ts +297 -56
  486. package/src/runtime/session-title.ts +60 -0
  487. package/src/runtime/tool-defaults-wrapper.ts +1 -3
  488. package/src/runtime/validation.ts +1 -4
  489. package/src/runtime/workflow-events.ts +7 -7
  490. package/src/runtime/workflow-runner.ts +5 -11
  491. package/src/sentiment/adapters/finnhub.ts +7 -2
  492. package/src/sentiment/adapters/reddit.ts +2 -2
  493. package/src/sentiment/adapters/twitter.ts +1 -1
  494. package/src/sentiment/adapters/web.ts +1 -1
  495. package/src/sentiment/index.ts +16 -26
  496. package/src/sentiment/keywords.ts +26 -4
  497. package/src/sentiment/pipeline.ts +15 -4
  498. package/src/sentiment/scorer.ts +1 -1
  499. package/src/sentiment/store.ts +2 -2
  500. package/src/sentiment/trends.ts +9 -3
  501. package/src/sentiment/types.ts +5 -4
  502. package/src/system-prompt.ts +7 -3
  503. package/src/tool-kit.ts +10 -9
  504. package/src/tools/fundamentals/company-overview.ts +20 -10
  505. package/src/tools/fundamentals/comps.ts +69 -56
  506. package/src/tools/fundamentals/dcf.ts +146 -96
  507. package/src/tools/fundamentals/earnings.ts +17 -7
  508. package/src/tools/fundamentals/financials.ts +17 -8
  509. package/src/tools/fundamentals/sec-filings.ts +52 -8
  510. package/src/tools/index.ts +53 -38
  511. package/src/tools/interaction/ask-user.ts +22 -10
  512. package/src/tools/interaction/twitter-login.ts +17 -5
  513. package/src/tools/macro/fear-greed.ts +2 -2
  514. package/src/tools/macro/fred-data.ts +80 -42
  515. package/src/tools/market/crypto-history.ts +25 -4
  516. package/src/tools/market/crypto-price.ts +7 -7
  517. package/src/tools/market/screen-stocks.ts +279 -0
  518. package/src/tools/market/search-ticker.ts +219 -18
  519. package/src/tools/market/stock-history.ts +38 -13
  520. package/src/tools/market/stock-quote.ts +11 -8
  521. package/src/tools/options/greeks.ts +5 -6
  522. package/src/tools/options/option-chain.ts +47 -18
  523. package/src/tools/portfolio/alerts.ts +457 -0
  524. package/src/tools/portfolio/correlation.ts +48 -21
  525. package/src/tools/portfolio/daily-report.ts +101 -0
  526. package/src/tools/portfolio/holdings-overlap.ts +139 -0
  527. package/src/tools/portfolio/notifications.ts +45 -0
  528. package/src/tools/portfolio/predictions.ts +407 -107
  529. package/src/tools/portfolio/risk-analysis.ts +47 -8
  530. package/src/tools/portfolio/tracker.ts +271 -110
  531. package/src/tools/portfolio/watchlist.ts +251 -116
  532. package/src/tools/sentiment/reddit-sentiment.ts +51 -25
  533. package/src/tools/sentiment/sentiment-summary.ts +116 -35
  534. package/src/tools/sentiment/sentiment-trend.ts +24 -7
  535. package/src/tools/sentiment/twitter-sentiment.ts +23 -16
  536. package/src/tools/sentiment/untrusted-text.ts +21 -0
  537. package/src/tools/sentiment/web-search.ts +52 -16
  538. package/src/tools/sentiment/web-sentiment.ts +27 -11
  539. package/src/tools/technical/backtest.ts +78 -47
  540. package/src/tools/technical/indicators.ts +40 -17
  541. package/src/types/index.ts +8 -3
  542. package/src/types/market.ts +1 -0
  543. package/src/types/options.ts +17 -0
  544. package/src/types/portfolio.ts +46 -4
  545. package/src/types/sentiment.ts +2 -2
  546. package/src/workflows/compare-assets.ts +67 -19
  547. package/src/workflows/index.ts +3 -4
  548. package/src/workflows/options-screener.ts +98 -22
  549. package/src/workflows/portfolio-builder.ts +40 -29
  550. package/dist/runtime/index.d.ts +0 -16
  551. package/dist/runtime/index.js +0 -10
  552. package/dist/runtime/index.js.map +0 -1
  553. package/dist/runtime/provider-ids.d.ts +0 -14
  554. package/dist/runtime/provider-ids.js +0 -14
  555. package/dist/runtime/provider-ids.js.map +0 -1
  556. package/dist/workflows/types.d.ts +0 -4
  557. package/dist/workflows/types.js +0 -2
  558. package/dist/workflows/types.js.map +0 -1
  559. package/gui/web/dist/assets/CatalogOverlay-D1ImSJTe.js +0 -1
  560. package/gui/web/dist/assets/index-DBrWq43L.css +0 -1
  561. package/gui/web/dist/assets/index-RflHaj0y.js +0 -67
  562. package/src/runtime/index.ts +0 -55
  563. package/src/runtime/provider-ids.ts +0 -15
  564. package/src/workflows/types.ts +0 -4
@@ -1,13 +1,20 @@
1
- import { Type } from "@sinclair/typebox";
2
1
  import type { AgentTool } from "@earendil-works/pi-agent-core";
3
- import { getHistory } from "../../providers/yahoo-finance.js";
2
+ import { Type } from "@sinclair/typebox";
4
3
  import { wrapProvider } from "../../providers/wrap-provider.js";
4
+ import { getHistory } from "../../providers/yahoo-finance.js";
5
5
  import type { RiskMetrics } from "../../types/portfolio.js";
6
6
 
7
+ const RISK_PERIODS = ["6mo", "1y", "2y"] as const;
8
+
7
9
  const params = Type.Object({
8
10
  symbol: Type.String({ description: "Stock ticker symbol (e.g. AAPL, MSFT, SPY)" }),
9
11
  period: Type.Optional(
10
- Type.String({ description: "Historical period for analysis: 6mo, 1y, 2y. Default: 1y" }),
12
+ Type.Union(
13
+ RISK_PERIODS.map((period) => Type.Literal(period)),
14
+ {
15
+ description: "Historical period for analysis: 6mo, 1y, 2y. Default: 1y",
16
+ },
17
+ ),
11
18
  ),
12
19
  });
13
20
 
@@ -17,13 +24,15 @@ export const riskAnalysisTool: AgentTool<typeof params, RiskMetrics> = {
17
24
  description:
18
25
  "Compute risk metrics for a stock: annualized return, volatility, Sharpe ratio, max drawdown, and Value at Risk (95%). All computed locally from historical data.",
19
26
  parameters: params,
20
- async execute(toolCallId, args) {
27
+ async execute(_toolCallId, args) {
21
28
  const symbol = args.symbol.toUpperCase();
22
29
  const period = args.period ?? "1y";
23
30
  const result = await wrapProvider("yahoo", () => getHistory(symbol, period, "1d"));
24
31
  if (result.status === "unavailable") {
25
32
  return {
26
- content: [{ type: "text", text: `⚠ Risk analysis unavailable for ${symbol} (${result.reason}).` }],
33
+ content: [
34
+ { type: "text", text: `⚠ Risk analysis unavailable for ${symbol} (${result.reason}).` },
35
+ ],
27
36
  details: null as any,
28
37
  };
29
38
  }
@@ -32,12 +41,33 @@ export const riskAnalysisTool: AgentTool<typeof params, RiskMetrics> = {
32
41
 
33
42
  if (closes.length < 30) {
34
43
  return {
35
- content: [{ type: "text", text: `Insufficient data for risk analysis (need 30+ days, got ${closes.length})` }],
44
+ content: [
45
+ {
46
+ type: "text",
47
+ text: `Insufficient data for risk analysis (need 30+ days, got ${closes.length})`,
48
+ },
49
+ ],
36
50
  details: null as any,
37
51
  };
38
52
  }
39
53
 
40
- const metrics = computeRiskMetrics(symbol, closes);
54
+ let metrics: RiskMetrics;
55
+ try {
56
+ metrics = computeRiskMetrics(symbol, closes);
57
+ } catch (error) {
58
+ if (error instanceof Error && error.message === "insufficient usable price history") {
59
+ return {
60
+ content: [
61
+ {
62
+ type: "text",
63
+ text: `Insufficient data for risk analysis (no usable price returns after filtering invalid closes)`,
64
+ },
65
+ ],
66
+ details: null as any,
67
+ };
68
+ }
69
+ throw error;
70
+ }
41
71
 
42
72
  const text = [
43
73
  `**${symbol} Risk Analysis** (${bars[0].date} to ${bars[bars.length - 1].date}, ${closes.length} days)`,
@@ -57,6 +87,9 @@ export const riskAnalysisTool: AgentTool<typeof params, RiskMetrics> = {
57
87
 
58
88
  export function computeRiskMetrics(symbol: string, closes: number[]): RiskMetrics {
59
89
  const dailyReturns = computeDailyReturns(closes);
90
+ if (dailyReturns.length === 0) {
91
+ throw new Error("insufficient usable price history");
92
+ }
60
93
  const avgDailyReturn = mean(dailyReturns);
61
94
  const dailyVol = stddev(dailyReturns);
62
95
 
@@ -84,16 +117,19 @@ export function computeRiskMetrics(symbol: string, closes: number[]): RiskMetric
84
117
  export function computeDailyReturns(prices: number[]): number[] {
85
118
  const returns: number[] = [];
86
119
  for (let i = 1; i < prices.length; i++) {
120
+ if (prices[i - 1] <= 0) continue;
87
121
  returns.push((prices[i] - prices[i - 1]) / prices[i - 1]);
88
122
  }
89
123
  return returns;
90
124
  }
91
125
 
92
126
  export function computeMaxDrawdown(prices: number[]): number {
93
- let peak = prices[0];
127
+ let peak = 0;
94
128
  let maxDd = 0;
95
129
  for (const price of prices) {
130
+ if (price <= 0 && peak <= 0) continue;
96
131
  if (price > peak) peak = price;
132
+ if (peak <= 0) continue;
97
133
  const dd = (peak - price) / peak;
98
134
  if (dd > maxDd) maxDd = dd;
99
135
  }
@@ -101,6 +137,9 @@ export function computeMaxDrawdown(prices: number[]): number {
101
137
  }
102
138
 
103
139
  export function computeVaR(returns: number[], confidence: number): number {
140
+ if (returns.length === 0) {
141
+ throw new Error("insufficient usable price history");
142
+ }
104
143
  const sorted = [...returns].sort((a, b) => a - b);
105
144
  const idx = Math.floor(sorted.length * confidence);
106
145
  return Math.abs(sorted[idx]);
@@ -1,147 +1,308 @@
1
- import { Type } from "@sinclair/typebox";
2
1
  import type { AgentTool } from "@earendil-works/pi-agent-core";
3
- import { readFileSync, writeFileSync, existsSync } from "node:fs";
4
- import { getQuote } from "../../providers/yahoo-finance.js";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { isZeroFilledQuote } from "../../market-state/resolve.js";
4
+ import { resolveInstrumentForMutation } from "../../market-state/resolve-for-mutation.js";
5
+ import { MarketStateService } from "../../market-state/service.js";
6
+ import { initDefaultDatabase } from "../../memory/sqlite.js";
5
7
  import { wrapProvider } from "../../providers/wrap-provider.js";
6
- import type { Position, PortfolioSummary } from "../../types/portfolio.js";
7
- import { ensureParentDir, getPortfolioPath } from "../../infra/opencandle-paths.js";
8
-
9
- function loadPortfolio(): Position[] {
10
- const portfolioPath = getPortfolioPath();
11
- if (!existsSync(portfolioPath)) return [];
12
- try {
13
- return JSON.parse(readFileSync(portfolioPath, "utf-8"));
14
- } catch {
15
- return [];
16
- }
17
- }
18
-
19
- function savePortfolio(positions: Position[]): void {
20
- const portfolioPath = getPortfolioPath();
21
- ensureParentDir(portfolioPath);
22
- writeFileSync(portfolioPath, JSON.stringify(positions, null, 2));
23
- }
8
+ import { getQuote } from "../../providers/yahoo-finance.js";
9
+ import type { PortfolioSummary, Position } from "../../types/portfolio.js";
24
10
 
25
- async function getCurrentPrice(symbol: string): Promise<number | null> {
11
+ async function getCurrentPrice(
12
+ symbol: string,
13
+ ): Promise<
14
+ | { status: "ok"; price: number; currency: string | null }
15
+ | { status: "unavailable"; reason: string }
16
+ > {
26
17
  const result = await wrapProvider("yahoo", () => getQuote(symbol));
27
- if (result.status === "unavailable") return null;
28
- return result.data.price;
18
+ if (result.status === "unavailable") return { status: "unavailable", reason: result.reason };
19
+ if (result.stale) return { status: "unavailable", reason: "provider returned stale market data" };
20
+ if (isZeroFilledQuote(result.data)) {
21
+ return { status: "unavailable", reason: "Yahoo returned no valid market data." };
22
+ }
23
+ return { status: "ok", price: result.data.price, currency: result.data.currency ?? null };
29
24
  }
30
25
 
31
26
  const params = Type.Object({
32
- action: Type.Union([
33
- Type.Literal("add"),
34
- Type.Literal("remove"),
35
- Type.Literal("view"),
36
- ], { description: "Action: add a position, remove a position, or view portfolio" }),
27
+ action: Type.Union(
28
+ [Type.Literal("add"), Type.Literal("update"), Type.Literal("remove"), Type.Literal("view")],
29
+ { description: "Action: add a position, update a lot, remove a position, or view portfolio" },
30
+ ),
31
+ lot_id: Type.Optional(
32
+ Type.Integer({
33
+ minimum: 1,
34
+ description: "Portfolio lot id for precise update or single-lot removal",
35
+ }),
36
+ ),
37
37
  symbol: Type.Optional(
38
- Type.String({ description: "Ticker symbol — stocks (AAPL, MSFT) or crypto with -USD suffix (BTC-USD, ETH-USD, SOL-USD). Use search_ticker to find the right ticker." }),
38
+ Type.String({
39
+ description:
40
+ "Ticker symbol — stocks (AAPL, MSFT) or crypto with -USD suffix (BTC-USD, ETH-USD, SOL-USD). Use search_ticker to find the right ticker.",
41
+ }),
39
42
  ),
40
43
  shares: Type.Optional(
41
- Type.Number({ description: "Number of shares/units (required for add)" }),
44
+ Type.Number({ exclusiveMinimum: 0, description: "Number of shares/units (required for add)" }),
42
45
  ),
43
46
  avg_cost: Type.Optional(
44
- Type.Number({ description: "Average cost per share/unit in USD (required for add)" }),
47
+ Type.Number({
48
+ exclusiveMinimum: 0,
49
+ description: "Average cost per share/unit in the lot currency (required for add)",
50
+ }),
51
+ ),
52
+ currency: Type.Optional(
53
+ Type.String({
54
+ description:
55
+ "Lot currency, such as USD or CAD (defaults to the resolved instrument currency)",
56
+ }),
45
57
  ),
46
58
  });
47
59
 
48
- export const portfolioTrackerTool: AgentTool<typeof params, PortfolioSummary | null> = {
60
+ export const portfolioTrackerTool: AgentTool<typeof params> = {
49
61
  name: "track_portfolio",
50
62
  label: "Portfolio Tracker",
51
63
  description:
52
- "Track your portfolio of stocks and crypto. Add/remove positions with cost basis, or view current holdings with live P&L. For stocks use standard tickers (AAPL, MSFT). For crypto use the -USD suffix (BTC-USD, ETH-USD, SOL-USD). Use search_ticker first if you're unsure of the exact ticker. Data persisted to ~/.opencandle/portfolio.json.",
64
+ "Track your portfolio of stocks and crypto. Add/remove positions with cost basis, or view current holdings with live P&L. For stocks use standard tickers (AAPL, MSFT). For crypto use the -USD suffix (BTC-USD, ETH-USD, SOL-USD). Use search_ticker first if you're unsure of the exact ticker.",
53
65
  parameters: params,
54
- async execute(toolCallId, args) {
55
- const positions = loadPortfolio();
66
+ async execute(_toolCallId, args) {
67
+ const db = initDefaultDatabase();
68
+ const service = new MarketStateService(db);
56
69
 
57
- if (args.action === "add") {
58
- if (!args.symbol || !args.shares || !args.avg_cost) {
59
- throw new Error("symbol, shares, and avg_cost are required for add action.");
60
- }
61
- const symbol = args.symbol.toUpperCase();
62
- const existing = positions.find((p) => p.symbol === symbol);
63
- if (existing) {
64
- const totalShares = existing.shares + args.shares;
65
- existing.avgCost =
66
- (existing.avgCost * existing.shares + args.avg_cost * args.shares) / totalShares;
67
- existing.shares = totalShares;
68
- } else {
69
- positions.push({
70
- symbol,
71
- shares: args.shares,
70
+ try {
71
+ if (args.action === "add") {
72
+ if (!args.symbol || args.shares == null || args.avg_cost == null) {
73
+ throw new Error("symbol, shares, and avg_cost are required for add action.");
74
+ }
75
+ if (args.shares <= 0) throw new Error("shares must be greater than 0.");
76
+ if (args.avg_cost <= 0) throw new Error("avg_cost must be greater than 0.");
77
+ const instrument = await resolveInstrumentForMutation(args.symbol);
78
+ if (instrument.status === "needs_selection") {
79
+ return {
80
+ content: [
81
+ {
82
+ type: "text",
83
+ text: `Could not verify ${instrument.query}. Choose one of the returned candidates before adding it to the portfolio.`,
84
+ },
85
+ ],
86
+ details: instrument,
87
+ };
88
+ }
89
+ const resolvedCurrency = args.currency?.trim() || instrument.instrument.currency?.trim();
90
+ if (!resolvedCurrency) {
91
+ return {
92
+ content: [
93
+ {
94
+ type: "text",
95
+ text: `Could not determine currency for ${instrument.instrument.symbol}. Supply currency explicitly before adding it to the portfolio.`,
96
+ },
97
+ ],
98
+ details: {
99
+ status: "needs_currency",
100
+ symbol: instrument.instrument.symbol,
101
+ },
102
+ };
103
+ }
104
+ const currency = resolvedCurrency.toUpperCase();
105
+ const lot = service.addPortfolioLot({
106
+ instrument: instrument.instrument,
107
+ quantity: args.shares,
72
108
  avgCost: args.avg_cost,
73
- addedAt: new Date().toISOString(),
109
+ currency,
74
110
  });
111
+ return {
112
+ content: [
113
+ {
114
+ type: "text",
115
+ text: `Added ${args.shares} shares of ${lot.symbol} at ${formatMoney(args.avg_cost, lot.currency)}`,
116
+ },
117
+ ],
118
+ details: lot,
119
+ };
75
120
  }
76
- savePortfolio(positions);
77
- return {
78
- content: [{ type: "text", text: `Added ${args.shares} shares of ${symbol} at $${args.avg_cost.toFixed(2)}` }],
79
- details: null,
80
- };
81
- }
82
121
 
83
- if (args.action === "remove") {
84
- if (!args.symbol) {
85
- throw new Error("symbol is required for remove action.");
86
- }
87
- const symbol = args.symbol.toUpperCase();
88
- const idx = positions.findIndex((p) => p.symbol === symbol);
89
- if (idx === -1) {
122
+ if (args.action === "remove") {
123
+ if (args.lot_id != null) {
124
+ const removed = service.removePortfolioLot(args.lot_id);
125
+ if (removed == null) {
126
+ return {
127
+ content: [{ type: "text", text: `lot ${args.lot_id} not found in portfolio` }],
128
+ details: null,
129
+ };
130
+ }
131
+ return {
132
+ content: [
133
+ { type: "text", text: `Removed ${removed.symbol} portfolio lot ${removed.id}` },
134
+ ],
135
+ details: removed,
136
+ };
137
+ }
138
+ if (!args.symbol) {
139
+ throw new Error("lot_id or symbol is required for remove action.");
140
+ }
141
+ const symbol = args.symbol.toUpperCase();
142
+ const removedLots = service.listPortfolioLots().filter((lot) => lot.symbol === symbol);
143
+ if (!service.removePortfolioLotsBySymbol(symbol)) {
144
+ return {
145
+ content: [{ type: "text", text: `${symbol} not found in portfolio` }],
146
+ details: null,
147
+ };
148
+ }
90
149
  return {
91
- content: [{ type: "text", text: `${symbol} not found in portfolio` }],
92
- details: null,
150
+ content: [{ type: "text", text: `Removed ${symbol} from portfolio` }],
151
+ details: {
152
+ symbol,
153
+ removedCount: removedLots.length,
154
+ removedLotIds: removedLots.map((lot) => lot.id),
155
+ instrumentIds: [...new Set(removedLots.map((lot) => lot.instrumentId))],
156
+ },
93
157
  };
94
158
  }
95
- positions.splice(idx, 1);
96
- savePortfolio(positions);
97
- return {
98
- content: [{ type: "text", text: `Removed ${symbol} from portfolio` }],
99
- details: null,
100
- };
101
- }
102
159
 
103
- // View portfolio
104
- if (positions.length === 0) {
105
- return {
106
- content: [{ type: "text", text: "Portfolio is empty. Use add action to add positions." }],
107
- details: null,
108
- };
109
- }
160
+ if (args.action === "update") {
161
+ if (args.lot_id == null) {
162
+ return {
163
+ content: [
164
+ {
165
+ type: "text",
166
+ text: "lot_id is required for update action. Use view to find the lot id before updating a holding.",
167
+ },
168
+ ],
169
+ details: {
170
+ status: "needs_lot_id",
171
+ symbol: args.symbol?.toUpperCase(),
172
+ },
173
+ };
174
+ }
175
+ const updateParams = {
176
+ quantity: args.shares,
177
+ avgCost: args.avg_cost,
178
+ currency: args.currency?.trim() || undefined,
179
+ };
180
+ if (updateParams.quantity != null && updateParams.quantity <= 0) {
181
+ throw new Error("shares must be greater than 0.");
182
+ }
183
+ if (updateParams.avgCost != null && updateParams.avgCost <= 0) {
184
+ throw new Error("avg_cost must be greater than 0.");
185
+ }
186
+ const updated = service.updatePortfolioLot(args.lot_id, updateParams);
187
+ if (updated == null) {
188
+ const target = args.lot_id ? `lot ${args.lot_id}` : args.symbol?.toUpperCase();
189
+ return {
190
+ content: [{ type: "text", text: `${target} not found in portfolio` }],
191
+ details: null,
192
+ };
193
+ }
194
+ return {
195
+ content: [
196
+ { type: "text", text: `Updated ${updated.symbol} portfolio lot ${updated.id}` },
197
+ ],
198
+ details: updated,
199
+ };
200
+ }
110
201
 
111
- const enriched = await Promise.all(
112
- positions.map(async (p) => {
113
- const currentPrice = await getCurrentPrice(p.symbol) ?? p.avgCost;
114
- const marketValue = currentPrice * p.shares;
115
- const totalCost = p.avgCost * p.shares;
202
+ const lots = service.listPortfolioLots();
203
+ if (lots.length === 0) {
116
204
  return {
117
- ...p,
118
- currentPrice,
119
- marketValue,
120
- totalCost,
121
- pnl: marketValue - totalCost,
122
- pnlPercent: ((marketValue - totalCost) / totalCost) * 100,
205
+ content: [{ type: "text", text: "Portfolio is empty. Use add action to add positions." }],
206
+ details: null,
123
207
  };
124
- }),
125
- );
208
+ }
126
209
 
127
- const totalValue = enriched.reduce((s, p) => s + p.marketValue, 0);
128
- const totalCost = enriched.reduce((s, p) => s + p.totalCost, 0);
210
+ const portfolio = service.getDefaultPortfolio();
211
+ const baseCurrency = portfolio.baseCurrency ?? "USD";
212
+ const enriched = await Promise.all(
213
+ lots.map(async (p) => {
214
+ const quote = await getCurrentPrice(p.symbol);
215
+ const totalCost = p.avgCost * p.quantity;
216
+ const lotCurrency = p.currency || baseCurrency;
217
+ const quoteCurrency =
218
+ quote.status === "ok"
219
+ ? (quote.currency ?? p.instrumentCurrency ?? lotCurrency)
220
+ : (p.instrumentCurrency ?? lotCurrency);
221
+ const canValueRow = quote.status === "ok" && quoteCurrency === lotCurrency;
222
+ const currentPrice = canValueRow ? quote.price : null;
223
+ const marketValue = currentPrice == null ? null : currentPrice * p.quantity;
224
+ const canComputePnlPercent = totalCost > 0;
225
+ const includedInTotals = canValueRow && lotCurrency === baseCurrency;
226
+ const exclusionReason =
227
+ quote.status === "unavailable"
228
+ ? `Quote unavailable: ${quote.reason}`
229
+ : includedInTotals
230
+ ? undefined
231
+ : canValueRow
232
+ ? `No FX conversion from ${lotCurrency} to ${baseCurrency}`
233
+ : `No FX conversion from ${quoteCurrency} to ${lotCurrency}`;
234
+ const position: Position = {
235
+ symbol: p.symbol,
236
+ shares: p.quantity,
237
+ avgCost: p.avgCost,
238
+ currency: lotCurrency,
239
+ addedAt: p.createdAt,
240
+ };
241
+ return {
242
+ ...position,
243
+ currentPrice,
244
+ marketValue,
245
+ totalCost,
246
+ pnl: marketValue == null ? null : marketValue - totalCost,
247
+ pnlPercent:
248
+ marketValue == null || !canComputePnlPercent
249
+ ? null
250
+ : ((marketValue - totalCost) / totalCost) * 100,
251
+ includedInTotals,
252
+ quoteStatus: quote.status,
253
+ exclusionReason,
254
+ };
255
+ }),
256
+ );
129
257
 
130
- const summary: PortfolioSummary = {
131
- positions: enriched,
132
- totalValue,
133
- totalCost,
134
- totalPnl: totalValue - totalCost,
135
- totalPnlPercent: totalCost > 0 ? ((totalValue - totalCost) / totalCost) * 100 : 0,
136
- };
258
+ const included = enriched.filter((p) => p.includedInTotals);
259
+ const excludedFromTotals = enriched
260
+ .filter((p) => !p.includedInTotals)
261
+ .map((p) => ({
262
+ symbol: p.symbol,
263
+ currency: p.currency,
264
+ reason: p.exclusionReason ?? `No FX conversion from ${p.currency} to ${baseCurrency}`,
265
+ }));
266
+ const totalValue = included.reduce((s, p) => s + (p.marketValue ?? 0), 0);
267
+ const totalCost = included.reduce((s, p) => s + p.totalCost, 0);
137
268
 
138
- const header = `**Portfolio** — ${enriched.length} positions | Value: $${totalValue.toFixed(2)} | P&L: $${summary.totalPnl.toFixed(2)} (${summary.totalPnlPercent >= 0 ? "+" : ""}${summary.totalPnlPercent.toFixed(2)}%)`;
139
- const rows = enriched.map((p) => {
140
- const sign = p.pnlPercent >= 0 ? "+" : "";
141
- return ` ${p.symbol}: ${p.shares} @ $${p.avgCost.toFixed(2)} → $${p.currentPrice.toFixed(2)} | P&L: $${p.pnl.toFixed(2)} (${sign}${p.pnlPercent.toFixed(2)}%)`;
142
- });
269
+ const summary: PortfolioSummary = {
270
+ positions: enriched,
271
+ baseCurrency,
272
+ totalValue,
273
+ totalCost,
274
+ totalPnl: totalValue - totalCost,
275
+ totalPnlPercent: totalCost > 0 ? ((totalValue - totalCost) / totalCost) * 100 : 0,
276
+ excludedFromTotals,
277
+ };
143
278
 
144
- const text = [header, ...rows].join("\n");
145
- return { content: [{ type: "text", text }], details: summary };
279
+ const header = `**Portfolio** — ${enriched.length} positions | Value: ${formatMoney(totalValue, baseCurrency)} | P&L: ${formatMoney(summary.totalPnl, baseCurrency)} (${summary.totalPnlPercent >= 0 ? "+" : ""}${summary.totalPnlPercent.toFixed(2)}%)`;
280
+ const rows = enriched.map((p) => {
281
+ const excluded = p.includedInTotals
282
+ ? ""
283
+ : ` [excluded from ${baseCurrency} totals: ${p.exclusionReason}]`;
284
+ if (p.currentPrice == null || p.pnl == null || p.pnlPercent == null) {
285
+ return ` ${p.symbol}: ${p.shares} @ ${formatMoney(p.avgCost, p.currency)} → unavailable | P&L: unavailable${excluded}`;
286
+ }
287
+ const sign = p.pnlPercent >= 0 ? "+" : "";
288
+ return ` ${p.symbol}: ${p.shares} @ ${formatMoney(p.avgCost, p.currency)} → ${formatMoney(p.currentPrice, p.currency)} | P&L: ${formatMoney(p.pnl, p.currency)} (${sign}${p.pnlPercent.toFixed(2)}%)${excluded}`;
289
+ });
290
+
291
+ const exclusions =
292
+ excludedFromTotals.length === 0
293
+ ? []
294
+ : [
295
+ `Excluded from ${baseCurrency} totals: ${excludedFromTotals.map((p) => `${p.symbol} (${p.currency})`).join(", ")}`,
296
+ ];
297
+ const text = [header, ...rows, ...exclusions].join("\n");
298
+ return { content: [{ type: "text", text }], details: summary };
299
+ } finally {
300
+ db.close();
301
+ }
146
302
  },
147
303
  };
304
+
305
+ function formatMoney(value: number, currency: string): string {
306
+ if (currency === "USD") return `$${value.toFixed(2)}`;
307
+ return `${currency} ${value.toFixed(2)}`;
308
+ }