gloomberb 0.18.0 → 0.18.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 (365) hide show
  1. package/package.json +1 -1
  2. package/src/api-client/commodity-balances.ts +6 -0
  3. package/src/api-client/company-kpis.ts +9 -0
  4. package/src/api-client/crypto-markets.ts +9 -0
  5. package/src/api-client/data.ts +6 -5
  6. package/src/api-client/equity-screener.ts +2 -0
  7. package/src/api-client/market-movers.ts +7 -0
  8. package/src/api-client/types.ts +14 -0
  9. package/src/app/pane-runtime/plugin-bindings.ts +3 -1
  10. package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
  11. package/src/app/runtime/plugin-bindings.ts +6 -2
  12. package/src/brokers/logos.ts +66 -0
  13. package/src/brokers/profile-form.ts +3 -0
  14. package/src/brokers/remote-broker-adapter.ts +13 -3
  15. package/src/capabilities/index.ts +1 -1
  16. package/src/capabilities/service-capabilities.ts +7 -1
  17. package/src/cli/base-converter.ts +60 -18
  18. package/src/cli/commands/fred-series.ts +15 -5
  19. package/src/cli/commands/market.ts +290 -159
  20. package/src/cli/commands/news-search.ts +35 -8
  21. package/src/cli/commands/overview.ts +31 -6
  22. package/src/cli/commands/system.ts +35 -1
  23. package/src/cli/commands/ticker.ts +105 -52
  24. package/src/cli/currency-hints.ts +27 -0
  25. package/src/cli/fx-pair.ts +41 -4
  26. package/src/cli/glossary.ts +57 -4
  27. package/src/cli/helpers.ts +36 -3
  28. package/src/cli/index.ts +4 -1
  29. package/src/cli/known-gaps.ts +37 -1
  30. package/src/cli/listing-arg.ts +37 -2
  31. package/src/cli/pane-functions/catalog.ts +3 -1
  32. package/src/cli/pane-functions/discovery.ts +15 -1
  33. package/src/cli/pane-functions/dom.ts +9 -4
  34. package/src/cli/pane-functions/freshness.ts +4 -4
  35. package/src/cli/pane-functions/function-help.ts +31 -7
  36. package/src/cli/pane-functions/headless.ts +6 -5
  37. package/src/cli/pane-functions/index.ts +3 -2
  38. package/src/cli/pane-functions/lock-note.ts +7 -4
  39. package/src/cli/pane-functions/resolver.ts +2 -1
  40. package/src/cli/quote-answer.ts +179 -0
  41. package/src/cli/series-search.ts +2 -1
  42. package/src/cli/symbol-list.ts +51 -16
  43. package/src/cli/types.ts +4 -0
  44. package/src/components/chart/composite/composite-chart.tsx +22 -7
  45. package/src/components/chart/composite/format.ts +13 -1
  46. package/src/components/chart/composite/level-labels.ts +30 -0
  47. package/src/components/chart/composite/levels.ts +17 -6
  48. package/src/components/chart/composite/panel-surface.tsx +64 -16
  49. package/src/components/chart/composite/price-axis-labels.tsx +2 -1
  50. package/src/components/chart/composite/types.ts +4 -0
  51. package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
  52. package/src/components/command-bar/workflow/collection-actions.ts +4 -0
  53. package/src/components/index.ts +2 -0
  54. package/src/components/layout/pane/content.tsx +16 -14
  55. package/src/components/layout/pane/footer/index.tsx +22 -3
  56. package/src/components/layout/pane/footer/model.ts +99 -4
  57. package/src/components/layout/pane/header-tabs.tsx +15 -1
  58. package/src/components/onboarding/onboarding-frame.tsx +9 -11
  59. package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
  60. package/src/components/onboarding/wizard-model.ts +10 -3
  61. package/src/components/onboarding/wizard-positions.ts +8 -0
  62. package/src/components/ui/button.tsx +11 -4
  63. package/src/components/ui/choice-dialog.tsx +8 -5
  64. package/src/components/ui/display.tsx +11 -6
  65. package/src/components/ui/fields.tsx +100 -20
  66. package/src/components/ui/icon.tsx +6 -1
  67. package/src/components/ui/list-row-lines.tsx +87 -0
  68. package/src/components/ui/list-view.tsx +14 -0
  69. package/src/components/ui/logo-mark.tsx +64 -0
  70. package/src/components/ui/number-step.ts +54 -0
  71. package/src/components/ui/select-button.tsx +5 -4
  72. package/src/components/ui/select-field.tsx +3 -3
  73. package/src/components/ui/status.tsx +17 -3
  74. package/src/components/ui/tabs.tsx +9 -1
  75. package/src/components/ui/toggle.tsx +17 -3
  76. package/src/core/state/app/instrument.ts +8 -3
  77. package/src/data/config/store/normalize.ts +4 -1
  78. package/src/data/plugin-cache.ts +32 -6
  79. package/src/docs/snapshot.json +57 -22
  80. package/src/i18n/es.ts +9 -3
  81. package/src/i18n/ja.ts +9 -3
  82. package/src/i18n/ko.ts +9 -3
  83. package/src/i18n/zh-cn.ts +10 -4
  84. package/src/i18n/zh-tw.ts +10 -4
  85. package/src/market-data/commodity-names.ts +80 -0
  86. package/src/market-data/coordinator/auxiliary.ts +3 -1
  87. package/src/market-data/coordinator/chart.ts +3 -1
  88. package/src/market-data/futures-board.ts +38 -1
  89. package/src/market-data/futures-contract.ts +91 -0
  90. package/src/market-data/fx-closes.ts +78 -1
  91. package/src/market-data/market/change-basis.ts +219 -0
  92. package/src/market-data/market/format.ts +28 -12
  93. package/src/market-data/market/freshness.ts +38 -0
  94. package/src/market-data/market/status.ts +55 -9
  95. package/src/market-data/market/year-to-date.ts +46 -0
  96. package/src/market-data/quotes/contributions.ts +12 -0
  97. package/src/market-data/quotes/delisting.ts +34 -0
  98. package/src/market-data/quotes/live-valuation.ts +23 -0
  99. package/src/market-data/quotes/resolution.ts +8 -2
  100. package/src/market-data/tax-equivalent.ts +10 -2
  101. package/src/notifications/app-notifier.ts +2 -2
  102. package/src/notifications/notification-log.ts +8 -6
  103. package/src/plugins/builtin/alerts/cli.ts +26 -18
  104. package/src/plugins/builtin/alerts/headless.ts +1 -1
  105. package/src/plugins/builtin/alerts/index.tsx +13 -4
  106. package/src/plugins/builtin/analytics/index.tsx +1 -1
  107. package/src/plugins/builtin/analytics/risk-client.ts +44 -11
  108. package/src/plugins/builtin/analytics/risk-fx.ts +61 -0
  109. package/src/plugins/builtin/analytics/risk-headless.ts +10 -1
  110. package/src/plugins/builtin/analytics/risk-model.ts +79 -16
  111. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  112. package/src/plugins/builtin/analytics/risk-test-data.ts +3 -0
  113. package/src/plugins/builtin/attention/trending.tsx +8 -6
  114. package/src/plugins/builtin/backtest/client.ts +5 -2
  115. package/src/plugins/builtin/backtest/window.ts +16 -3
  116. package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
  117. package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
  118. package/src/plugins/builtin/broker-manager/index.tsx +20 -9
  119. package/src/plugins/builtin/broker-manager/model.ts +5 -0
  120. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
  121. package/src/plugins/builtin/broker-manager/table.ts +5 -1
  122. package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
  123. package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
  124. package/src/plugins/builtin/broker-trading/command.ts +103 -0
  125. package/src/plugins/builtin/broker-trading/controller.ts +688 -0
  126. package/src/plugins/builtin/broker-trading/dock.tsx +182 -0
  127. package/src/plugins/builtin/broker-trading/headless.ts +76 -0
  128. package/src/plugins/builtin/broker-trading/index.tsx +77 -0
  129. package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
  130. package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
  131. package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
  132. package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
  133. package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
  134. package/src/plugins/builtin/broker-trading/pane.tsx +505 -0
  135. package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
  136. package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
  137. package/src/plugins/builtin/broker-trading/ticket.tsx +573 -0
  138. package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
  139. package/src/plugins/builtin/buildout/format.ts +1 -0
  140. package/src/plugins/builtin/buildout/model/index.ts +16 -7
  141. package/src/plugins/builtin/buildout/pane/index.tsx +3 -2
  142. package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
  143. package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
  144. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
  145. package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
  146. package/src/plugins/builtin/chart-composer/headless.ts +2 -1
  147. package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
  148. package/src/plugins/builtin/chart-composer/pane.tsx +31 -11
  149. package/src/plugins/builtin/chart-composer/presets.ts +2 -2
  150. package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
  151. package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
  152. package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
  153. package/src/plugins/builtin/chat/index.tsx +1 -1
  154. package/src/plugins/builtin/chat/status-widget.tsx +1 -1
  155. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
  156. package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
  157. package/src/plugins/builtin/cloud/team/pane-sections.tsx +10 -3
  158. package/src/plugins/builtin/cloud/team/pane.tsx +30 -9
  159. package/src/plugins/builtin/commodity-balances/client.ts +6 -4
  160. package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
  161. package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
  162. package/src/plugins/builtin/commodity-balances/model.ts +22 -0
  163. package/src/plugins/builtin/commodity-balances/pane.tsx +36 -5
  164. package/src/plugins/builtin/company-kpis/client.ts +3 -2
  165. package/src/plugins/builtin/company-kpis/headless.ts +5 -7
  166. package/src/plugins/builtin/company-kpis/model.ts +85 -2
  167. package/src/plugins/builtin/company-kpis/pane.tsx +26 -12
  168. package/src/plugins/builtin/composite-plugins.ts +3 -2
  169. package/src/plugins/builtin/cot/headless.ts +1 -1
  170. package/src/plugins/builtin/cot/index.tsx +1 -1
  171. package/src/plugins/builtin/cot/model.ts +35 -12
  172. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  173. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
  174. package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
  175. package/src/plugins/builtin/crypto-board/client.ts +33 -2
  176. package/src/plugins/builtin/crypto-board/headless.ts +5 -2
  177. package/src/plugins/builtin/crypto-board/model.ts +28 -8
  178. package/src/plugins/builtin/custom-view/loader.ts +3 -2
  179. package/src/plugins/builtin/custom-view/pane.tsx +5 -4
  180. package/src/plugins/builtin/custom-view/view-spec.ts +1 -0
  181. package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
  182. package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
  183. package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
  184. package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
  185. package/src/plugins/builtin/econ/calendar-model.ts +38 -2
  186. package/src/plugins/builtin/econ/fred-catalog.ts +6 -2
  187. package/src/plugins/builtin/econ/fred-series-map.ts +12 -1
  188. package/src/plugins/builtin/econ/index.tsx +31 -4
  189. package/src/plugins/builtin/econ/print-watch.ts +279 -0
  190. package/src/plugins/builtin/equity-screener/headless.ts +20 -5
  191. package/src/plugins/builtin/equity-screener/model.ts +40 -2
  192. package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
  193. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
  194. package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
  195. package/src/plugins/builtin/executives/data.ts +24 -8
  196. package/src/plugins/builtin/executives/index.tsx +2 -18
  197. package/src/plugins/builtin/executives/pane.tsx +34 -10
  198. package/src/plugins/builtin/exposure/client.ts +4 -1
  199. package/src/plugins/builtin/exposure/editor.tsx +3 -3
  200. package/src/plugins/builtin/exposure/evidence.ts +4 -1
  201. package/src/plugins/builtin/exposure/holdings.ts +3 -1
  202. package/src/plugins/builtin/exposure/pane.tsx +37 -16
  203. package/src/plugins/builtin/filing-events/index.tsx +9 -3
  204. package/src/plugins/builtin/futures/table.tsx +6 -3
  205. package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
  206. package/src/plugins/builtin/insider/base-values.ts +72 -0
  207. package/src/plugins/builtin/insider/headless.ts +29 -4
  208. package/src/plugins/builtin/jobs/headless.ts +2 -2
  209. package/src/plugins/builtin/jobs/lock-note.ts +14 -3
  210. package/src/plugins/builtin/market-halts/model.ts +31 -6
  211. package/src/plugins/builtin/market-halts/pane.tsx +3 -0
  212. package/src/plugins/builtin/market-movers/client.ts +2 -2
  213. package/src/plugins/builtin/market-movers/index.tsx +0 -3
  214. package/src/plugins/builtin/market-movers/session-body.tsx +27 -11
  215. package/src/plugins/builtin/market-movers/session.ts +25 -10
  216. package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
  217. package/src/plugins/builtin/members/pane.tsx +8 -3
  218. package/src/plugins/builtin/news/wire/feed-config.ts +33 -13
  219. package/src/plugins/builtin/news/wire/index.tsx +7 -2
  220. package/src/plugins/builtin/news/wire/letters/client.ts +1 -0
  221. package/src/plugins/builtin/news/wire/news/command-bar-search.ts +6 -3
  222. package/src/plugins/builtin/news/wire/news/detail-view.tsx +2 -1
  223. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
  224. package/src/plugins/builtin/news/wire/news/table.tsx +2 -1
  225. package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
  226. package/src/plugins/builtin/news/wire/rss/feed-xml.ts +6 -1
  227. package/src/plugins/builtin/news/wire/rss/managed-feed.ts +1 -0
  228. package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
  229. package/src/plugins/builtin/notes/cli.ts +33 -8
  230. package/src/plugins/builtin/notes/export.ts +49 -3
  231. package/src/plugins/builtin/notes/files.ts +14 -3
  232. package/src/plugins/builtin/notes/index.tsx +3 -3
  233. package/src/plugins/builtin/notes/quick-notes-pane.tsx +45 -10
  234. package/src/plugins/builtin/notes/store.ts +13 -3
  235. package/src/plugins/builtin/options/footer.ts +25 -20
  236. package/src/plugins/builtin/options/live-quotes.ts +33 -18
  237. package/src/plugins/builtin/options/view.tsx +77 -22
  238. package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
  239. package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
  240. package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
  241. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -7
  242. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +166 -15
  243. package/src/plugins/builtin/portfolio-list/cli/render.ts +69 -15
  244. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +2 -2
  245. package/src/plugins/builtin/portfolio-list/headless.ts +3 -1
  246. package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
  247. package/src/plugins/builtin/portfolio-list/pane/index.tsx +44 -17
  248. package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
  249. package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -1
  250. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +42 -7
  251. package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
  252. package/src/plugins/builtin/primary-dealers/client.ts +137 -62
  253. package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
  254. package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
  255. package/src/plugins/builtin/primary-dealers/model.ts +179 -129
  256. package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
  257. package/src/plugins/builtin/research/analyst-model.ts +5 -1
  258. package/src/plugins/builtin/risk-factors/index.tsx +12 -3
  259. package/src/plugins/builtin/scanner/feed.ts +6 -1
  260. package/src/plugins/builtin/scanner/headless.ts +1 -1
  261. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  262. package/src/plugins/builtin/series-inventory.ts +1 -0
  263. package/src/plugins/builtin/shared/broker-reconnect.ts +105 -0
  264. package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
  265. package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
  266. package/src/plugins/builtin/shared/free-routes.ts +24 -0
  267. package/src/plugins/builtin/shared/headless-options.ts +11 -0
  268. package/src/plugins/builtin/shared/pro-feature-wall.tsx +9 -4
  269. package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
  270. package/src/plugins/builtin/shared/research-trading.tsx +62 -0
  271. package/src/plugins/builtin/social-mentions/headless.ts +1 -1
  272. package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
  273. package/src/plugins/builtin/themes/headless.ts +8 -2
  274. package/src/plugins/builtin/themes/model.ts +43 -10
  275. package/src/plugins/builtin/themes/pane.tsx +8 -6
  276. package/src/plugins/builtin/thirteenf/api.ts +4 -1
  277. package/src/plugins/builtin/thirteenf/data.ts +5 -1
  278. package/src/plugins/builtin/thirteenf/headless.ts +3 -1
  279. package/src/plugins/builtin/thirteenf/pane.tsx +8 -3
  280. package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
  281. package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
  282. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
  283. package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
  284. package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
  285. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
  286. package/src/plugins/builtin/time-sales/pane.tsx +2 -2
  287. package/src/plugins/builtin/trace-bonds/client.ts +14 -3
  288. package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
  289. package/src/plugins/builtin/vol-surface/pane.tsx +2 -2
  290. package/src/plugins/builtin-editorial.ts +1 -1
  291. package/src/plugins/compat.ts +3 -0
  292. package/src/plugins/external-runtime.ts +5 -0
  293. package/src/plugins/loader.ts +6 -0
  294. package/src/plugins/plugin-icon.ts +61 -0
  295. package/src/plugins/registry/host-actions.ts +2 -1
  296. package/src/plugins/registry/index.ts +9 -1
  297. package/src/plugins/runtime/context.tsx +5 -0
  298. package/src/plugins/runtime/index.tsx +3 -0
  299. package/src/portfolio/column-values.ts +13 -4
  300. package/src/portfolio/cost-fx.ts +61 -0
  301. package/src/portfolio/mutations.ts +16 -2
  302. package/src/portfolio/position-metrics.ts +45 -4
  303. package/src/portfolio/position-quantity.ts +43 -0
  304. package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
  305. package/src/public/broker.ts +8 -0
  306. package/src/public/market-data.ts +5 -0
  307. package/src/renderers/dom/desktop/control-styles.ts +7 -1
  308. package/src/renderers/dom/desktop/controls.tsx +39 -7
  309. package/src/renderers/dom/desktop/icons.tsx +14 -0
  310. package/src/renderers/dom/desktop/list-view.tsx +51 -19
  311. package/src/renderers/dom/desktop/menu.tsx +10 -1
  312. package/src/renderers/dom/desktop/select-field.tsx +8 -6
  313. package/src/renderers/dom/host/style.ts +6 -1
  314. package/src/renderers/dom/host/tabs.tsx +6 -0
  315. package/src/renderers/dom/styles.css +12 -5
  316. package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
  317. package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
  318. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  319. package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
  320. package/src/renderers/electrobun/view/external-plugins.ts +1 -0
  321. package/src/series/validate.ts +2 -2
  322. package/src/sources/gloomberb-cloud/fred-series.ts +27 -2
  323. package/src/sources/gloomberb-cloud/index.ts +50 -17
  324. package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
  325. package/src/sources/history-result.ts +3 -0
  326. package/src/sources/provider-errors.ts +3 -1
  327. package/src/sources/provider-router/history.ts +49 -6
  328. package/src/sources/provider-router/index.ts +2 -1
  329. package/src/state/app/activity.ts +13 -0
  330. package/src/sync/controller.ts +4 -0
  331. package/src/sync/core-contributors.ts +63 -11
  332. package/src/sync/local-only.ts +121 -0
  333. package/src/sync/react.ts +6 -0
  334. package/src/sync/types.ts +3 -0
  335. package/src/team/collections.ts +2 -0
  336. package/src/tickers/listing-fate.ts +27 -8
  337. package/src/tickers/metadata.ts +10 -0
  338. package/src/tickers/search/index.ts +24 -3
  339. package/src/time-series/price-comparison.ts +35 -0
  340. package/src/time-series/resolve.ts +52 -11
  341. package/src/time-series/spec.ts +1 -1
  342. package/src/time-series/types.ts +5 -0
  343. package/src/types/broker.ts +56 -1
  344. package/src/types/config.ts +6 -0
  345. package/src/types/data-provider.ts +5 -1
  346. package/src/types/financials.ts +23 -0
  347. package/src/types/headless.ts +12 -2
  348. package/src/types/instrument.ts +4 -0
  349. package/src/types/plugin.ts +24 -6
  350. package/src/types/price-history.ts +6 -1
  351. package/src/types/ticker.ts +9 -0
  352. package/src/types/trading.ts +67 -0
  353. package/src/ui/host.tsx +4 -0
  354. package/src/utils/base-currency.ts +108 -0
  355. package/src/utils/currency-names.ts +128 -0
  356. package/src/utils/currency-pair.ts +2 -1
  357. package/src/utils/currency-units.ts +41 -1
  358. package/src/utils/dom-screenshot.ts +22 -9
  359. package/src/utils/exchange-notation.generated.ts +360 -0
  360. package/src/utils/exchanges.ts +15 -322
  361. package/src/utils/format.ts +31 -1
  362. package/src/utils/instrument-identity.ts +38 -1
  363. package/src/utils/options.ts +46 -1
  364. package/src/utils/price-history.ts +4 -0
  365. package/src/utils/reported-money.ts +8 -4
