gloomberb 0.1.0 → 0.3.1

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 (311) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +68 -121
  3. package/bin/gloomberb +3 -0
  4. package/package.json +13 -4
  5. package/patches/@opentui%2Fcore@0.1.90.patch +356 -0
  6. package/src/app.tsx +1330 -0
  7. package/src/brokers/sync-broker-instance.test.ts +100 -0
  8. package/src/brokers/sync-broker-instance.ts +341 -0
  9. package/src/cli/commands/plugins.ts +173 -0
  10. package/src/cli/commands/portfolio.ts +210 -0
  11. package/src/cli/commands/search.ts +120 -0
  12. package/src/cli/commands/ticker.ts +375 -0
  13. package/src/cli/commands/watchlist.ts +210 -0
  14. package/src/cli/context.ts +62 -0
  15. package/src/cli/errors.ts +13 -0
  16. package/src/cli/helpers.ts +85 -0
  17. package/src/cli/index.ts +143 -0
  18. package/src/cli/ticker-resolution.ts +39 -0
  19. package/src/cli/types.ts +15 -0
  20. package/src/cli.test.ts +361 -0
  21. package/src/components/chart/chart-data.ts +239 -0
  22. package/src/components/chart/chart-renderer.ts +1199 -0
  23. package/src/components/chart/chart-types.ts +82 -0
  24. package/src/components/chart/chart-viewport.ts +21 -0
  25. package/src/components/chart/chart.test.ts +439 -0
  26. package/src/components/chart/comparison-chart-data.test.ts +109 -0
  27. package/src/components/chart/comparison-chart-data.ts +316 -0
  28. package/src/components/chart/comparison-chart-renderer.test.ts +84 -0
  29. package/src/components/chart/comparison-chart-renderer.ts +354 -0
  30. package/src/components/chart/comparison-stock-chart.tsx +1486 -0
  31. package/src/components/chart/cursor-motion.ts +48 -0
  32. package/src/components/chart/native/chart-rasterizer.ts +732 -0
  33. package/src/components/chart/native/kitty-adapter.ts +12 -0
  34. package/src/components/chart/native/kitty-manager.ts +142 -0
  35. package/src/components/chart/native/kitty-protocol.ts +110 -0
  36. package/src/components/chart/native/kitty-support.ts +71 -0
  37. package/src/components/chart/native/kitty.test.ts +620 -0
  38. package/src/components/chart/native/renderer-selection.ts +24 -0
  39. package/src/components/chart/native/surface-manager.ts +225 -0
  40. package/src/components/chart/native/surface-sync.ts +34 -0
  41. package/src/components/chart/stock-chart-renderer-switch.test.tsx +165 -0
  42. package/src/components/chart/stock-chart.test.ts +107 -0
  43. package/src/components/chart/stock-chart.tsx +1556 -0
  44. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +137 -0
  45. package/src/components/command-bar/command-bar.test.tsx +1524 -0
  46. package/src/components/command-bar/command-bar.tsx +4823 -0
  47. package/src/components/command-bar/command-registry.ts +261 -0
  48. package/src/components/command-bar/helpers.test.ts +224 -0
  49. package/src/components/command-bar/helpers.ts +371 -0
  50. package/src/components/command-bar/root-shortcuts.ts +158 -0
  51. package/src/components/command-bar/view-model.test.ts +152 -0
  52. package/src/components/command-bar/view-model.ts +185 -0
  53. package/src/components/command-bar/workflow-ops.test.ts +70 -0
  54. package/src/components/command-bar/workflow-ops.ts +433 -0
  55. package/src/components/command-bar/workflow-types.ts +127 -0
  56. package/src/components/detail-feed-view.test.tsx +105 -0
  57. package/src/components/detail-feed-view.tsx +295 -0
  58. package/src/components/index.ts +32 -0
  59. package/src/components/layout/floating-pane.tsx +49 -0
  60. package/src/components/layout/header.tsx +129 -0
  61. package/src/components/layout/pane-header.tsx +44 -0
  62. package/src/components/layout/pane.tsx +49 -0
  63. package/src/components/layout/pixel-mouse-mode.test.ts +122 -0
  64. package/src/components/layout/pixel-mouse.test.ts +40 -0
  65. package/src/components/layout/shell.test.tsx +625 -0
  66. package/src/components/layout/shell.tsx +1200 -0
  67. package/src/components/layout/status-bar.test.tsx +173 -0
  68. package/src/components/layout/status-bar.tsx +138 -0
  69. package/src/components/markdown-editor.tsx +134 -0
  70. package/src/components/onboarding/onboarding-wizard.test.tsx +184 -0
  71. package/src/components/onboarding/onboarding-wizard.tsx +1365 -0
  72. package/src/components/pane-settings-dialog.tsx +468 -0
  73. package/src/components/pane-template-wizard.test.tsx +52 -0
  74. package/src/components/pane-template-wizard.tsx +199 -0
  75. package/src/components/price-selector-dialog.tsx +195 -0
  76. package/src/components/spinner.tsx +2 -0
  77. package/src/components/tab-bar.tsx +17 -0
  78. package/src/components/ticker-badge-text.test.tsx +160 -0
  79. package/src/components/ticker-badge-text.tsx +58 -0
  80. package/src/components/ticker-badge.tsx +88 -0
  81. package/src/components/ticker-list-table.tsx +188 -0
  82. package/src/components/toggle-list.tsx +74 -0
  83. package/src/components/ui/button.tsx +77 -0
  84. package/src/components/ui/data-table.tsx +210 -0
  85. package/src/components/ui/external-link.tsx +22 -0
  86. package/src/components/ui/fields.tsx +187 -0
  87. package/src/components/ui/frame.tsx +73 -0
  88. package/src/components/ui/index.ts +53 -0
  89. package/src/components/ui/list-view.tsx +166 -0
  90. package/src/components/ui/loading.tsx +70 -0
  91. package/src/components/ui/status.tsx +83 -0
  92. package/src/components/ui/tabs.tsx +47 -0
  93. package/src/components/ui/toggle.tsx +164 -0
  94. package/src/components/ui/ui.test.tsx +134 -0
  95. package/src/data/app-persistence.ts +25 -0
  96. package/src/data/config-store.test.ts +310 -0
  97. package/src/data/config-store.ts +469 -0
  98. package/src/data/plugin-state-store.ts +102 -0
  99. package/src/data/resource-store.ts +323 -0
  100. package/src/data/session-store.ts +52 -0
  101. package/src/data/sqlite-cache.test.ts +103 -0
  102. package/src/data/sqlite-database.ts +62 -0
  103. package/src/data/sqlite-json.ts +19 -0
  104. package/src/data/ticker-repository.ts +45 -0
  105. package/src/data/ticker-store.ts +46 -0
  106. package/src/index.tsx +67 -0
  107. package/src/market-data/coordinator.test.ts +369 -0
  108. package/src/market-data/coordinator.ts +580 -0
  109. package/src/market-data/hooks.tsx +234 -0
  110. package/src/market-data/query-store.ts +28 -0
  111. package/src/market-data/request-types.test.ts +64 -0
  112. package/src/market-data/request-types.ts +70 -0
  113. package/src/market-data/result-types.ts +56 -0
  114. package/src/market-data/selectors.ts +166 -0
  115. package/src/market-data/trace.ts +7 -0
  116. package/src/pane-settings.test.ts +71 -0
  117. package/src/pane-settings.ts +73 -0
  118. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +338 -0
  119. package/src/plugins/builtin/ai/gloomberb-cli.ts +70 -0
  120. package/src/plugins/builtin/ai/index.test.tsx +139 -0
  121. package/src/plugins/builtin/ai/index.tsx +86 -0
  122. package/src/plugins/builtin/ai/providers.ts +99 -0
  123. package/src/plugins/builtin/ai/runner.ts +82 -0
  124. package/src/plugins/builtin/ai/screener-contract.test.ts +117 -0
  125. package/src/plugins/builtin/ai/screener-contract.ts +174 -0
  126. package/src/plugins/builtin/ai/screener-pane.test.tsx +365 -0
  127. package/src/plugins/builtin/ai/screener-pane.tsx +1161 -0
  128. package/src/plugins/builtin/ai/settings.ts +79 -0
  129. package/src/plugins/builtin/ai/ticker-context.ts +64 -0
  130. package/src/plugins/builtin/ai/utils.ts +71 -0
  131. package/src/plugins/builtin/ask-ai.test.tsx +258 -0
  132. package/src/plugins/builtin/ask-ai.tsx +9 -0
  133. package/src/plugins/builtin/chat-controller.test.ts +217 -0
  134. package/src/plugins/builtin/chat-controller.ts +340 -0
  135. package/src/plugins/builtin/chat.test.tsx +451 -0
  136. package/src/plugins/builtin/chat.tsx +620 -0
  137. package/src/plugins/builtin/comparison-chart.test.tsx +277 -0
  138. package/src/plugins/builtin/comparison-chart.tsx +169 -0
  139. package/src/plugins/builtin/data-providers.ts +16 -0
  140. package/src/plugins/builtin/debug.tsx +351 -0
  141. package/src/plugins/builtin/help.test.tsx +110 -0
  142. package/src/plugins/builtin/help.tsx +281 -0
  143. package/src/plugins/builtin/layout-manager.tsx +264 -0
  144. package/src/plugins/builtin/manual-entry.tsx +25 -0
  145. package/src/plugins/builtin/news.tsx +143 -0
  146. package/src/plugins/builtin/notes-files.ts +58 -0
  147. package/src/plugins/builtin/notes.tsx +396 -0
  148. package/src/plugins/builtin/options-availability.test.tsx +179 -0
  149. package/src/plugins/builtin/options-availability.ts +158 -0
  150. package/src/plugins/builtin/options.tsx +364 -0
  151. package/src/plugins/builtin/portfolio-list/header.tsx +227 -0
  152. package/src/plugins/builtin/portfolio-list/index.tsx +99 -0
  153. package/src/plugins/builtin/portfolio-list/metrics.ts +436 -0
  154. package/src/plugins/builtin/portfolio-list/pane.tsx +533 -0
  155. package/src/plugins/builtin/portfolio-list/settings.ts +280 -0
  156. package/src/plugins/builtin/portfolio-list/summary.tsx +270 -0
  157. package/src/plugins/builtin/portfolio-list/table.tsx +64 -0
  158. package/src/plugins/builtin/portfolio-list.test.ts +74 -0
  159. package/src/plugins/builtin/portfolio-list.test.tsx +1164 -0
  160. package/src/plugins/builtin/portfolio-metrics.test.ts +153 -0
  161. package/src/plugins/builtin/quote-monitor.test.tsx +114 -0
  162. package/src/plugins/builtin/sec.tsx +250 -0
  163. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +43 -0
  164. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +390 -0
  165. package/src/plugins/builtin/ticker-detail/index.tsx +80 -0
  166. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +242 -0
  167. package/src/plugins/builtin/ticker-detail/pane.tsx +183 -0
  168. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +83 -0
  169. package/src/plugins/builtin/ticker-detail/settings.ts +132 -0
  170. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +193 -0
  171. package/src/plugins/builtin/ticker-detail.test.tsx +578 -0
  172. package/src/plugins/builtin/yahoo.ts +18 -0
  173. package/src/plugins/event-bus.ts +37 -0
  174. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +35 -0
  175. package/src/plugins/ibkr/account-cache.test.ts +101 -0
  176. package/src/plugins/ibkr/account-cache.ts +137 -0
  177. package/src/plugins/ibkr/broker-adapter.ts +168 -0
  178. package/src/plugins/ibkr/config.test.ts +68 -0
  179. package/src/plugins/ibkr/config.ts +262 -0
  180. package/src/plugins/ibkr/dialogs.tsx +83 -0
  181. package/src/plugins/ibkr/flex.test.ts +107 -0
  182. package/src/plugins/ibkr/flex.ts +267 -0
  183. package/src/plugins/ibkr/fundamental-parser.ts +149 -0
  184. package/src/plugins/ibkr/gateway-helpers.ts +28 -0
  185. package/src/plugins/ibkr/gateway-selection.ts +51 -0
  186. package/src/plugins/ibkr/gateway-service.test.ts +450 -0
  187. package/src/plugins/ibkr/gateway-service.ts +2228 -0
  188. package/src/plugins/ibkr/index.test.tsx +183 -0
  189. package/src/plugins/ibkr/index.tsx +153 -0
  190. package/src/plugins/ibkr/instance-selection.test.ts +121 -0
  191. package/src/plugins/ibkr/instance-selection.ts +36 -0
  192. package/src/plugins/ibkr/price-normalization.test.ts +38 -0
  193. package/src/plugins/ibkr/price-normalization.ts +59 -0
  194. package/src/plugins/ibkr/trade-badge.tsx +27 -0
  195. package/src/plugins/ibkr/trade-tab.test.tsx +225 -0
  196. package/src/plugins/ibkr/trade-tab.tsx +1050 -0
  197. package/src/plugins/ibkr/trade-utils.test.ts +175 -0
  198. package/src/plugins/ibkr/trade-utils.ts +149 -0
  199. package/src/plugins/ibkr/trading-pane.tsx +438 -0
  200. package/src/plugins/ibkr/trading-state.test.ts +109 -0
  201. package/src/plugins/ibkr/trading-state.ts +339 -0
  202. package/src/plugins/loader.ts +71 -0
  203. package/src/plugins/pane-manager.test.ts +103 -0
  204. package/src/plugins/pane-manager.ts +974 -0
  205. package/src/plugins/plugin-persistence.ts +85 -0
  206. package/src/plugins/plugin-runtime.test.tsx +185 -0
  207. package/src/plugins/plugin-runtime.tsx +175 -0
  208. package/src/plugins/prediction-markets/cache.ts +114 -0
  209. package/src/plugins/prediction-markets/categories.ts +223 -0
  210. package/src/plugins/prediction-markets/chart.tsx +512 -0
  211. package/src/plugins/prediction-markets/columns.ts +133 -0
  212. package/src/plugins/prediction-markets/controller-data.ts +614 -0
  213. package/src/plugins/prediction-markets/controller.ts +725 -0
  214. package/src/plugins/prediction-markets/detail/book.tsx +118 -0
  215. package/src/plugins/prediction-markets/detail/outcomes.tsx +102 -0
  216. package/src/plugins/prediction-markets/detail/overview.tsx +73 -0
  217. package/src/plugins/prediction-markets/detail/pane.tsx +308 -0
  218. package/src/plugins/prediction-markets/detail/rules.tsx +16 -0
  219. package/src/plugins/prediction-markets/detail/shared.tsx +29 -0
  220. package/src/plugins/prediction-markets/detail/trades.tsx +30 -0
  221. package/src/plugins/prediction-markets/detail.test.tsx +47 -0
  222. package/src/plugins/prediction-markets/index.tsx +102 -0
  223. package/src/plugins/prediction-markets/keyboard.ts +64 -0
  224. package/src/plugins/prediction-markets/launch.test.ts +126 -0
  225. package/src/plugins/prediction-markets/launch.ts +240 -0
  226. package/src/plugins/prediction-markets/metrics.ts +311 -0
  227. package/src/plugins/prediction-markets/navigation.ts +90 -0
  228. package/src/plugins/prediction-markets/outcome-order.ts +19 -0
  229. package/src/plugins/prediction-markets/pane.test.tsx +425 -0
  230. package/src/plugins/prediction-markets/pane.tsx +294 -0
  231. package/src/plugins/prediction-markets/plugin.test.tsx +570 -0
  232. package/src/plugins/prediction-markets/rows.ts +229 -0
  233. package/src/plugins/prediction-markets/services/fetch.ts +97 -0
  234. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +555 -0
  235. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +660 -0
  236. package/src/plugins/prediction-markets/services/polymarket-ws.ts +108 -0
  237. package/src/plugins/prediction-markets/settings.ts +164 -0
  238. package/src/plugins/prediction-markets/status.ts +24 -0
  239. package/src/plugins/prediction-markets/table.tsx +84 -0
  240. package/src/plugins/prediction-markets/test-helpers.tsx +636 -0
  241. package/src/plugins/prediction-markets/types.ts +186 -0
  242. package/src/plugins/registry.ts +633 -0
  243. package/src/sources/exchange-rates.ts +18 -0
  244. package/src/sources/gloomberb-cloud.test.ts +199 -0
  245. package/src/sources/gloomberb-cloud.ts +326 -0
  246. package/src/sources/provider-errors.ts +19 -0
  247. package/src/sources/provider-router.test.ts +1476 -0
  248. package/src/sources/provider-router.ts +1405 -0
  249. package/src/sources/sec-edgar.test.ts +286 -0
  250. package/src/sources/sec-edgar.ts +526 -0
  251. package/src/sources/yahoo-finance.test.ts +14 -0
  252. package/src/sources/yahoo-finance.ts +1017 -0
  253. package/src/state/app-activity.test.ts +41 -0
  254. package/src/state/app-activity.ts +106 -0
  255. package/src/state/app-bootstrap.test.ts +471 -0
  256. package/src/state/app-bootstrap.ts +308 -0
  257. package/src/state/app-context.test.ts +406 -0
  258. package/src/state/app-context.test.tsx +132 -0
  259. package/src/state/app-context.tsx +969 -0
  260. package/src/state/selectors.ts +67 -0
  261. package/src/state/session-persistence.test.ts +128 -0
  262. package/src/state/session-persistence.ts +161 -0
  263. package/src/state/ticker-refresh-queue.test.ts +46 -0
  264. package/src/state/ticker-refresh-queue.ts +40 -0
  265. package/src/state/use-inline-tickers.ts +174 -0
  266. package/src/state/use-quote-streaming.test.ts +37 -0
  267. package/src/state/use-quote-streaming.test.tsx +70 -0
  268. package/src/state/use-quote-streaming.ts +80 -0
  269. package/src/state/use-session-snapshot.ts +84 -0
  270. package/src/test-support/data-provider.ts +22 -0
  271. package/src/theme/color-utils.ts +67 -0
  272. package/src/theme/colors.ts +128 -0
  273. package/src/theme/themes.test.ts +45 -0
  274. package/src/theme/themes.ts +491 -0
  275. package/src/types/broker.ts +105 -0
  276. package/src/types/config.ts +434 -0
  277. package/src/types/data-provider.ts +81 -0
  278. package/src/types/financials.ts +142 -0
  279. package/src/types/instrument.ts +29 -0
  280. package/src/types/persistence.ts +21 -0
  281. package/src/types/plugin.ts +354 -0
  282. package/src/types/ticker.ts +58 -0
  283. package/src/types/trading.ts +85 -0
  284. package/src/updater.test.ts +162 -0
  285. package/src/updater.ts +247 -0
  286. package/src/utils/api-client.ts +666 -0
  287. package/src/utils/ascii-chart.ts +172 -0
  288. package/src/utils/broker-instances.ts +47 -0
  289. package/src/utils/cli-output.ts +83 -0
  290. package/src/utils/currency-units.test.ts +49 -0
  291. package/src/utils/currency-units.ts +82 -0
  292. package/src/utils/debug-log.ts +133 -0
  293. package/src/utils/exchange-rate-map.ts +8 -0
  294. package/src/utils/format.ts +158 -0
  295. package/src/utils/fuzzy-search.ts +34 -0
  296. package/src/utils/market-status.ts +95 -0
  297. package/src/utils/options.ts +86 -0
  298. package/src/utils/price-history.ts +39 -0
  299. package/src/utils/quote-time.test.ts +22 -0
  300. package/src/utils/quote-time.ts +50 -0
  301. package/src/utils/sec.test.ts +55 -0
  302. package/src/utils/sec.ts +45 -0
  303. package/src/utils/ticker-list.test.ts +22 -0
  304. package/src/utils/ticker-list.ts +35 -0
  305. package/src/utils/ticker-metadata.ts +51 -0
  306. package/src/utils/ticker-search.test.ts +227 -0
  307. package/src/utils/ticker-search.ts +764 -0
  308. package/src/utils/ticker-tokenizer.test.ts +48 -0
  309. package/src/utils/ticker-tokenizer.ts +72 -0
  310. package/src/version.ts +1 -0
  311. package/install.mjs +0 -69
