gloomberb 0.11.2 → 0.12.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 (531) hide show
  1. package/README.md +7 -4
  2. package/README.zh-CN.md +4 -2
  3. package/package.json +28 -1
  4. package/src/api-client/auth.ts +10 -0
  5. package/src/api-client/data.ts +108 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +134 -1
  8. package/src/api-client/normalizers.ts +39 -0
  9. package/src/api-client/paths.ts +85 -1
  10. package/src/api-client/types.ts +220 -0
  11. package/src/app/pane-runtime/index.tsx +15 -12
  12. package/src/app/pane-runtime/layout-placement.test.ts +27 -0
  13. package/src/app/pane-runtime/layout-placement.ts +13 -0
  14. package/src/app/pane-runtime/plugin-bindings.ts +62 -0
  15. package/src/app.tsx +60 -18
  16. package/src/architecture/browser-entry-graph.test.ts +96 -0
  17. package/src/brokers/profile-form.test.ts +25 -27
  18. package/src/brokers/test-broker.ts +68 -0
  19. package/src/capabilities/chart-series.ts +3 -2
  20. package/src/cli/commands/market.ts +2 -2
  21. package/src/cli/commands/plugins.ts +39 -11
  22. package/src/cli/desktop-pane-shot.ts +285 -16
  23. package/src/cli/index.ts +1 -1
  24. package/src/cli/pane-functions/capabilities.ts +61 -31
  25. package/src/cli/pane-functions/catalog.ts +16 -4
  26. package/src/cli/pane-functions/discovery.ts +1 -0
  27. package/src/cli/pane-functions/headless.test.ts +247 -0
  28. package/src/cli/pane-functions/headless.ts +423 -0
  29. package/src/cli/pane-functions/index.test.ts +9 -0
  30. package/src/cli/pane-functions/index.ts +6 -2
  31. package/src/cli/pane-functions/options.ts +27 -2
  32. package/src/cli/pane-functions/report.ts +4 -0
  33. package/src/cli/pane-functions/resolver.ts +26 -8
  34. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  35. package/src/cli/pane-functions/screenshot.ts +236 -20
  36. package/src/cli/registry.ts +0 -22
  37. package/src/cli/restore-plugins.ts +34 -0
  38. package/src/cli/result.ts +7 -10
  39. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  40. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  41. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  42. package/src/components/chart/composite/composite-chart.test.tsx +68 -20
  43. package/src/components/chart/composite/composite-chart.tsx +301 -204
  44. package/src/components/chart/composite/format.ts +8 -2
  45. package/src/components/chart/composite/interactions.test.ts +68 -102
  46. package/src/components/chart/composite/interactions.ts +214 -264
  47. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  48. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  49. package/src/components/chart/composite/scene.ts +57 -3
  50. package/src/components/chart/composite/text-renderer.ts +3 -2
  51. package/src/components/chart/composite/time-scale.ts +138 -57
  52. package/src/components/chart/composite/types.ts +25 -1
  53. package/src/components/chart/core/data.ts +9 -13
  54. package/src/components/chart/core/date-window.ts +7 -1
  55. package/src/components/chart/core/pointer.ts +3 -0
  56. package/src/components/chart/core/types.ts +0 -45
  57. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  58. package/src/components/chart/native/kitty/index.test.ts +0 -188
  59. package/src/components/chart/native/raster/crosshair.ts +6 -5
  60. package/src/components/chart/native/raster/primitives.ts +0 -23
  61. package/src/components/chart/native/renderer-selection.ts +81 -31
  62. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  63. package/src/components/chart/static/chart-surface.tsx +150 -0
  64. package/src/components/chart/static/index.ts +8 -0
  65. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  66. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  67. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  68. package/src/components/chart/static/series.ts +49 -0
  69. package/src/components/command-bar/action-runtime.ts +1 -3
  70. package/src/components/command-bar/assist/model.test.ts +17 -2
  71. package/src/components/command-bar/assist/model.ts +40 -9
  72. package/src/components/command-bar/commands/direct/index.ts +1 -1
  73. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  74. package/src/components/command-bar/commands/registry.ts +0 -11
  75. package/src/components/command-bar/helpers.test.ts +2 -2
  76. package/src/components/command-bar/helpers.ts +2 -6
  77. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  78. package/src/components/command-bar/list/badge.test.ts +54 -0
  79. package/src/components/command-bar/list/badge.ts +62 -0
  80. package/src/components/command-bar/list/model.test.ts +103 -0
  81. package/src/components/command-bar/list/model.ts +61 -4
  82. package/src/components/command-bar/list/view.test.tsx +113 -0
  83. package/src/components/command-bar/list/view.tsx +147 -131
  84. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  85. package/src/components/command-bar/panel/body.tsx +51 -65
  86. package/src/components/command-bar/panel/index.tsx +48 -13
  87. package/src/components/command-bar/panel/layout.test.ts +139 -0
  88. package/src/components/command-bar/panel/layout.ts +65 -28
  89. package/src/components/command-bar/panel/native-surface.ts +24 -0
  90. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  91. package/src/components/command-bar/panel/runtime.ts +6 -3
  92. package/src/components/command-bar/panel/state.ts +41 -7
  93. package/src/components/command-bar/panel/types.ts +7 -1
  94. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  95. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  96. package/src/components/command-bar/routes/root/results.ts +30 -28
  97. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  98. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  99. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  100. package/src/components/command-bar/routes/root/selection.ts +1 -12
  101. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  102. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  103. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  104. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  105. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  106. package/src/components/command-bar/routing/actions.ts +1 -81
  107. package/src/components/command-bar/routing/launch-request.ts +1 -1
  108. package/src/components/command-bar/routing/list-state.ts +6 -21
  109. package/src/components/command-bar/selection-runtime.ts +1 -1
  110. package/src/components/command-bar/surface/assist.test.tsx +14 -10
  111. package/src/components/command-bar/surface/environment.ts +8 -1
  112. package/src/components/command-bar/surface/index.test.tsx +84 -68
  113. package/src/components/command-bar/surface/index.tsx +33 -36
  114. package/src/components/command-bar/surface/test-harness.tsx +15 -3
  115. package/src/components/command-bar/theme-picker.tsx +43 -15
  116. package/src/components/command-bar/view-model.test.ts +53 -8
  117. package/src/components/command-bar/view-model.ts +43 -14
  118. package/src/components/command-bar/workflow/tickers.ts +1 -0
  119. package/src/components/command-bar/workflow/types.ts +1 -1
  120. package/src/components/data-table/export.test.ts +21 -0
  121. package/src/components/data-table/export.ts +21 -0
  122. package/src/components/data-table/view.test.tsx +45 -6
  123. package/src/components/data-table/view.tsx +55 -9
  124. package/src/components/index.ts +10 -2
  125. package/src/components/input-search-bar.test.tsx +36 -2
  126. package/src/components/input-search-bar.tsx +18 -1
  127. package/src/components/layout/header.test.tsx +57 -10
  128. package/src/components/layout/header.tsx +292 -149
  129. package/src/components/layout/market-summary.test.ts +81 -0
  130. package/src/components/layout/market-summary.ts +109 -0
  131. package/src/components/layout/shell/chrome.ts +96 -1
  132. package/src/components/layout/shell/index.tsx +11 -3
  133. package/src/components/layout/shell/menu.ts +9 -0
  134. package/src/components/layout/shell/pane/actions.ts +29 -0
  135. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  136. package/src/components/layout/shell/shortcuts.ts +3 -0
  137. package/src/components/layout/status-bar.test.tsx +9 -8
  138. package/src/components/layout/status-bar.tsx +85 -54
  139. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  140. package/src/components/price-sparkline/index.ts +61 -34
  141. package/src/components/price-sparkline/view.tsx +1 -1
  142. package/src/components/sign-in-gate.test.tsx +128 -0
  143. package/src/components/sign-in-gate.tsx +149 -0
  144. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  145. package/src/components/ticker/badge/list.tsx +17 -3
  146. package/src/components/ticker/list-table-view.tsx +6 -3
  147. package/src/components/ui/data-table/index.tsx +22 -4
  148. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  149. package/src/components/ui/frame.tsx +28 -0
  150. package/src/components/ui/index.ts +3 -0
  151. package/src/components/ui/native-select.tsx +20 -10
  152. package/src/components/ui/select-button.tsx +138 -0
  153. package/src/components/ui/status.tsx +2 -2
  154. package/src/components/ui/table-layout.ts +1 -1
  155. package/src/components/use-double-click-activation.ts +7 -3
  156. package/src/core/app-services.ts +3 -3
  157. package/src/data/config/chart-settings.ts +4 -4
  158. package/src/data/config/layout.ts +12 -6
  159. package/src/data/config/store/index.test.ts +97 -0
  160. package/src/data/config/store/migrations.ts +41 -0
  161. package/src/data/config/store/normalize.ts +5 -3
  162. package/src/i18n/es.ts +6 -2
  163. package/src/i18n/ja.ts +6 -2
  164. package/src/i18n/ko.ts +6 -2
  165. package/src/i18n/zh-cn.ts +6 -2
  166. package/src/i18n/zh-tw.ts +6 -2
  167. package/src/layout-marketplace/gallery-terminal.tsx +126 -96
  168. package/src/layout-marketplace/gallery.test.tsx +65 -1
  169. package/src/layout-marketplace/pane-imagery.ts +1 -0
  170. package/src/layout-marketplace/payload.test.ts +17 -0
  171. package/src/layout-marketplace/payload.ts +40 -5
  172. package/src/market-data/coordinator/index.ts +3 -2
  173. package/src/market-data/coordinator/quotes.ts +173 -66
  174. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  175. package/src/pane-settings.ts +1 -1
  176. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  177. package/src/plugins/builtin/analytics/view.tsx +1 -1
  178. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  179. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  180. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  181. package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
  182. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  183. package/src/plugins/builtin/chart-composer/settings.ts +2 -13
  184. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  185. package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
  186. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  187. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  188. package/src/plugins/builtin/composite-plugins.ts +16 -1
  189. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  190. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  191. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  192. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  193. package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
  194. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  195. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  196. package/src/plugins/builtin/earnings/index.tsx +1 -0
  197. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  198. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  199. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  200. package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
  201. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  202. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  203. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
  204. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  205. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  206. package/src/plugins/builtin/econ/index.tsx +3 -1
  207. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  208. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  209. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  210. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  211. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  212. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  213. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  214. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  215. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  216. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  217. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  218. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  219. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  220. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  221. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  222. package/src/plugins/builtin/futures/index.tsx +1 -0
  223. package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
  224. package/src/plugins/builtin/help/index.tsx +4 -0
  225. package/src/plugins/builtin/holders/index.tsx +1 -0
  226. package/src/plugins/builtin/insider/index.tsx +1 -0
  227. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
  228. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  229. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  230. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  231. package/src/plugins/builtin/market-movers/index.tsx +1 -0
  232. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  233. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  234. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  235. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  236. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  237. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  238. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  239. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  240. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  241. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  242. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  243. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  244. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  245. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  246. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  247. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  248. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  249. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  250. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  251. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  252. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  253. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  254. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  255. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  256. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  257. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  258. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  259. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  260. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  261. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  262. package/src/plugins/builtin/options/analytics.test.ts +83 -0
  263. package/src/plugins/builtin/options/analytics.ts +110 -0
  264. package/src/plugins/builtin/options/index.tsx +18 -2
  265. package/src/plugins/builtin/options/table.ts +121 -51
  266. package/src/plugins/builtin/options/types.ts +22 -15
  267. package/src/plugins/builtin/options/view.test.tsx +53 -1
  268. package/src/plugins/builtin/options/view.tsx +92 -41
  269. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  270. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  271. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  272. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  273. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  274. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  275. package/src/plugins/builtin/plugin-module.test.ts +2 -4
  276. package/src/plugins/builtin/plugin-module.ts +9 -8
  277. package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
  278. package/src/plugins/builtin/research/index.tsx +4 -0
  279. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  280. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  281. package/src/plugins/builtin/research-search/data.ts +109 -0
  282. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  283. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  284. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  285. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  286. package/src/plugins/builtin/research-search/index.tsx +85 -0
  287. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  288. package/src/plugins/builtin/research-search/model.ts +291 -0
  289. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  290. package/src/plugins/builtin/research-search/pane.tsx +678 -0
  291. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  292. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  293. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  294. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  295. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  296. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  297. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  298. package/src/plugins/builtin/scanner/index.ts +2 -0
  299. package/src/plugins/builtin/sec/index.tsx +1 -0
  300. package/src/plugins/builtin/sectors/index.tsx +1 -0
  301. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  302. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  303. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  304. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  305. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
  306. package/src/plugins/builtin/ticker-detail/index.tsx +2 -14
  307. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  308. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  309. package/src/plugins/builtin/volatility/index.tsx +1 -1
  310. package/src/plugins/builtin/world-indices/index.tsx +1 -0
  311. package/src/plugins/builtin/world-venue-map/index.ts +27 -0
  312. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  313. package/src/plugins/builtin/world-venue-map/map.tsx +481 -0
  314. package/src/plugins/builtin/world-venue-map/model.test.ts +85 -0
  315. package/src/plugins/builtin/world-venue-map/model.ts +268 -0
  316. package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
  317. package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
  318. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  319. package/src/plugins/bundle.test.ts +109 -0
  320. package/src/plugins/bundle.ts +139 -0
  321. package/src/plugins/catalog-backend.ts +6 -44
  322. package/src/plugins/catalog-browser.test.ts +1 -0
  323. package/src/plugins/catalog-browser.ts +12 -0
  324. package/src/plugins/catalog-manifest.test.ts +43 -0
  325. package/src/plugins/catalog-manifest.ts +92 -0
  326. package/src/plugins/catalog-ui.ts +20 -4
  327. package/src/plugins/catalog.ts +9 -2
  328. package/src/plugins/current-target.ts +28 -0
  329. package/src/plugins/host-contract.ts +37 -0
  330. package/src/plugins/host-link.ts +158 -0
  331. package/src/plugins/host-modules.ts +61 -0
  332. package/src/plugins/loader.ts +41 -22
  333. package/src/plugins/ownership.ts +1 -0
  334. package/src/plugins/prediction-markets/index.tsx +1 -0
  335. package/src/plugins/registry/context.ts +3 -0
  336. package/src/plugins/registry/contributions.ts +27 -0
  337. package/src/plugins/registry/index.test.ts +25 -0
  338. package/src/plugins/registry/index.ts +8 -0
  339. package/src/plugins/registry/pane-settings.test.ts +53 -0
  340. package/src/plugins/registry/pane-settings.ts +29 -3
  341. package/src/plugins/seed.test.ts +83 -0
  342. package/src/plugins/seed.ts +80 -0
  343. package/src/public/broker.ts +31 -0
  344. package/src/public/market-data.ts +9 -0
  345. package/src/public/pane-hooks.ts +57 -0
  346. package/src/public/public-api.test.ts +285 -0
  347. package/src/public/react.ts +62 -0
  348. package/src/public/test-support.ts +23 -0
  349. package/src/public/time-series.ts +8 -0
  350. package/src/public/utils.ts +77 -0
  351. package/src/remote/layout-helpers.ts +6 -8
  352. package/src/remote/resources.ts +2 -0
  353. package/src/remote/schema.ts +1 -0
  354. package/src/remote/semantic-tree.test.tsx +105 -0
  355. package/src/remote/semantic-tree.tsx +53 -35
  356. package/src/renderers/browser/main.tsx +6 -0
  357. package/src/renderers/browser/ui-host.tsx +11 -0
  358. package/src/renderers/cloudflare/worker.test.ts +19 -0
  359. package/src/renderers/cloudflare/worker.ts +1 -1
  360. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
  361. package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
  362. package/src/renderers/electrobun/bun/index.ts +6 -2
  363. package/src/renderers/electrobun/shared/protocol.ts +25 -0
  364. package/src/renderers/electrobun/view/build-assets.ts +0 -1
  365. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
  366. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  367. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  368. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  369. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  370. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  371. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  372. package/src/renderers/electrobun/view/main.tsx +27 -2
  373. package/src/renderers/electrobun/view/styles.css +5 -0
  374. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  375. package/src/renderers/electrobun/view/ui-host.tsx +3 -0
  376. package/src/renderers/opentui/chart-surface.tsx +5 -1
  377. package/src/renderers/opentui/host.tsx +20 -2
  378. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  379. package/src/renderers/opentui/interaction-performance.ts +188 -0
  380. package/src/renderers/opentui/start.tsx +26 -1
  381. package/src/renderers/opentui/test-utils.tsx +13 -0
  382. package/src/renderers/opentui/toast-host.tsx +26 -2
  383. package/src/renderers/opentui/ui-host.tsx +1 -0
  384. package/src/sources/provider-router/search.test.ts +191 -0
  385. package/src/sources/provider-router/search.ts +92 -39
  386. package/src/state/app/context/index.tsx +2 -0
  387. package/src/state/app/context/selector.test.tsx +33 -1
  388. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  389. package/src/state/hooks/quote-streaming.ts +106 -30
  390. package/src/state/pane-table-export-registry.ts +50 -0
  391. package/src/theme/color-utils.ts +1 -1
  392. package/src/theme/colors.ts +50 -0
  393. package/src/theme/themes.test.ts +15 -2
  394. package/src/theme/themes.ts +19 -1
  395. package/src/tickers/search/index.test.ts +26 -0
  396. package/src/tickers/search/index.ts +42 -17
  397. package/src/tickers/search/ranking.ts +14 -0
  398. package/src/time-series/chart-data.test.ts +216 -0
  399. package/src/time-series/range.ts +12 -0
  400. package/src/time-series/resolution.test.ts +25 -26
  401. package/src/time-series/resolution.ts +44 -25
  402. package/src/time-series/resolve.ts +7 -0
  403. package/src/time-series/spec.ts +5 -5
  404. package/src/time-series/types.ts +3 -1
  405. package/src/time-series/use-chart-resolution.ts +5 -0
  406. package/src/types/config.ts +123 -3
  407. package/src/types/data-provider.ts +11 -0
  408. package/src/types/headless.ts +161 -0
  409. package/src/types/plugin.ts +104 -20
  410. package/src/ui/host.tsx +19 -1
  411. package/src/ui/index.tsx +1 -0
  412. package/src/ui/toast.tsx +8 -4
  413. package/src/utils/csv.test.ts +13 -0
  414. package/src/utils/csv.ts +37 -0
  415. package/src/utils/format.ts +35 -0
  416. package/src/utils/fuzzy-search.test.ts +94 -0
  417. package/src/utils/fuzzy-search.ts +81 -22
  418. package/src/utils/save-text-file.test.ts +24 -0
  419. package/src/utils/save-text-file.ts +36 -0
  420. package/src/utils/shortcut-labels.ts +14 -0
  421. package/src/components/chart/core/axis-format.ts +0 -157
  422. package/src/components/chart/core/index.test.ts +0 -1075
  423. package/src/components/chart/core/projection.ts +0 -357
  424. package/src/components/chart/core/render-utils.ts +0 -4
  425. package/src/components/chart/core/renderer.ts +0 -244
  426. package/src/components/chart/core/scene.ts +0 -180
  427. package/src/components/chart/core/time-axis.ts +0 -330
  428. package/src/components/chart/cursor-motion.ts +0 -47
  429. package/src/components/chart/indicators/types.ts +0 -26
  430. package/src/components/chart/market-session.ts +0 -176
  431. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  432. package/src/components/chart/multi-line/renderer.ts +0 -161
  433. package/src/components/chart/multi-line/scene.ts +0 -188
  434. package/src/components/chart/native/raster/layout.ts +0 -129
  435. package/src/components/chart/native/raster/price-chart.ts +0 -244
  436. package/src/components/chart/rendering/chart-draw.ts +0 -270
  437. package/src/components/chart/rendering/geometry.ts +0 -95
  438. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  439. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  440. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  441. package/src/components/chart/static/chart/surface.tsx +0 -358
  442. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  443. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  444. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  445. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  446. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  447. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  448. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  449. package/src/plugins/builtin/substack/api/store.ts +0 -186
  450. package/src/plugins/builtin/substack/api/types.ts +0 -42
  451. package/src/plugins/builtin/substack/api.test.ts +0 -298
  452. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  453. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  454. package/src/plugins/builtin/substack/content.ts +0 -478
  455. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  456. package/src/plugins/builtin/substack/index.tsx +0 -49
  457. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  458. package/src/plugins/builtin/substack/model.test.ts +0 -155
  459. package/src/plugins/builtin/substack/normalize.ts +0 -343
  460. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  461. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  462. package/src/plugins/builtin/substack/pane.tsx +0 -547
  463. package/src/plugins/builtin/substack/read-state.ts +0 -30
  464. package/src/plugins/builtin/substack/table.ts +0 -111
  465. package/src/plugins/builtin/substack/types.ts +0 -71
  466. package/src/plugins/builtin/substack/utils.ts +0 -131
  467. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  468. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  469. package/src/plugins/ibkr/account-cache.ts +0 -24
  470. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  471. package/src/plugins/ibkr/config.test.ts +0 -79
  472. package/src/plugins/ibkr/config.ts +0 -250
  473. package/src/plugins/ibkr/dialogs.tsx +0 -40
  474. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  475. package/src/plugins/ibkr/flex/index.ts +0 -367
  476. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  477. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  478. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  479. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  480. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  481. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  482. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  483. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  484. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  485. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  486. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  487. package/src/plugins/ibkr/gateway/history.ts +0 -120
  488. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  489. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  490. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  491. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  492. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  493. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  494. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  495. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  496. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  497. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  498. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  499. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  500. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  501. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  502. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  503. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  504. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  505. package/src/plugins/ibkr/gateway/types.ts +0 -27
  506. package/src/plugins/ibkr/index.test.tsx +0 -162
  507. package/src/plugins/ibkr/index.tsx +0 -159
  508. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  509. package/src/plugins/ibkr/instance-selection.ts +0 -36
  510. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  511. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  512. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  513. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  514. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  515. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  516. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  517. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  518. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  519. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  520. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  521. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  522. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  523. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  524. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  525. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  526. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  527. package/src/plugins/ibkr/trade/utils.ts +0 -144
  528. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  529. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  530. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  531. package/src/plugins/ibkr/trading/state.ts +0 -335