@@ -0,0 +1,182 @@
1
+ import { startTransition, useCallback, useLayoutEffect, useMemo, useRef, useState, type ReactNode } from "react";
2
+ import { PaneFooterScope, usePaneMenuItems } from "../../../components";
3
+ import { usePaneAppConfig, usePaneStateValue } from "../../../state/app/context";
4
+ import { usePluginBrokerActions } from "../../runtime";
5
+ import { useThemeColors } from "../../../theme/theme-context";
6
+ import { Box, Text, useNativeRenderer, useUiCapabilities, type BoxRenderable } from "../../../ui";
7
+ import { capturePointerDrag } from "../../../ui/pointer-drag";
8
+ import { useShortcut } from "../../../react/input";
9
+ import { useDialogState } from "../../../ui/dialog";
10
+ import { ResearchTradingProvider, type ResearchTradeProjection } from "../shared/research-trading";
11
+ import type { BrokerOrder } from "../../../types/trading";
12
+ import { TickerOrdersSection } from "./overview-orders";
13
+ import { createTradeIntent } from "./command";
14
+ import { BrokerTradeTab } from "./pane";
15
+
16
+ const TRADE_TAB = "broker-trade";
17
+ /** Cells one press of `<` or `>` moves the divider, as in a pane sidebar. */
18
+ const RESIZE_STEP = 4;
19
+
20
+ /** Default, narrowest and widest ticket beside the chart; null when the pane is too narrow to share. */
21
+ function tradeDockWidths(width: number, native: boolean, cellWidth = 8): { initial: number; min: number; max: number } | null {
22
+ if (width < (native ? 900 / cellWidth : 100)) return null;
23
+ const initial = native ? 336 / cellWidth : 34;
24
+ const min = native ? 280 / cellWidth : 30;
25
+ // The chart keeps at least half the pane.
26
+ return { initial, min, max: Math.max(min, Math.floor(width / 2)) };
27
+ }
28
+
29
+ interface DividerPointerEvent { x?: number; preciseX?: number; preventDefault?(): void; stopPropagation?(): void }
30
+
31
+ /**
32
+ * Trade is the chart with the ticket beside it; Chart is the chart alone and
33
+ * Overview never carries the ticket. b or s on either opens Trade. Below the
34
+ * width where both fit, Trade is the ticket alone and the Chart tab is the
35
+ * other half. The ticket stays mounted across tabs once opened, so a draft
36
+ * survives a look at another tab.
37
+ */
38
+ export function ResearchTradeWorkspace({ width, height, focused, symbol, tabId, selectTab, onCapture, locked = false, children }: {
39
+ width: number; height: number; focused: boolean; symbol: string; tabId: string;
40
+ selectTab(id: string): void; onCapture(captured: boolean): void;
41
+ /** A pane pinned to one tab (no strip) opens the ticket over that tab instead of switching. */
42
+ locked?: boolean;
43
+ /** Renders a research tab: the selected one, or Chart beside the ticket. */
44
+ children(width: number, focused: boolean, shownTabId?: string): ReactNode;
45
+ }) {
46
+ const c = useThemeColors();
47
+ const { nativePaneChrome, cellWidthPx = 8 } = useUiCapabilities();
48
+ const nativeRenderer = useNativeRenderer();
49
+ const widths = tradeDockWidths(width, !!nativePaneChrome, cellWidthPx);
50
+ const [savedWidth, setSavedWidth] = usePaneStateValue<number | null>("brokerTradePanelWidth", null);
51
+ const [dragWidth, setDragWidth] = useState<number | null>(null);
52
+ const panelWidth = widths ? Math.min(widths.max, Math.max(widths.min, dragWidth ?? savedWidth ?? widths.initial)) : null;
53
+ const [intent, setIntent] = usePaneStateValue<string | null>("brokerTradeIntent", null);
54
+ const [mounted, setMounted] = useState(false);
55
+ const [lockedOpen, setLockedOpen] = useState(false);
56
+ const [panelFocused, setPanelFocused] = useState(true);
57
+ const dialogOpen = useDialogState((state) => state.isOpen);
58
+ const [projection, publish] = useState<ResearchTradeProjection>({ levels: [] });
59
+ // A text field in the ticket owns typed keys; otherwise keys the ticket does
60
+ // not use (zoom, pan, cursor) still reach the chart beside it.
61
+ const [ticketCaptured, setTicketCaptured] = useState(false);
62
+ const captureTicket = useCallback((captured: boolean) => { setTicketCaptured(captured); onCapture(captured); }, [onCapture]);
63
+ const config = usePaneAppConfig();
64
+ const { getBrokerAdapter } = usePluginBrokerActions();
65
+ const available = config.brokerInstances.some((profile) => profile.enabled !== false && !!getBrokerAdapter(profile.brokerType)?.placeOrder);
66
+ const docked = panelWidth !== null;
67
+ const visible = tabId === TRADE_TAB || locked && lockedOpen;
68
+ // Layout effect: the tab switches before the mounted ticket's effect takes the intent.
69
+ useLayoutEffect(() => {
70
+ if (visible || intent) { setMounted(true); setPanelFocused(true); }
71
+ if (intent && !visible) { if (locked) setLockedOpen(true); else selectTab(TRADE_TAB); }
72
+ }, [intent, visible, locked, selectTab]);
73
+ const openTicket = useCallback((side?: "BUY" | "SELL") => {
74
+ if (projection.busy) return;
75
+ if (side) setIntent(createTradeIntent({ action: side }));
76
+ setMounted(true); setPanelFocused(true);
77
+ if (locked) setLockedOpen(true); else selectTab(TRADE_TAB);
78
+ }, [projection.busy, locked, selectTab, setIntent]);
79
+ // Overview's order opens Trade with that order in the ticket, as the drawer's edit button does.
80
+ const modifyOrder = useCallback((order: BrokerOrder) => {
81
+ if (projection.busy) return;
82
+ setIntent(createTradeIntent({ action: order.action, modify: order })); setMounted(true); setPanelFocused(true);
83
+ if (locked) setLockedOpen(true); else selectTab(TRADE_TAB);
84
+ }, [projection.busy, locked, selectTab, setIntent]);
85
+ const orders = useCallback((ordersWidth: number) => <TickerOrdersSection width={ordersWidth} onModify={modifyOrder} />, [modifyOrder]);
86
+ const close = useCallback(() => {
87
+ captureTicket(false); setLockedOpen(false);
88
+ if (tabId === TRADE_TAB) selectTab("chart");
89
+ }, [captureTicket, selectTab, tabId]);
90
+ const split = visible && docked;
91
+ const ticketFocused = split && panelFocused;
92
+ const context = useMemo(() => ({ ...projection, symbol, available, docked, visible, ticketFocused, open: openTicket, close, publish, ...(available ? { orders } : {}) }),
93
+ [projection, symbol, available, docked, visible, ticketFocused, openTicket, close, orders]);
94
+ useShortcut((event) => {
95
+ if (event.name !== "f6" || event.repeat) return;
96
+ event.preventDefault(); event.stopPropagation(); captureTicket(false); setPanelFocused((value) => !value);
97
+ }, { enabled: focused && split && !dialogOpen, allowEditable: true, phase: "before", scope: "research:trade-focus" });
98
+ useShortcut((event) => {
99
+ if (event.name !== "escape" || event.defaultPrevented || event.targetEditable) return;
100
+ event.preventDefault(); event.stopPropagation(); close();
101
+ }, { enabled: focused && split && !panelFocused && !dialogOpen, phase: "after", scope: "research:trade-close" });
102
+
103
+ // The divider between chart and ticket drags, and < > move it from the keyboard.
104
+ const dividerRef = useRef<BoxRenderable>(null);
105
+ const dragOrigin = useRef<{ pointerX: number; width: number } | null>(null);
106
+ const [dividerActive, setDividerActive] = useState(false);
107
+ const clampWidth = (next: number) => widths ? Math.min(widths.max, Math.max(widths.min, next)) : next;
108
+ const dragTo = (event?: DividerPointerEvent) => {
109
+ const origin = dragOrigin.current;
110
+ if (!origin) return null;
111
+ event?.preventDefault?.(); event?.stopPropagation?.();
112
+ const pointerX = event?.preciseX ?? event?.x ?? origin.pointerX;
113
+ // Dragging left widens the ticket on the right.
114
+ const next = clampWidth(origin.width - (pointerX - origin.pointerX));
115
+ return nativePaneChrome ? next : Math.round(next);
116
+ };
117
+ const dividerHandlers = split ? {
118
+ onMouseDown: (event?: DividerPointerEvent) => {
119
+ event?.preventDefault?.(); event?.stopPropagation?.();
120
+ dragOrigin.current = { pointerX: event?.preciseX ?? event?.x ?? 0, width: panelWidth! };
121
+ setDividerActive(true);
122
+ capturePointerDrag(nativeRenderer, dividerRef.current);
123
+ },
124
+ onMouseDrag: (event?: DividerPointerEvent) => {
125
+ const next = dragTo(event);
126
+ if (next !== null) startTransition(() => setDragWidth(next));
127
+ },
128
+ onMouseDragEnd: (event?: DividerPointerEvent) => {
129
+ const next = dragTo(event);
130
+ const origin = dragOrigin.current;
131
+ dragOrigin.current = null;
132
+ setDividerActive(false);
133
+ setDragWidth(null);
134
+ // A click that never moved keeps the width as it was.
135
+ if (next !== null && next !== origin?.width) setSavedWidth(next);
136
+ },
137
+ onMouseOver: () => setDividerActive(true),
138
+ onMouseOut: () => { if (!dragOrigin.current) setDividerActive(false); },
139
+ } : {};
140
+ const stepWidth = useCallback((delta: number) => {
141
+ if (!widths || panelWidth === null) return;
142
+ const next = Math.min(widths.max, Math.max(widths.min, panelWidth + delta));
143
+ if (next !== panelWidth) setSavedWidth(next);
144
+ }, [widths?.min, widths?.max, panelWidth, setSavedWidth]);
145
+ useShortcut((event) => {
146
+ if (event.defaultPrevented || event.targetEditable || event.ctrl || event.meta || event.alt || event.super) return;
147
+ if (event.name !== "<" && event.name !== ">") return;
148
+ event.preventDefault(); event.stopPropagation(); stepWidth(event.name === "<" ? RESIZE_STEP : -RESIZE_STEP);
149
+ }, { enabled: focused && split && !dialogOpen, phase: "before", scope: "research:trade-resize" });
150
+ usePaneMenuItems("research:trade-focus", () => focused && split ? [
151
+ { id: "trade-focus", accelerator: "F6", label: panelFocused ? "Focus chart" : "Focus ticket", onSelect: () => setPanelFocused((value) => !value) },
152
+ { id: "trade-widen", accelerator: "<", label: "Widen trade panel", enabled: panelWidth! < widths!.max, onSelect: () => stepWidth(RESIZE_STEP) },
153
+ { id: "trade-narrow", accelerator: ">", label: "Narrow trade panel", enabled: panelWidth! > widths!.min, onSelect: () => stepWidth(-RESIZE_STEP) },
154
+ ] : null, [focused, split, panelFocused, panelWidth, widths?.min, widths?.max, stepWidth]);
155
+
156
+ const chartWidth = split ? width - panelWidth! : width;
157
+ const contentWidth = (split ? panelWidth! : width) - (split ? nativePaneChrome ? 1 / cellWidthPx : 1 : 0);
158
+ const dividerColor = dividerActive ? c.borderFocused : c.border;
159
+ return <ResearchTradingProvider value={context}>
160
+ <Box flexDirection="row" width={width} height={height} overflow="hidden">
161
+ <Box visible={!visible || split} width={chartWidth} height={height} flexDirection="column" overflow="hidden" onMouseDown={() => setPanelFocused(false)}>
162
+ <PaneFooterScope active={!visible || !panelFocused}>{children(chartWidth, focused && (!visible || !panelFocused || split && !ticketCaptured), split && !locked ? "chart" : undefined)}</PaneFooterScope>
163
+ </Box>
164
+ {mounted ? <Box visible={visible} position="relative" onMouseDown={() => setPanelFocused(true)} width={split ? panelWidth! : width} height={height} flexShrink={0} flexDirection="row">
165
+ {split && !nativePaneChrome ? <Box ref={dividerRef} width={1} height={height} flexDirection="column" flexShrink={0} {...dividerHandlers}>
166
+ {Array.from({ length: height }, (_, index) => <Text key={index} fg={dividerColor} selectable={false}>│</Text>)}
167
+ </Box> : null}
168
+ {split && nativePaneChrome ? <>
169
+ <Box position="absolute" top={0} left={0} width={1} height={height} style={{ width: 1, height: "100%", backgroundColor: dividerColor, pointerEvents: "none" }} />
170
+ {/* A 1px line is too thin to grab, so the handle straddles it. */}
171
+ <Box ref={dividerRef} position="absolute" top={0} left={0} width={1} height={height} title="Resize trade panel (< >)" {...dividerHandlers}
172
+ style={{ width: 7, left: -3, height: "100%", zIndex: 3, cursor: "col-resize" }} />
173
+ </> : null}
174
+ <Box flexDirection="column" width={contentWidth} height={height} flexGrow={1}>
175
+ <PaneFooterScope active={visible && panelFocused}>
176
+ <BrokerTradeTab width={contentWidth} height={height} focused={focused && visible && panelFocused} onCapture={captureTicket} docked={split} onClose={close} />
177
+ </PaneFooterScope>
178
+ </Box>
179
+ </Box> : null}
180
+ </Box>
181
+ </ResearchTradingProvider>;
182
+ }
@@ -0,0 +1,76 @@
1
+ import type { BrokerAdapter } from "../../../types/broker";
2
+ import type { HeadlessPaneContext, HeadlessPaneDefinition, HeadlessPaneColumn } from "../../../types/headless";
3
+ import { brokerOrderStatus, loadBrokerOrdersSnapshot } from "./orders-data";
4
+
5
+ type BrokerResolver = (brokerType: string, context: HeadlessPaneContext) => BrokerAdapter | null;
6
+
7
+ const orderColumns: HeadlessPaneColumn[] = [
8
+ { key: "symbol", header: "Symbol" }, { key: "side", header: "Side" }, { key: "status", header: "Status" },
9
+ { key: "quantity", header: "Quantity", align: "right" }, { key: "filled", header: "Filled", align: "right" },
10
+ { key: "orderType", header: "Type" }, { key: "limitPrice", header: "Limit", align: "right" },
11
+ { key: "stopPrice", header: "Stop", align: "right" }, { key: "updatedAt", header: "Updated" },
12
+ ];
13
+ const activityColumns: HeadlessPaneColumn[] = [
14
+ { key: "symbol", header: "Symbol" }, { key: "side", header: "Side" },
15
+ { key: "quantity", header: "Quantity", align: "right" }, { key: "price", header: "Price", align: "right" },
16
+ { key: "commission", header: "Fees", align: "right" }, { key: "time", header: "Time" },
17
+ ];
18
+ const isoTime = (value: number) => Number.isFinite(value) && value > 0 ? new Date(value).toISOString() : null;
19
+
20
+ function ordinal(value: unknown, count: number, label: string): number {
21
+ if (value === undefined && count === 1) return 0;
22
+ if (value === undefined) throw new Error(`Choose ${label === "account" ? "an" : "a"} ${label} with --${label} (1 to ${count}).`);
23
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > count) throw new Error(`The ${label} must be between 1 and ${count}.`);
24
+ return value - 1;
25
+ }
26
+
27
+ function createBrokerOrdersHeadless(
28
+ resolveBroker: BrokerResolver = (type, context) => context.resolveBroker?.(type) ?? null,
29
+ ): HeadlessPaneDefinition<"bundle"> {
30
+ return {
31
+ shape: "bundle", argument: { kind: "none" },
32
+ description: "Read open orders and recent activity for one connected broker account.",
33
+ discovery: { id: "broker-orders", aliases: ["orders", "executions"], screenshotReadiness: "live-dom" },
34
+ options: [
35
+ { key: "profile", type: "integer", minimum: 1, description: "Broker profile number, required when more than one is available." },
36
+ { key: "account", type: "integer", minimum: 1, description: "Account number, required when the profile has more than one account." },
37
+ { key: "view", type: "enum", values: [{ value: "all" }, { value: "open" }, { value: "activity" }], defaultValue: "all", description: "Orders, activity, or both." },
38
+ ],
39
+ async load(args, context) {
40
+ context.signal.throwIfAborted();
41
+ const profiles = context.config.brokerInstances.filter((profile) => profile.enabled !== false)
42
+ .map((instance) => ({ instance, broker: resolveBroker(instance.brokerType, context) }))
43
+ .filter((row): row is { instance: typeof row.instance; broker: BrokerAdapter } => !!row.broker && !!(row.broker.listOpenOrders || row.broker.listExecutions));
44
+ if (!profiles.length) throw new Error("Connect a broker profile that reports orders in Brokers first.");
45
+ const { instance, broker } = profiles[ordinal(args.options.profile, profiles.length, "profile")]!;
46
+ if (!broker.listAccounts) throw new Error("This broker does not report account identities.");
47
+ const accounts = await broker.listAccounts(instance);
48
+ context.signal.throwIfAborted();
49
+ if (!accounts.length) throw new Error("This broker profile has no available accounts.");
50
+ const account = accounts[ordinal(args.options.account, accounts.length, "account")]!;
51
+ const data = await loadBrokerOrdersSnapshot(broker, instance, account.accountId, context.signal);
52
+ const view = args.options.view ?? "all";
53
+ const mode = account.tradingMode === "simulation" ? "SIMULATION" : account.tradingMode === "live" ? "LIVE" : "UNKNOWN";
54
+ return {
55
+ sections: [
56
+ { title: "Account", entries: [{ label: "Broker", value: broker.name }, { label: "Account", value: account.name }, { label: "Mode", value: mode }] },
57
+ ...(view !== "activity" ? [{ title: "Open orders", columns: orderColumns, rows: data.orders.map((order) => ({
58
+ symbol: order.contract.localSymbol || order.contract.symbol, side: order.action, status: brokerOrderStatus(order.status),
59
+ quantity: order.quantity, filled: order.filled, orderType: order.orderType,
60
+ limitPrice: order.limitPrice ?? null, stopPrice: order.stopPrice ?? null,
61
+ updatedAt: isoTime(order.updatedAt),
62
+ })) }] : []),
63
+ ...(view !== "open" ? [{ title: data.executionKind === "order-summaries" ? "Cumulative order summaries" : "Recent activity", columns: activityColumns,
64
+ rows: data.executions.map((execution) => ({ symbol: execution.contract.localSymbol || execution.contract.symbol,
65
+ side: execution.side, quantity: execution.shares, price: execution.price,
66
+ commission: execution.commission ?? null, commissionCurrency: execution.commissionCurrency ?? null,
67
+ time: isoTime(execution.time) })) }] : []),
68
+ ],
69
+ complete: data.errors.length === 0, errors: data.errors, notes: data.notes,
70
+ freshness: { source: broker.name, status: "not-a-feed", basis: "broker account snapshot", asOf: data.updatedAt, oldest: null },
71
+ };
72
+ },
73
+ };
74
+ }
75
+
76
+ export const brokerOrdersHeadless = createBrokerOrdersHeadless();
@@ -0,0 +1,77 @@
1
+ import type { PluginModule } from "../plugin-module";
2
+ import { brokerOrdersHeadless } from "./headless";
3
+ import { BrokerOrdersPane, BrokerTradeTab } from "./pane";
4
+ import { brokerCanTrade, createTradeCommand, openTradeTicket } from "./command";
5
+ import { brokerOrderFeed } from "./order-feed";
6
+ import { tickerSecType } from "./pane-model";
7
+
8
+ /** Every enabled trading profile's orders, held for as long as the app runs: its fills toast with no ticket open. */
9
+ let releaseHeldOrders: (() => void) | undefined;
10
+
11
+ /** A ticker without a saved asset category trades as its research instrument kind says. */
12
+ const INSTRUMENT_CATEGORY: Record<string, string> = { option: "OPT", future: "FUT", bond: "BOND", crypto: "CRYPTO", currency: "CASH" };
13
+
14
+ /** Trading lives with broker profiles, with one research tab and one orders view. */
15
+ export const brokerTradingModule: PluginModule = {
16
+ panes: [{
17
+ id: "broker-orders", name: "Orders", personalData: "account", icon: "O", component: BrokerOrdersPane,
18
+ defaultPosition: "right", defaultMode: "floating",
19
+ defaultFloatingSize: { width: 100, height: 30 }, headless: brokerOrdersHeadless,
20
+ portableShare: { private: { title: true, params: true, settings: true, state: true } },
21
+ }],
22
+ paneTemplates: [{
23
+ id: "broker-orders-pane", paneId: "broker-orders", label: "Orders",
24
+ description: "Open orders and recent activity for a broker account",
25
+ keywords: ["orders", "trades", "executions", "cancel", "modify"],
26
+ shortcut: { prefix: "ORD" }, headless: brokerOrdersHeadless,
27
+ createInstance: () => ({ placement: "floating" }),
28
+ }],
29
+ setup(ctx) {
30
+ brokerOrderFeed.setNotifier((notification) => { ctx.notify(notification); });
31
+ let held = new Map<string, { key: string; release(): void }>();
32
+ // A broker registered by a later plugin, or a profile added or edited, is held from then on.
33
+ const holdProfiles = () => {
34
+ const next = new Map<string, { key: string; release(): void }>();
35
+ for (const instance of ctx.getConfig().brokerInstances) {
36
+ const broker = instance.enabled === false ? null : ctx.getBrokerAdapter?.(instance.brokerType);
37
+ if (!broker?.listOpenOrders || !broker.placeOrder) continue;
38
+ const key = JSON.stringify([broker.id, instance.config]);
39
+ const current = held.get(instance.id);
40
+ if (current?.key === key) { next.set(instance.id, current); held.delete(instance.id); continue; }
41
+ next.set(instance.id, { key, release: brokerOrderFeed.hold(broker, instance) });
42
+ }
43
+ for (const stale of held.values()) stale.release();
44
+ held = next;
45
+ };
46
+ const unsubscribe = [ctx.on("config:changed", holdProfiles), ctx.on("plugin:registered", holdProfiles)];
47
+ holdProfiles();
48
+ releaseHeldOrders = () => {
49
+ for (const stop of unsubscribe) stop();
50
+ for (const entry of held.values()) entry.release();
51
+ held.clear();
52
+ brokerOrderFeed.setNotifier(undefined);
53
+ brokerOrderFeed.clear();
54
+ };
55
+ const supported = (symbol: string, secType = "STK") => brokerCanTrade(ctx.getConfig().brokerInstances, (type) => ctx.getBrokerAdapter?.(type), symbol, secType);
56
+ ctx.registerTickerResearchTab({
57
+ id: "broker-trade", name: "Trade", order: 20, component: BrokerTradeTab,
58
+ instruments: ["equity", "fund", "option", "future", "bond", "crypto", "currency", "other"],
59
+ isVisible: ({ ticker, instrumentKind }) => supported(ticker?.metadata.ticker ?? "", tickerSecType(ticker?.metadata.assetCategory, INSTRUMENT_CATEGORY[instrumentKind])),
60
+ });
61
+ for (const side of ["BUY", "SELL"] as const) {
62
+ ctx.registerCommand(createTradeCommand(ctx, side, supported));
63
+ ctx.registerTickerAction({
64
+ id: `broker-${side.toLowerCase()}`, label: side === "BUY" ? "Buy" : "Sell",
65
+ keywords: [side.toLowerCase(), "trade", "order", "broker"],
66
+ filter: (ticker) => supported(ticker.metadata.ticker, tickerSecType(ticker.metadata.assetCategory)),
67
+ execute: (ticker) => {
68
+ openTradeTicket(ctx, ticker.metadata.ticker, { action: side });
69
+ },
70
+ });
71
+ }
72
+ },
73
+ dispose() {
74
+ releaseHeldOrders?.();
75
+ releaseHeldOrders = undefined;
76
+ },
77
+ };