@@ -0,0 +1,512 @@
1
+ import { useEffect, useMemo, useRef, useState } from "react";
2
+ import { TextAttributes, type BoxRenderable } from "@opentui/core";
3
+ import { useRenderer } from "@opentui/react";
4
+ import {
5
+ buildChartScene,
6
+ renderChart,
7
+ resolveChartPalette,
8
+ type StyledContent,
9
+ } from "../../components/chart/chart-renderer";
10
+ import {
11
+ computeBitmapSize,
12
+ renderNativeChartBase,
13
+ type CellRect,
14
+ type NativeChartBitmap,
15
+ } from "../../components/chart/native/chart-rasterizer";
16
+ import {
17
+ ensureKittySupport,
18
+ getCachedKittySupport,
19
+ } from "../../components/chart/native/kitty-support";
20
+ import { resolveChartRendererState } from "../../components/chart/native/renderer-selection";
21
+ import { getNativeSurfaceManager } from "../../components/chart/native/surface-manager";
22
+ import { syncCachedNativeSurface } from "../../components/chart/native/surface-sync";
23
+ import { projectChartData } from "../../components/chart/chart-data";
24
+ import { useAppState, usePaneInstanceId } from "../../state/app-context";
25
+ import { EmptyState } from "../../components/ui/status";
26
+ import { colors } from "../../theme/colors";
27
+ import { formatNumber, formatPercentRaw } from "../../utils/format";
28
+ import type { PricePoint } from "../../types/financials";
29
+ import type { PredictionHistoryPoint, PredictionHistoryRange } from "./types";
30
+
31
+ const RANGES: PredictionHistoryRange[] = ["1D", "1W", "1M", "ALL"];
32
+ const AXIS_WIDTH = 8;
33
+
34
+ interface RenderableNode {
35
+ x: number;
36
+ y: number;
37
+ width: number;
38
+ height: number;
39
+ parent: RenderableNode | null;
40
+ }
41
+
42
+ function coercePredictionPointDate(value: unknown): Date | null {
43
+ if (value instanceof Date) {
44
+ return Number.isFinite(value.getTime()) ? value : null;
45
+ }
46
+ if (typeof value === "string" || typeof value === "number") {
47
+ const next = new Date(value);
48
+ return Number.isFinite(next.getTime()) ? next : null;
49
+ }
50
+ return null;
51
+ }
52
+
53
+ function toPricePoints(points: PredictionHistoryPoint[]): PricePoint[] {
54
+ return points.flatMap((point) => {
55
+ const date = coercePredictionPointDate(point.date);
56
+ if (!date) return [];
57
+ return [
58
+ {
59
+ date,
60
+ close: point.close,
61
+ open: point.open,
62
+ high: point.high,
63
+ low: point.low,
64
+ volume: point.volume,
65
+ },
66
+ ];
67
+ });
68
+ }
69
+
70
+ function extractCellRect(renderable: RenderableNode): CellRect {
71
+ return {
72
+ x: renderable.x,
73
+ y: renderable.y,
74
+ width: renderable.width,
75
+ height: renderable.height,
76
+ };
77
+ }
78
+
79
+ function intersectCellRects(left: CellRect, right: CellRect): CellRect | null {
80
+ const x = Math.max(left.x, right.x);
81
+ const y = Math.max(left.y, right.y);
82
+ const maxX = Math.min(left.x + left.width, right.x + right.width);
83
+ const maxY = Math.min(left.y + left.height, right.y + right.height);
84
+ if (maxX <= x || maxY <= y) return null;
85
+ return { x, y, width: maxX - x, height: maxY - y };
86
+ }
87
+
88
+ function resolveVisibleRect(
89
+ renderable: RenderableNode | null,
90
+ terminalWidth: number,
91
+ terminalHeight: number,
92
+ ): CellRect | null {
93
+ if (!renderable) return null;
94
+
95
+ let visible: CellRect = {
96
+ x: 0,
97
+ y: 0,
98
+ width: terminalWidth,
99
+ height: terminalHeight,
100
+ };
101
+ let current: RenderableNode | null = renderable;
102
+
103
+ while (current) {
104
+ const nextVisible = intersectCellRects(visible, extractCellRect(current));
105
+ if (!nextVisible) return null;
106
+ visible = nextVisible;
107
+ current = current.parent;
108
+ }
109
+
110
+ return visible;
111
+ }
112
+
113
+ function buildBlankPlotLines(width: number, height: number): string[] {
114
+ return Array.from({ length: height }, () => " ".repeat(width));
115
+ }
116
+
117
+ function buildNativeBitmapKey(
118
+ points: PricePoint[],
119
+ pixelWidth: number,
120
+ pixelHeight: number,
121
+ paletteId: string,
122
+ ): string {
123
+ const fingerprint = points
124
+ .map(
125
+ (point) =>
126
+ `${point.date.getTime()}:${point.open}:${point.high}:${point.low}:${point.close}:${point.volume ?? 0}`,
127
+ )
128
+ .join("|");
129
+ return [pixelWidth, pixelHeight, paletteId, fingerprint].join("::");
130
+ }
131
+
132
+ export function PredictionMarketChart({
133
+ history,
134
+ width,
135
+ height,
136
+ range,
137
+ onRangeSelect,
138
+ }: {
139
+ history: PredictionHistoryPoint[];
140
+ width: number;
141
+ height: number;
142
+ range: PredictionHistoryRange;
143
+ onRangeSelect: (range: PredictionHistoryRange) => void;
144
+ }) {
145
+ const { state } = useAppState();
146
+ const renderer = useRenderer();
147
+ const paneId = usePaneInstanceId();
148
+ const nativeSurfaceManager = useMemo(
149
+ () => getNativeSurfaceManager(renderer),
150
+ [renderer],
151
+ );
152
+ const [kittySupport, setKittySupport] = useState<boolean | null>(() =>
153
+ getCachedKittySupport(renderer),
154
+ );
155
+ const plotRef = useRef<BoxRenderable>(null);
156
+ const nativeSurfaceIdRef = useRef(`prediction-chart-surface:${paneId}`);
157
+ const lastNativeBitmapRef = useRef<{
158
+ key: string;
159
+ bitmap: NativeChartBitmap;
160
+ } | null>(null);
161
+ const lastNativeGeometryRef = useRef<{
162
+ rect: CellRect;
163
+ visibleRect: CellRect | null;
164
+ } | null>(null);
165
+
166
+ const chartWidth = Math.max(width - AXIS_WIDTH - 2, 18);
167
+ const chartHeight = Math.max(height - 2, 6);
168
+ const hasHistory = history.length > 0;
169
+
170
+ useEffect(() => {
171
+ if (kittySupport !== null) return;
172
+ let disposed = false;
173
+ ensureKittySupport(renderer)
174
+ .then((supported) => {
175
+ if (!disposed) setKittySupport(supported);
176
+ })
177
+ .catch(() => {
178
+ if (!disposed) setKittySupport(false);
179
+ });
180
+ return () => {
181
+ disposed = true;
182
+ };
183
+ }, [kittySupport, renderer]);
184
+
185
+ const pricePoints = useMemo(() => toPricePoints(history), [history]);
186
+ const projection = useMemo(
187
+ () => projectChartData(pricePoints, chartWidth, "area"),
188
+ [chartWidth, pricePoints],
189
+ );
190
+ const first = projection.points[0] ?? null;
191
+ const last = projection.points[projection.points.length - 1] ?? null;
192
+ const delta = first && last ? last.close - first.close : 0;
193
+ const deltaPct = first?.close ? (delta / first.close) * 100 : 0;
194
+ const palette = useMemo(
195
+ () =>
196
+ resolveChartPalette(
197
+ colors,
198
+ delta > 0 ? "positive" : delta < 0 ? "negative" : "neutral",
199
+ ),
200
+ [delta],
201
+ );
202
+ const rendererState = resolveChartRendererState(
203
+ state.config.chartPreferences.renderer,
204
+ kittySupport,
205
+ renderer.resolution,
206
+ );
207
+ const effectiveRenderer = rendererState.renderer;
208
+
209
+ const rendered = useMemo(
210
+ () =>
211
+ renderChart(projection.points, {
212
+ width: chartWidth,
213
+ height: chartHeight,
214
+ mode: "area",
215
+ showVolume: false,
216
+ volumeHeight: 0,
217
+ cursorX: null,
218
+ cursorY: null,
219
+ colors: palette,
220
+ axisMode: "price",
221
+ currency: "USD",
222
+ }),
223
+ [chartHeight, chartWidth, palette, projection.points],
224
+ );
225
+
226
+ const nativeScene = useMemo(
227
+ () =>
228
+ buildChartScene(projection.points, {
229
+ width: chartWidth,
230
+ height: chartHeight,
231
+ showVolume: false,
232
+ volumeHeight: 0,
233
+ cursorX: null,
234
+ cursorY: null,
235
+ mode: "area",
236
+ axisMode: "price",
237
+ colors: palette,
238
+ }),
239
+ [chartHeight, chartWidth, palette, projection.points],
240
+ );
241
+
242
+ const axisByRow = useMemo(
243
+ () => new Map(rendered.axisLabels.map((entry) => [entry.row, entry.label])),
244
+ [rendered.axisLabels],
245
+ );
246
+ const blankPlotLines = useMemo(
247
+ () => buildBlankPlotLines(chartWidth, chartHeight),
248
+ [chartHeight, chartWidth],
249
+ );
250
+
251
+ useEffect(
252
+ () => () => {
253
+ lastNativeBitmapRef.current = null;
254
+ lastNativeGeometryRef.current = null;
255
+ nativeSurfaceManager.removeSurface(nativeSurfaceIdRef.current);
256
+ renderer.requestRender();
257
+ },
258
+ [nativeSurfaceManager, renderer],
259
+ );
260
+
261
+ useEffect(() => {
262
+ if (effectiveRenderer === "kitty" && rendererState.nativeReady) return;
263
+ lastNativeBitmapRef.current = null;
264
+ lastNativeGeometryRef.current = null;
265
+ nativeSurfaceManager.removeSurface(nativeSurfaceIdRef.current);
266
+ }, [effectiveRenderer, nativeSurfaceManager, rendererState.nativeReady]);
267
+
268
+ useEffect(() => {
269
+ if (
270
+ effectiveRenderer !== "kitty" ||
271
+ !rendererState.nativeReady ||
272
+ !plotRef.current
273
+ )
274
+ return;
275
+ const plot = plotRef.current;
276
+ let mountTimer: ReturnType<typeof setTimeout> | null = null;
277
+
278
+ const syncPlacement = () => {
279
+ if (
280
+ effectiveRenderer !== "kitty" ||
281
+ !rendererState.nativeReady ||
282
+ !plotRef.current
283
+ )
284
+ return;
285
+ const rect = extractCellRect(plotRef.current);
286
+ const visibleRect = resolveVisibleRect(
287
+ plotRef.current,
288
+ renderer.terminalWidth,
289
+ renderer.terminalHeight,
290
+ );
291
+ const previous = lastNativeGeometryRef.current;
292
+ if (
293
+ previous &&
294
+ previous.rect.x === rect.x &&
295
+ previous.rect.y === rect.y &&
296
+ previous.rect.width === rect.width &&
297
+ previous.rect.height === rect.height &&
298
+ previous.visibleRect?.x === visibleRect?.x &&
299
+ previous.visibleRect?.y === visibleRect?.y &&
300
+ previous.visibleRect?.width === visibleRect?.width &&
301
+ previous.visibleRect?.height === visibleRect?.height
302
+ ) {
303
+ return;
304
+ }
305
+ lastNativeGeometryRef.current = { rect, visibleRect };
306
+ syncCachedNativeSurface(
307
+ nativeSurfaceManager,
308
+ nativeSurfaceIdRef.current,
309
+ { paneId, rect, visibleRect },
310
+ lastNativeBitmapRef.current,
311
+ );
312
+ };
313
+
314
+ plot.onLifecyclePass = syncPlacement;
315
+ renderer.registerLifecyclePass(plot);
316
+ syncPlacement();
317
+ mountTimer = setTimeout(() => {
318
+ syncPlacement();
319
+ renderer.requestRender();
320
+ }, 0);
321
+
322
+ return () => {
323
+ if (mountTimer) clearTimeout(mountTimer);
324
+ plot.onLifecyclePass = null;
325
+ renderer.unregisterLifecyclePass(plot);
326
+ lastNativeGeometryRef.current = null;
327
+ };
328
+ }, [
329
+ effectiveRenderer,
330
+ nativeSurfaceManager,
331
+ paneId,
332
+ renderer,
333
+ rendererState.nativeReady,
334
+ ]);
335
+
336
+ useEffect(() => {
337
+ if (
338
+ effectiveRenderer !== "kitty" ||
339
+ !rendererState.nativeReady ||
340
+ !renderer.resolution ||
341
+ !plotRef.current ||
342
+ !nativeScene
343
+ ) {
344
+ lastNativeBitmapRef.current = null;
345
+ nativeSurfaceManager.removeSurface(nativeSurfaceIdRef.current);
346
+ return;
347
+ }
348
+
349
+ const plotRect = extractCellRect(plotRef.current);
350
+ const visibleRect = resolveVisibleRect(
351
+ plotRef.current,
352
+ renderer.terminalWidth,
353
+ renderer.terminalHeight,
354
+ );
355
+ if (!visibleRect) {
356
+ nativeSurfaceManager.removeSurface(nativeSurfaceIdRef.current);
357
+ return;
358
+ }
359
+
360
+ const bitmapSize = computeBitmapSize(
361
+ plotRect,
362
+ renderer.resolution,
363
+ renderer.terminalWidth,
364
+ renderer.terminalHeight,
365
+ );
366
+ const bitmapKey = buildNativeBitmapKey(
367
+ pricePoints,
368
+ bitmapSize.pixelWidth,
369
+ bitmapSize.pixelHeight,
370
+ [palette.lineColor, palette.fillColor, palette.gridColor].join(","),
371
+ );
372
+ const cachedBitmap =
373
+ lastNativeBitmapRef.current?.key === bitmapKey
374
+ ? lastNativeBitmapRef.current.bitmap
375
+ : null;
376
+ const bitmap =
377
+ cachedBitmap ??
378
+ renderNativeChartBase(
379
+ nativeScene,
380
+ bitmapSize.pixelWidth,
381
+ bitmapSize.pixelHeight,
382
+ );
383
+ if (!cachedBitmap) {
384
+ lastNativeBitmapRef.current = { key: bitmapKey, bitmap };
385
+ }
386
+
387
+ nativeSurfaceManager.upsertSurface({
388
+ id: nativeSurfaceIdRef.current,
389
+ paneId,
390
+ rect: plotRect,
391
+ visibleRect,
392
+ bitmap,
393
+ bitmapKey,
394
+ });
395
+ renderer.requestRender();
396
+ }, [
397
+ effectiveRenderer,
398
+ nativeScene,
399
+ nativeSurfaceManager,
400
+ paneId,
401
+ palette.fillColor,
402
+ palette.gridColor,
403
+ palette.lineColor,
404
+ pricePoints,
405
+ renderer,
406
+ rendererState.nativeReady,
407
+ ]);
408
+
409
+ const plotLines: Array<string | StyledContent> =
410
+ effectiveRenderer === "kitty" ? blankPlotLines : rendered.lines;
411
+ const plotContent = plotLines.map((line, index) => (
412
+ <text key={index} content={line as any} />
413
+ ));
414
+
415
+ if (!hasHistory) {
416
+ return (
417
+ <box flexDirection="column" height={height}>
418
+ <box flexDirection="row" gap={1} height={1}>
419
+ {RANGES.map((entry) => {
420
+ const active = entry === range;
421
+ return (
422
+ <box
423
+ key={entry}
424
+ onMouseDown={(event) => {
425
+ event.preventDefault();
426
+ onRangeSelect(entry);
427
+ }}
428
+ >
429
+ <text
430
+ fg={active ? colors.textBright : colors.textDim}
431
+ attributes={active ? TextAttributes.BOLD : 0}
432
+ >
433
+ {entry}
434
+ </text>
435
+ </box>
436
+ );
437
+ })}
438
+ </box>
439
+ <box flexGrow={1} justifyContent="center">
440
+ <EmptyState
441
+ title="No chart history."
442
+ hint="This venue did not return price history for the selected market."
443
+ />
444
+ </box>
445
+ </box>
446
+ );
447
+ }
448
+
449
+ return (
450
+ <box flexDirection="column" height={height}>
451
+ <box flexDirection="row" height={1}>
452
+ <box flexDirection="row" gap={1}>
453
+ {RANGES.map((entry) => {
454
+ const active = entry === range;
455
+ return (
456
+ <box
457
+ key={entry}
458
+ onMouseDown={(event) => {
459
+ event.preventDefault();
460
+ onRangeSelect(entry);
461
+ }}
462
+ >
463
+ <text
464
+ fg={active ? colors.textBright : colors.textDim}
465
+ attributes={active ? TextAttributes.BOLD : 0}
466
+ >
467
+ {entry}
468
+ </text>
469
+ </box>
470
+ );
471
+ })}
472
+ </box>
473
+ <box flexGrow={1} />
474
+ <text
475
+ fg={
476
+ delta > 0
477
+ ? colors.positive
478
+ : delta < 0
479
+ ? colors.negative
480
+ : colors.text
481
+ }
482
+ >
483
+ {`${formatNumber(last?.close ?? 0, 3)} ${formatPercentRaw(deltaPct)}`}
484
+ </text>
485
+ </box>
486
+
487
+ <box flexDirection="row" height={chartHeight}>
488
+ <box
489
+ ref={plotRef}
490
+ width={chartWidth}
491
+ height={chartHeight}
492
+ flexDirection="column"
493
+ backgroundColor={palette.bgColor}
494
+ >
495
+ {plotContent}
496
+ </box>
497
+ <box width={1} />
498
+ <box width={AXIS_WIDTH} flexDirection="column" height={chartHeight}>
499
+ {Array.from({ length: chartHeight }, (_, index) => (
500
+ <text key={index} fg={colors.textDim}>
501
+ {axisByRow.get(index) ?? ""}
502
+ </text>
503
+ ))}
504
+ </box>
505
+ </box>
506
+
507
+ <box height={1}>
508
+ <text fg={colors.textDim}>{rendered.timeLabels}</text>
509
+ </box>
510
+ </box>
511
+ );
512
+ }
@@ -0,0 +1,133 @@
1
+ import type { PredictionColumnDef } from "./types";
2
+
3
+ export const PREDICTION_COLUMN_DEFS: PredictionColumnDef[] = [
4
+ {
5
+ id: "watch",
6
+ label: "★",
7
+ width: 2,
8
+ align: "left",
9
+ description: "Star a market for the prediction watchlist.",
10
+ },
11
+ {
12
+ id: "market",
13
+ label: "MARKET",
14
+ width: 34,
15
+ align: "left",
16
+ description: "Event or primary question.",
17
+ },
18
+ {
19
+ id: "target",
20
+ label: "TARGET",
21
+ width: 18,
22
+ align: "left",
23
+ description: "Selected contract or top target for grouped events.",
24
+ },
25
+ {
26
+ id: "venue",
27
+ label: "VENUE",
28
+ width: 9,
29
+ align: "left",
30
+ description: "Prediction venue.",
31
+ },
32
+ {
33
+ id: "yes",
34
+ label: "TOP ODDS",
35
+ width: 20,
36
+ align: "left",
37
+ description:
38
+ "Implied probability, with the leading target shown inline for grouped events.",
39
+ },
40
+ {
41
+ id: "spread",
42
+ label: "SPR",
43
+ width: 7,
44
+ align: "right",
45
+ description: "Best spread.",
46
+ },
47
+ {
48
+ id: "vol_24h",
49
+ label: "VOL24H",
50
+ width: 11,
51
+ align: "right",
52
+ description: "24-hour venue-native volume.",
53
+ },
54
+ {
55
+ id: "open_interest",
56
+ label: "OI",
57
+ width: 10,
58
+ align: "right",
59
+ description: "Open interest.",
60
+ },
61
+ {
62
+ id: "ends",
63
+ label: "ENDS",
64
+ width: 15,
65
+ align: "left",
66
+ description: "Market close time.",
67
+ },
68
+ {
69
+ id: "status",
70
+ label: "STATUS",
71
+ width: 8,
72
+ align: "left",
73
+ description: "Market status.",
74
+ },
75
+ {
76
+ id: "event",
77
+ label: "EVENT",
78
+ width: 28,
79
+ align: "left",
80
+ description: "Parent event or series.",
81
+ },
82
+ {
83
+ id: "category",
84
+ label: "CAT",
85
+ width: 12,
86
+ align: "left",
87
+ description: "Venue category.",
88
+ },
89
+ {
90
+ id: "vol_total",
91
+ label: "TOTALVOL",
92
+ width: 11,
93
+ align: "right",
94
+ description: "Total venue-native volume.",
95
+ },
96
+ {
97
+ id: "liquidity",
98
+ label: "LIQ",
99
+ width: 11,
100
+ align: "right",
101
+ description: "Available liquidity.",
102
+ },
103
+ {
104
+ id: "updated",
105
+ label: "UPDATED",
106
+ width: 10,
107
+ align: "left",
108
+ description: "Last upstream update age.",
109
+ },
110
+ {
111
+ id: "market_id",
112
+ label: "ID",
113
+ width: 20,
114
+ align: "left",
115
+ description: "Venue market identifier.",
116
+ },
117
+ ];
118
+
119
+ export const DEFAULT_PREDICTION_COLUMN_IDS = [
120
+ "watch",
121
+ "market",
122
+ "venue",
123
+ "yes",
124
+ "spread",
125
+ "vol_24h",
126
+ "open_interest",
127
+ "ends",
128
+ "status",
129
+ ];
130
+
131
+ export const PREDICTION_COLUMNS_BY_ID = new Map(
132
+ PREDICTION_COLUMN_DEFS.map((column) => [column.id, column]),
133
+ );