@@ -0,0 +1,110 @@
1
+ import type { OptionContract, OptionsChain, PricePoint } from "../../../types/financials";
2
+ import {
3
+ DEFAULT_OPTION_CALC_DRAFT,
4
+ daysToExpiryFrom,
5
+ valueOption,
6
+ type OptionSide,
7
+ type OptionValuation,
8
+ } from "../options-calculator/model";
9
+
10
+ const TRADING_DAYS_PER_YEAR = 252;
11
+ const HISTORICAL_VOLATILITY_SESSIONS = 30;
12
+
13
+ export interface OptionsSummary {
14
+ atmImpliedVolatility: number | null;
15
+ historicalVolatility30d: number | null;
16
+ impliedHistoricalRatio: number | null;
17
+ expirationVolume: number;
18
+ putCallVolumeRatio: number | null;
19
+ putCallOpenInterestRatio: number | null;
20
+ }
21
+
22
+ function positive(value: number | undefined): value is number {
23
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
24
+ }
25
+
26
+ function ratio(numerator: number, denominator: number): number | null {
27
+ return denominator > 0 ? numerator / denominator : null;
28
+ }
29
+
30
+ function sum(contracts: readonly OptionContract[], field: "volume" | "openInterest"): number {
31
+ return contracts.reduce((total, contract) => {
32
+ const value = contract[field];
33
+ return total + (Number.isFinite(value) && value > 0 ? value : 0);
34
+ }, 0);
35
+ }
36
+
37
+ function atmImpliedVolatility(chain: OptionsChain, spot: number | undefined): number | null {
38
+ if (!positive(spot)) return null;
39
+ const contracts = [...chain.calls, ...chain.puts].filter((contract) => positive(contract.impliedVolatility));
40
+ if (contracts.length === 0) return null;
41
+ const distance = Math.min(...contracts.map((contract) => Math.abs(contract.strike - spot)));
42
+ const atTheMoney = contracts
43
+ .filter((contract) => Math.abs(Math.abs(contract.strike - spot) - distance) < 1e-8)
44
+ .map((contract) => contract.impliedVolatility);
45
+ return atTheMoney.reduce((total, value) => total + value, 0) / atTheMoney.length;
46
+ }
47
+
48
+ /** Annualized standard deviation of the latest 30 daily log returns. */
49
+ export function historicalVolatility30d(points: readonly PricePoint[]): number | null {
50
+ const closes = points
51
+ .map((point) => ({
52
+ close: point.close,
53
+ time: point.date instanceof Date ? point.date.getTime() : Date.parse(String(point.date)),
54
+ }))
55
+ .filter((point) => positive(point.close) && Number.isFinite(point.time))
56
+ .sort((a, b) => a.time - b.time)
57
+ .slice(-(HISTORICAL_VOLATILITY_SESSIONS + 1))
58
+ .map((point) => point.close);
59
+ if (closes.length < HISTORICAL_VOLATILITY_SESSIONS + 1) return null;
60
+
61
+ const returns = closes.slice(1).map((close, index) => Math.log(close / closes[index]!));
62
+ const mean = returns.reduce((total, value) => total + value, 0) / returns.length;
63
+ const variance = returns.reduce((total, value) => total + (value - mean) ** 2, 0) / (returns.length - 1);
64
+ return Math.sqrt(variance * TRADING_DAYS_PER_YEAR);
65
+ }
66
+
67
+ export function calculateOptionsSummary(
68
+ chain: OptionsChain,
69
+ spot: number | undefined,
70
+ priceHistory: readonly PricePoint[],
71
+ ): OptionsSummary {
72
+ const atmIv = atmImpliedVolatility(chain, spot);
73
+ const historicalVolatility = historicalVolatility30d(priceHistory);
74
+ const callVolume = sum(chain.calls, "volume");
75
+ const putVolume = sum(chain.puts, "volume");
76
+ const callOpenInterest = sum(chain.calls, "openInterest");
77
+ const putOpenInterest = sum(chain.puts, "openInterest");
78
+
79
+ return {
80
+ atmImpliedVolatility: atmIv,
81
+ historicalVolatility30d: historicalVolatility,
82
+ impliedHistoricalRatio: atmIv != null && historicalVolatility != null && historicalVolatility > 0
83
+ ? atmIv / historicalVolatility
84
+ : null,
85
+ expirationVolume: callVolume + putVolume,
86
+ putCallVolumeRatio: ratio(putVolume, callVolume),
87
+ putCallOpenInterestRatio: ratio(putOpenInterest, callOpenInterest),
88
+ };
89
+ }
90
+
91
+ export function calculateOptionGreeks(
92
+ contract: OptionContract | undefined,
93
+ side: OptionSide,
94
+ spot: number | undefined,
95
+ dividendYield: number | undefined,
96
+ now: number = Date.now(),
97
+ ): OptionValuation | undefined {
98
+ if (!contract || !positive(spot) || !positive(contract.strike) || !positive(contract.impliedVolatility)) {
99
+ return undefined;
100
+ }
101
+ return valueOption({
102
+ ...DEFAULT_OPTION_CALC_DRAFT,
103
+ side,
104
+ spot,
105
+ strike: contract.strike,
106
+ daysToExpiry: daysToExpiryFrom(contract.expiration, now),
107
+ volatility: contract.impliedVolatility,
108
+ dividendYield: Number.isFinite(dividendYield) ? dividendYield! : 0,
109
+ });
110
+ }
@@ -6,11 +6,26 @@ import {
6
6
  LIVE_STREAMING_QUICK_SETTING,
7
7
  withLiveStreamingSetting,
8
8
  } from "../shared/live-streaming";
9
+ import { OPTION_FIELD_DEFS, resolveOptionFieldIds } from "./table";
9
10
 
10
- function optionsSettings(): PaneSettingsDef {
11
+ function optionsSettings(settings: Record<string, unknown>): PaneSettingsDef {
11
12
  return {
12
13
  title: "Options Settings",
14
+ values: {
15
+ optionColumnIds: resolveOptionFieldIds(settings.optionColumnIds),
16
+ },
13
17
  fields: [
18
+ {
19
+ key: "optionColumnIds",
20
+ label: "Columns",
21
+ description: "Choose and order the fields mirrored around the strike.",
22
+ type: "ordered-multi-select",
23
+ options: OPTION_FIELD_DEFS.map((field) => ({
24
+ value: field.id,
25
+ label: field.label,
26
+ description: field.description,
27
+ })),
28
+ },
14
29
  {
15
30
  key: "chainRefreshMinutes",
16
31
  label: "Chain refresh",
@@ -38,7 +53,8 @@ export const optionsModule: PluginModule = {
38
53
  defaultMode: "floating",
39
54
  defaultFloatingSize: { width: 112, height: 28 },
40
55
  quickSettings: [LIVE_STREAMING_QUICK_SETTING],
41
- settings: (context) => withLiveStreamingSetting(optionsSettings(), context.settings),
56
+ settings: (context) => withLiveStreamingSetting(optionsSettings(context.settings), context.settings),
57
+ tableExport: true,
42
58
  },
43
59
  ],
44
60
 
@@ -1,32 +1,81 @@
1
1
  import { TextAttributes } from "../../../ui";
2
2
  import type { DataTableCell } from "../../../components";
3
3
  import type { OptionContract, OptionsChain } from "../../../types/financials";
4
- import { blendHex, colors, hoverBg } from "../../../theme/colors";
4
+ import { blendHex, colors } from "../../../theme/colors";
5
5
  import { blendForContrast, contrastRatio } from "../../../theme/color-utils";
6
6
  import { formatCompact, formatNumber } from "../../../utils/format";
7
7
  import { formatMarketPrice } from "../../../market-data/market/format";
8
- import type { OptionColumn, OptionColumnId, OptionTableRow } from "./types";
8
+ import type {
9
+ OptionColumn,
10
+ OptionFieldId,
11
+ OptionTableRow,
12
+ } from "./types";
13
+ import type { OptionSide, OptionValuation } from "../options-calculator/model";
9
14
 
10
15
  type OptionColorRole = "call" | "put" | "price" | "activity" | "iv" | "strike";
11
16
 
17
+ type OptionFieldDef = {
18
+ id: OptionFieldId;
19
+ label: string;
20
+ header: string;
21
+ width: number;
22
+ description: string;
23
+ };
24
+
12
25
  const OPTION_TEXT_MIN_CONTRAST = 4.5;
13
26
 
14
- export const OPTION_COLUMNS: Array<Omit<OptionColumn, "headerColor">> = [
15
- { id: "callOpenInterest", label: "C OI", width: 6, align: "right" },
16
- { id: "callVolume", label: "C VOL", width: 6, align: "right" },
17
- { id: "callLast", label: "C LAST", width: 7, align: "right" },
18
- { id: "callIv", label: "C IV", width: 6, align: "right" },
19
- { id: "callBid", label: "C BID", width: 7, align: "right" },
20
- { id: "callAsk", label: "C ASK", width: 7, align: "right" },
21
- { id: "strike", label: "STRIKE", width: 9, align: "right" },
22
- { id: "putBid", label: "P BID", width: 7, align: "right" },
23
- { id: "putAsk", label: "P ASK", width: 7, align: "right" },
24
- { id: "putIv", label: "P IV", width: 6, align: "right" },
25
- { id: "putLast", label: "P LAST", width: 7, align: "right" },
26
- { id: "putVolume", label: "P VOL", width: 6, align: "right" },
27
- { id: "putOpenInterest", label: "P OI", width: 6, align: "right" },
27
+ export const OPTION_FIELD_DEFS: OptionFieldDef[] = [
28
+ { id: "bid", label: "Bid", header: "BID", width: 7, description: "Best bid price." },
29
+ { id: "ask", label: "Ask", header: "ASK", width: 7, description: "Best ask price." },
30
+ { id: "last", label: "Last", header: "LAST", width: 7, description: "Last traded price." },
31
+ { id: "delta", label: "Delta", header: "Δ", width: 6, description: "Price sensitivity to a $1 move in the underlying." },
32
+ { id: "gamma", label: "Gamma", header: "Γ", width: 7, description: "Delta sensitivity to a $1 move in the underlying." },
33
+ { id: "theta", label: "Theta", header: "Θ", width: 7, description: "Estimated value decay per calendar day." },
34
+ { id: "vega", label: "Vega", header: "VEGA", width: 7, description: "Price sensitivity to one volatility point." },
35
+ { id: "rho", label: "Rho", header: "RHO", width: 7, description: "Price sensitivity to one interest-rate point." },
36
+ { id: "iv", label: "Implied volatility", header: "IV", width: 6, description: "Volatility implied by the contract price." },
37
+ { id: "volume", label: "Volume", header: "VOL", width: 6, description: "Contracts traded in the current session." },
38
+ { id: "openInterest", label: "Open interest", header: "OI", width: 6, description: "Outstanding open contracts." },
28
39
  ];
29
40
 
41
+ export const DEFAULT_OPTION_FIELD_IDS: OptionFieldId[] = ["bid", "ask", "last", "delta", "gamma"];
42
+
43
+ const OPTION_FIELDS_BY_ID = new Map(OPTION_FIELD_DEFS.map((field) => [field.id, field]));
44
+
45
+ export function resolveOptionFieldIds(value: unknown): OptionFieldId[] {
46
+ if (!Array.isArray(value)) return [...DEFAULT_OPTION_FIELD_IDS];
47
+ const seen = new Set<OptionFieldId>();
48
+ const fields = value.filter((id): id is OptionFieldId => {
49
+ if (typeof id !== "string" || !OPTION_FIELDS_BY_ID.has(id as OptionFieldId) || seen.has(id as OptionFieldId)) {
50
+ return false;
51
+ }
52
+ seen.add(id as OptionFieldId);
53
+ return true;
54
+ });
55
+ return fields.length > 0 ? fields : [...DEFAULT_OPTION_FIELD_IDS];
56
+ }
57
+
58
+ function sideColumn(side: OptionSide, field: OptionFieldId): Omit<OptionColumn, "headerColor"> {
59
+ const definition = OPTION_FIELDS_BY_ID.get(field)!;
60
+ return {
61
+ id: `${side}${field[0]!.toUpperCase()}${field.slice(1)}` as OptionColumn["id"],
62
+ field,
63
+ side,
64
+ label: `${side === "call" ? "C" : "P"} ${definition.header}`,
65
+ width: definition.width,
66
+ align: "right",
67
+ };
68
+ }
69
+
70
+ export function createOptionColumns(fieldIds: readonly OptionFieldId[]): Array<Omit<OptionColumn, "headerColor">> {
71
+ const fields = resolveOptionFieldIds(fieldIds);
72
+ return [
73
+ ...fields.map((field) => sideColumn("call", field)),
74
+ { id: "strike", field: "strike", side: null, label: "STRIKE", width: 9, align: "right" },
75
+ ...[...fields].reverse().map((field) => sideColumn("put", field)),
76
+ ];
77
+ }
78
+
30
79
  export function buildStrikeList(chain: OptionsChain): number[] {
31
80
  const set = new Set<number>();
32
81
  for (const c of chain.calls) set.add(c.strike);
@@ -68,20 +117,32 @@ export function formatIv(value: number | undefined): string {
68
117
  return `${(value * 100).toFixed(1)}%`;
69
118
  }
70
119
 
71
- export function optionColumnColor(columnId: OptionColumnId, surface = colors.bg): string {
72
- return optionRoleColor(optionColumnRole(columnId), surface);
120
+ function formatGreek(value: number | undefined): string {
121
+ if (value == null || !Number.isFinite(value)) return "\u2014";
122
+ return value.toFixed(3).replace(/^(-?)0\./, "$1.");
123
+ }
124
+
125
+ export function optionColumnColor(
126
+ column: Pick<OptionColumn, "field" | "side">,
127
+ surface = colors.bg,
128
+ ): string {
129
+ return optionRoleColor(optionColumnRole(column), surface);
130
+ }
131
+
132
+ function optionContractForColumn(row: OptionTableRow, column: OptionColumn): OptionContract | undefined {
133
+ return column.side === "call" ? row.call : row.put;
73
134
  }
74
135
 
75
- function optionContractForColumn(row: OptionTableRow, columnId: OptionColumnId): OptionContract | undefined {
76
- return columnId.startsWith("call") ? row.call : row.put;
136
+ function optionGreeksForColumn(row: OptionTableRow, column: OptionColumn): OptionValuation | undefined {
137
+ return column.side === "call" ? row.callGreeks : row.putGreeks;
77
138
  }
78
139
 
79
- function optionColumnRole(columnId: OptionColumnId): OptionColorRole {
80
- if (columnId === "strike") return "strike";
81
- if (columnId.endsWith("Iv")) return "iv";
82
- if (columnId.endsWith("Volume") || columnId.endsWith("OpenInterest")) return "activity";
83
- if (columnId.endsWith("Bid") || columnId.endsWith("Ask")) return "price";
84
- return columnId.startsWith("call") ? "call" : "put";
140
+ function optionColumnRole(column: Pick<OptionColumn, "field" | "side">): OptionColorRole {
141
+ if (column.field === "strike") return "strike";
142
+ if (column.field === "iv") return "iv";
143
+ if (column.field === "volume" || column.field === "openInterest") return "activity";
144
+ if (column.field === "bid" || column.field === "ask") return "price";
145
+ return column.side ?? "strike";
85
146
  }
86
147
 
87
148
  function optionRoleThemeColor(role: OptionColorRole): string {
@@ -131,12 +192,12 @@ function optionMutedColor(surface: string): string {
131
192
  function optionMoneynessBackground(
132
193
  row: OptionTableRow,
133
194
  contract: OptionContract | undefined,
134
- columnId: OptionColumnId,
195
+ column: OptionColumn,
135
196
  rowState: { selected: boolean },
136
197
  ): string | undefined {
137
- if (rowState.selected) return undefined;
138
- const inTheMoney = inferColumnMoneyness(row, contract, columnId);
139
- const sideColor = columnId.startsWith("call") ? colors.positive : colors.negative;
198
+ if (rowState.selected || !column.side) return undefined;
199
+ const inTheMoney = inferColumnMoneyness(row, contract, column.side);
200
+ const sideColor = column.side === "call" ? colors.positive : colors.negative;
140
201
  return inTheMoney
141
202
  ? blendHex(colors.bg, sideColor, 0.13)
142
203
  : blendHex(colors.bg, colors.neutral, 0.055);
@@ -145,34 +206,43 @@ function optionMoneynessBackground(
145
206
  function inferColumnMoneyness(
146
207
  row: OptionTableRow,
147
208
  contract: OptionContract | undefined,
148
- columnId: OptionColumnId,
209
+ side: OptionSide,
149
210
  ): boolean {
150
211
  if (contract) return contract.inTheMoney;
151
- const oppositeContract = columnId.startsWith("call") ? row.put : row.call;
212
+ const oppositeContract = side === "call" ? row.put : row.call;
152
213
  return oppositeContract ? !oppositeContract.inTheMoney : false;
153
214
  }
154
215
 
155
- function formatOptionContractCell(contract: OptionContract | undefined, column: OptionColumn): string {
216
+ function formatOptionContractCell(
217
+ row: OptionTableRow,
218
+ contract: OptionContract | undefined,
219
+ column: OptionColumn,
220
+ ): string {
156
221
  if (!contract) return "—";
157
- switch (column.id) {
158
- case "callLast":
159
- case "putLast":
222
+ const greeks = optionGreeksForColumn(row, column);
223
+ switch (column.field) {
224
+ case "last":
160
225
  return formatMarketPrice(contract.lastPrice, { assetCategory: "OPT", maxWidth: column.width });
161
- case "callBid":
162
- case "putBid":
226
+ case "bid":
163
227
  return formatMarketPrice(contract.bid, { assetCategory: "OPT", maxWidth: column.width });
164
- case "callAsk":
165
- case "putAsk":
228
+ case "ask":
166
229
  return formatMarketPrice(contract.ask, { assetCategory: "OPT", maxWidth: column.width });
167
- case "callVolume":
168
- case "putVolume":
230
+ case "volume":
169
231
  return formatCompact(contract.volume);
170
- case "callOpenInterest":
171
- case "putOpenInterest":
232
+ case "openInterest":
172
233
  return formatCompact(contract.openInterest);
173
- case "callIv":
174
- case "putIv":
234
+ case "iv":
175
235
  return formatIv(contract.impliedVolatility);
236
+ case "delta":
237
+ return formatGreek(greeks?.delta);
238
+ case "gamma":
239
+ return formatGreek(greeks?.gamma);
240
+ case "theta":
241
+ return formatGreek(greeks?.thetaPerDay);
242
+ case "vega":
243
+ return formatGreek(greeks?.vegaPerPoint);
244
+ case "rho":
245
+ return formatGreek(greeks?.rhoPerPoint);
176
246
  case "strike":
177
247
  return formatStrikeLabel(contract.strike);
178
248
  }
@@ -187,7 +257,7 @@ export function renderOptionCell(
187
257
  const selectedColor = rowState.selected ? colors.selectedText : undefined;
188
258
  const rowSurface = rowState.selected ? colors.selected : colors.bg;
189
259
 
190
- if (column.id === "strike") {
260
+ if (column.field === "strike") {
191
261
  const backgroundColor = rowState.selected
192
262
  ? undefined
193
263
  : blendHex(colors.bg, row.isPositionStrike ? colors.borderFocused : colors.header, row.isPositionStrike ? 0.18 : 0.1);
@@ -200,12 +270,12 @@ export function renderOptionCell(
200
270
  };
201
271
  }
202
272
 
203
- const contract = optionContractForColumn(row, column.id);
204
- const backgroundColor = optionMoneynessBackground(row, contract, column.id, rowState);
273
+ const contract = optionContractForColumn(row, column);
274
+ const backgroundColor = optionMoneynessBackground(row, contract, column, rowState);
205
275
  const surface = backgroundColor ?? rowSurface;
206
276
  return {
207
- text: formatOptionContractCell(contract, column),
208
- color: selectedColor ?? (contract ? optionRoleColor(optionColumnRole(column.id), surface) : optionMutedColor(surface)),
277
+ text: formatOptionContractCell(row, contract, column),
278
+ color: selectedColor ?? (contract ? optionRoleColor(optionColumnRole(column), surface) : optionMutedColor(surface)),
209
279
  backgroundColor,
210
280
  };
211
281
  }
@@ -1,27 +1,34 @@
1
1
  import type { DataTableColumn } from "../../../components";
2
2
  import type { OptionContract } from "../../../types/financials";
3
+ import type { OptionSide, OptionValuation } from "../options-calculator/model";
3
4
 
4
- export type OptionColumnId =
5
- | "callLast"
6
- | "callBid"
7
- | "callAsk"
8
- | "callVolume"
9
- | "callOpenInterest"
10
- | "callIv"
11
- | "strike"
12
- | "putLast"
13
- | "putBid"
14
- | "putAsk"
15
- | "putVolume"
16
- | "putOpenInterest"
17
- | "putIv";
5
+ export type OptionFieldId =
6
+ | "bid"
7
+ | "ask"
8
+ | "last"
9
+ | "delta"
10
+ | "gamma"
11
+ | "theta"
12
+ | "vega"
13
+ | "rho"
14
+ | "iv"
15
+ | "volume"
16
+ | "openInterest";
18
17
 
19
- export type OptionColumn = DataTableColumn & { id: OptionColumnId };
18
+ export type OptionColumnId = "strike" | `${OptionSide}${Capitalize<OptionFieldId>}`;
19
+
20
+ export type OptionColumn = DataTableColumn & {
21
+ id: OptionColumnId;
22
+ field: OptionFieldId | "strike";
23
+ side: OptionSide | null;
24
+ };
20
25
 
21
26
  export interface OptionTableRow {
22
27
  strike: number;
23
28
  call?: OptionContract;
24
29
  put?: OptionContract;
30
+ callGreeks?: OptionValuation;
31
+ putGreeks?: OptionValuation;
25
32
  isPositionStrike: boolean;
26
33
  }
27
34
 
@@ -1,7 +1,8 @@
1
1
  import { afterEach, expect, test } from "bun:test";
2
2
  import { act, useState } from "react";
3
3
  import type { ScrollBoxRenderable } from "@opentui/core";
4
- import { testRender } from "../../../renderers/opentui/test-utils";
4
+ import { takeSavedTextFile, testRender } from "../../../renderers/opentui/test-utils";
5
+ import { exportPaneTable, hasPaneTableExporter } from "../../../state/pane-table-export-registry";
5
6
  import { MarketDataCoordinator, setSharedMarketDataCoordinator } from "../../../market-data/coordinator";
6
7
  import { AppContext, PaneInstanceProvider, createInitialState } from "../../../state/app/context";
7
8
  import { createTestDataProvider } from "../../../test-support/data-provider";
@@ -152,6 +153,33 @@ afterEach(async () => {
152
153
  setSharedMarketDataCoordinator(null);
153
154
  });
154
155
 
156
+ test("exposes exactly one exportable table so CSV export stays wired up", async () => {
157
+ const provider = createTestDataProvider({
158
+ getOptionsChain: async () => makeChain([100, 101], 101),
159
+ });
160
+ setSharedMarketDataCoordinator(new MarketDataCoordinator(provider));
161
+
162
+ await act(async () => {
163
+ testSetup = await testRender(
164
+ <OptionsHarness ticker={makeTicker("AAPL")} quotePrice={101} />,
165
+ { width: 124, height: 12 },
166
+ );
167
+ });
168
+ await renderSettled();
169
+
170
+ // `tableExport: true` on the options pane only works when the pane mounts a single
171
+ // DataTable; a second concurrent table would silently disable the export action.
172
+ expect(hasPaneTableExporter(TEST_PANE_ID)).toBe(true);
173
+
174
+ const location = await exportPaneTable(TEST_PANE_ID, "options.csv");
175
+ expect(location).toBe("~/Downloads/options.csv");
176
+
177
+ const saved = takeSavedTextFile();
178
+ expect(saved?.name).toBe("options.csv");
179
+ expect(saved?.text.split("\n")[0]).toContain("STRIKE");
180
+ expect(saved?.text).toContain("101");
181
+ });
182
+
155
183
  test("defaults the table around the nearest strike to the current quote", async () => {
156
184
  const strikes = Array.from({ length: 25 }, (_, index) => 50 + index * 5);
157
185
  const provider = createTestDataProvider({
@@ -176,6 +204,30 @@ test("defaults the table around the nearest strike to the current quote", async
176
204
  expect(frame).not.toContain(" 50 ");
177
205
  });
178
206
 
207
+ test("shows volatility statistics and mirrored default Greeks", async () => {
208
+ const provider = createTestDataProvider({
209
+ getOptionsChain: async () => makeChain([100, 101], 101),
210
+ });
211
+ setSharedMarketDataCoordinator(new MarketDataCoordinator(provider));
212
+
213
+ await act(async () => {
214
+ testSetup = await testRender(
215
+ <OptionsHarness ticker={makeTicker("AAPL")} quotePrice={101} />,
216
+ { width: 124, height: 16 },
217
+ );
218
+ });
219
+ await renderSettled();
220
+
221
+ const frame = testSetup!.captureCharFrame();
222
+ expect(frame).toContain("ATM IV 20.0%");
223
+ expect(frame).toContain("HV30 —");
224
+ expect(frame).toContain("EXP VOL");
225
+ expect(frame).toContain("C Δ");
226
+ expect(frame).toContain("C Γ");
227
+ expect(frame).toContain("P Γ");
228
+ expect(frame).toContain("P Δ");
229
+ });
230
+
179
231
  test("streams live quotes without resetting manual scroll", async () => {
180
232
  const strikes = Array.from({ length: 100 }, (_, index) => 50 + index);
181
233
  const subscriptions: QuoteSubscriptionTarget[][] = [];