@columenlabs/lumen-ui 0.0.0-stage → 0.2.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 (297) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +12 -0
  3. package/README.md +4 -2
  4. package/dist/add-column.d.ts +23 -0
  5. package/dist/add-column.js +62 -0
  6. package/dist/api-fetch.d.ts +9 -0
  7. package/dist/api-fetch.js +60 -0
  8. package/dist/app-switcher.d.ts +21 -0
  9. package/dist/app-switcher.js +12 -0
  10. package/dist/app-views.d.ts +18 -0
  11. package/dist/app-views.js +41 -0
  12. package/dist/assistant-context.d.ts +72 -0
  13. package/dist/assistant-context.js +30 -0
  14. package/dist/assistant-panel.d.ts +58 -0
  15. package/dist/assistant-panel.js +244 -0
  16. package/dist/assistant-screen.d.ts +124 -0
  17. package/dist/assistant-screen.js +252 -0
  18. package/dist/card-switch.d.ts +16 -0
  19. package/dist/card-switch.js +16 -0
  20. package/dist/color-dot.d.ts +14 -0
  21. package/dist/color-dot.js +19 -0
  22. package/dist/confirm.d.ts +18 -0
  23. package/dist/confirm.js +5 -0
  24. package/dist/connections.d.ts +45 -0
  25. package/dist/connections.js +275 -0
  26. package/dist/coverage-calendar-map.d.ts +27 -0
  27. package/dist/coverage-calendar-map.js +84 -0
  28. package/dist/coverage-cells.d.ts +55 -0
  29. package/dist/coverage-cells.js +85 -0
  30. package/dist/coverage-map.d.ts +112 -0
  31. package/dist/coverage-map.js +428 -0
  32. package/dist/details-table.d.ts +20 -0
  33. package/dist/details-table.js +13 -0
  34. package/dist/drawer-section.d.ts +27 -0
  35. package/dist/drawer-section.js +23 -0
  36. package/dist/floating-head.d.ts +2 -0
  37. package/dist/floating-head.js +57 -0
  38. package/dist/fold.d.ts +16 -0
  39. package/dist/fold.js +70 -0
  40. package/dist/form-issues.d.ts +32 -0
  41. package/dist/form-issues.js +60 -0
  42. package/dist/formula-input.d.ts +17 -0
  43. package/dist/formula-input.js +104 -0
  44. package/dist/frame-script.d.ts +7 -0
  45. package/dist/frame-script.js +103 -0
  46. package/dist/freshness.d.ts +24 -0
  47. package/dist/freshness.js +99 -0
  48. package/dist/group-button.d.ts +33 -0
  49. package/dist/group-button.js +67 -0
  50. package/dist/growth-chart.d.ts +77 -0
  51. package/dist/growth-chart.js +195 -0
  52. package/dist/hooks/use-keyboard-fit.d.ts +15 -0
  53. package/dist/hooks/use-keyboard-fit.js +29 -0
  54. package/dist/hooks/use-mobile.d.ts +1 -0
  55. package/dist/hooks/use-mobile.js +15 -0
  56. package/dist/icon-button.d.ts +21 -0
  57. package/dist/icon-button.js +22 -0
  58. package/dist/icon-picker.d.ts +14 -0
  59. package/dist/icon-picker.js +36 -0
  60. package/dist/index.d.ts +83 -0
  61. package/dist/index.js +90 -0
  62. package/dist/integrations.d.ts +57 -0
  63. package/dist/integrations.js +73 -0
  64. package/dist/joins.d.ts +128 -0
  65. package/dist/joins.js +767 -0
  66. package/dist/kit-config.d.ts +13 -0
  67. package/dist/kit-config.js +15 -0
  68. package/dist/lib/utils.d.ts +2 -0
  69. package/dist/lib/utils.js +5 -0
  70. package/dist/lumen-boundary.d.ts +7 -0
  71. package/dist/lumen-boundary.js +10 -0
  72. package/dist/lumen-code-editor-impl.d.ts +2 -0
  73. package/dist/lumen-code-editor-impl.js +16 -0
  74. package/dist/lumen-code-editor.d.ts +11 -0
  75. package/dist/lumen-code-editor.js +11 -0
  76. package/dist/lumen-code.d.ts +22 -0
  77. package/dist/lumen-code.js +75 -0
  78. package/dist/lumen-drawer.d.ts +30 -0
  79. package/dist/lumen-drawer.js +27 -0
  80. package/dist/lumen-echart.d.ts +58 -0
  81. package/dist/lumen-echart.js +129 -0
  82. package/dist/lumen-filter.d.ts +75 -0
  83. package/dist/lumen-filter.js +546 -0
  84. package/dist/lumen-finder.d.ts +62 -0
  85. package/dist/lumen-finder.js +275 -0
  86. package/dist/lumen-form.d.ts +34 -0
  87. package/dist/lumen-form.js +81 -0
  88. package/dist/lumen-geo-map.d.ts +9 -0
  89. package/dist/lumen-geo-map.js +135 -0
  90. package/dist/lumen-grid.d.ts +49 -0
  91. package/dist/lumen-grid.js +203 -0
  92. package/dist/lumen-layout.d.ts +150 -0
  93. package/dist/lumen-layout.js +658 -0
  94. package/dist/lumen-links.d.ts +96 -0
  95. package/dist/lumen-links.js +213 -0
  96. package/dist/lumen-page-state.d.ts +20 -0
  97. package/dist/lumen-page-state.js +15 -0
  98. package/dist/lumen-selector.d.ts +139 -0
  99. package/dist/lumen-selector.js +308 -0
  100. package/dist/lumen-source.d.ts +137 -0
  101. package/dist/lumen-source.js +327 -0
  102. package/dist/lumen-tab-drawer.d.ts +27 -0
  103. package/dist/lumen-tab-drawer.js +26 -0
  104. package/dist/lumen-tabs.d.ts +21 -0
  105. package/dist/lumen-tabs.js +6 -0
  106. package/dist/lumen-toast.d.ts +11 -0
  107. package/dist/lumen-toast.js +16 -0
  108. package/dist/lumen-trend.d.ts +18 -0
  109. package/dist/lumen-trend.js +157 -0
  110. package/dist/lumen-view-card.d.ts +35 -0
  111. package/dist/lumen-view-card.js +50 -0
  112. package/dist/lumen-view.d.ts +172 -0
  113. package/dist/lumen-view.js +645 -0
  114. package/dist/lumen-widget-charts.d.ts +57 -0
  115. package/dist/lumen-widget-charts.js +69 -0
  116. package/dist/lumen-widget.d.ts +614 -0
  117. package/dist/lumen-widget.js +594 -0
  118. package/dist/market/compare-chart.d.ts +34 -0
  119. package/dist/market/compare-chart.js +292 -0
  120. package/dist/market/indicator-picker.d.ts +29 -0
  121. package/dist/market/indicator-picker.js +125 -0
  122. package/dist/market/indicators.d.ts +63 -0
  123. package/dist/market/indicators.js +314 -0
  124. package/dist/market/live.d.ts +120 -0
  125. package/dist/market/live.js +444 -0
  126. package/dist/market/lumen-chart.d.ts +88 -0
  127. package/dist/market/lumen-chart.js +645 -0
  128. package/dist/market/market-widget.d.ts +45 -0
  129. package/dist/market/market-widget.js +346 -0
  130. package/dist/market/option-chain.d.ts +19 -0
  131. package/dist/market/option-chain.js +53 -0
  132. package/dist/market/price-chart.d.ts +128 -0
  133. package/dist/market/price-chart.js +619 -0
  134. package/dist/mcp-settings.d.ts +20 -0
  135. package/dist/mcp-settings.js +65 -0
  136. package/dist/nav.d.ts +77 -0
  137. package/dist/nav.js +73 -0
  138. package/dist/note-line.d.ts +13 -0
  139. package/dist/note-line.js +5 -0
  140. package/dist/notifications.d.ts +9 -0
  141. package/dist/notifications.js +166 -0
  142. package/dist/origin-legend.d.ts +14 -0
  143. package/dist/origin-legend.js +38 -0
  144. package/dist/overview.d.ts +84 -0
  145. package/dist/overview.js +105 -0
  146. package/dist/page-inputs.d.ts +14 -0
  147. package/dist/page-inputs.js +16 -0
  148. package/dist/page.d.ts +32 -0
  149. package/dist/page.js +45 -0
  150. package/dist/pager.d.ts +10 -0
  151. package/dist/pager.js +14 -0
  152. package/dist/prefs.d.ts +71 -0
  153. package/dist/prefs.js +126 -0
  154. package/dist/query-client.d.ts +7 -0
  155. package/dist/query-client.js +19 -0
  156. package/dist/refresh.d.ts +4 -0
  157. package/dist/refresh.js +19 -0
  158. package/dist/rename-field.d.ts +11 -0
  159. package/dist/rename-field.js +26 -0
  160. package/dist/reset-button.d.ts +9 -0
  161. package/dist/reset-button.js +10 -0
  162. package/dist/route-error.d.ts +42 -0
  163. package/dist/route-error.js +229 -0
  164. package/dist/schedules.d.ts +88 -0
  165. package/dist/schedules.js +221 -0
  166. package/dist/settings.d.ts +51 -0
  167. package/dist/settings.js +181 -0
  168. package/dist/shell.d.ts +99 -0
  169. package/dist/shell.js +137 -0
  170. package/dist/source-health.d.ts +12 -0
  171. package/dist/source-health.js +58 -0
  172. package/dist/spotlight.d.ts +23 -0
  173. package/dist/spotlight.js +138 -0
  174. package/dist/status-health.d.ts +16 -0
  175. package/dist/status-health.js +41 -0
  176. package/dist/status.d.ts +9 -0
  177. package/dist/status.js +12 -0
  178. package/dist/summary-cards.d.ts +56 -0
  179. package/dist/summary-cards.js +270 -0
  180. package/dist/symbol-sheet-context.d.ts +4 -0
  181. package/dist/symbol-sheet-context.js +5 -0
  182. package/dist/symbol-sheet.d.ts +9 -0
  183. package/dist/symbol-sheet.js +24 -0
  184. package/dist/tasks.d.ts +29 -0
  185. package/dist/tasks.js +278 -0
  186. package/dist/theme.css +606 -0
  187. package/dist/tile.d.ts +37 -0
  188. package/dist/tile.js +23 -0
  189. package/dist/ui/alert-dialog.d.ts +18 -0
  190. package/dist/ui/alert-dialog.js +41 -0
  191. package/dist/ui/alert.d.ts +10 -0
  192. package/dist/ui/alert.js +27 -0
  193. package/dist/ui/avatar.d.ts +11 -0
  194. package/dist/ui/avatar.js +22 -0
  195. package/dist/ui/badge.d.ts +9 -0
  196. package/dist/ui/badge.js +27 -0
  197. package/dist/ui/breadcrumb.d.ts +11 -0
  198. package/dist/ui/breadcrumb.js +27 -0
  199. package/dist/ui/button-group.d.ts +11 -0
  200. package/dist/ui/button-group.js +27 -0
  201. package/dist/ui/button.d.ts +10 -0
  202. package/dist/ui/button.js +38 -0
  203. package/dist/ui/card.d.ts +11 -0
  204. package/dist/ui/card.js +24 -0
  205. package/dist/ui/chart.d.ts +44 -0
  206. package/dist/ui/chart.js +117 -0
  207. package/dist/ui/checkbox.d.ts +4 -0
  208. package/dist/ui/checkbox.js +8 -0
  209. package/dist/ui/collapsible.d.ts +5 -0
  210. package/dist/ui/collapsible.js +12 -0
  211. package/dist/ui/combobox.d.ts +24 -0
  212. package/dist/ui/combobox.js +60 -0
  213. package/dist/ui/command.d.ts +18 -0
  214. package/dist/ui/command.js +34 -0
  215. package/dist/ui/dialog.d.ts +23 -0
  216. package/dist/ui/dialog.js +50 -0
  217. package/dist/ui/dropdown-menu.d.ts +29 -0
  218. package/dist/ui/dropdown-menu.js +50 -0
  219. package/dist/ui/emoji-picker.d.ts +6 -0
  220. package/dist/ui/emoji-picker.js +28 -0
  221. package/dist/ui/empty.d.ts +11 -0
  222. package/dist/ui/empty.js +33 -0
  223. package/dist/ui/field.d.ts +24 -0
  224. package/dist/ui/field.js +65 -0
  225. package/dist/ui/hover-card.d.ts +6 -0
  226. package/dist/ui/hover-card.js +13 -0
  227. package/dist/ui/input-group.d.ts +16 -0
  228. package/dist/ui/input-group.js +56 -0
  229. package/dist/ui/input.d.ts +3 -0
  230. package/dist/ui/input.js +6 -0
  231. package/dist/ui/item.d.ts +23 -0
  232. package/dist/ui/item.js +67 -0
  233. package/dist/ui/kbd.d.ts +3 -0
  234. package/dist/ui/kbd.js +9 -0
  235. package/dist/ui/label.d.ts +4 -0
  236. package/dist/ui/label.js +7 -0
  237. package/dist/ui/popover.d.ts +10 -0
  238. package/dist/ui/popover.js +25 -0
  239. package/dist/ui/progress.d.ts +4 -0
  240. package/dist/ui/progress.js +9 -0
  241. package/dist/ui/resizable.d.ts +7 -0
  242. package/dist/ui/resizable.js +13 -0
  243. package/dist/ui/scroll-area.d.ts +5 -0
  244. package/dist/ui/scroll-area.js +10 -0
  245. package/dist/ui/select.d.ts +15 -0
  246. package/dist/ui/select.js +36 -0
  247. package/dist/ui/separator.d.ts +4 -0
  248. package/dist/ui/separator.js +7 -0
  249. package/dist/ui/sheet.d.ts +14 -0
  250. package/dist/ui/sheet.js +54 -0
  251. package/dist/ui/sidebar.d.ts +69 -0
  252. package/dist/ui/sidebar.js +206 -0
  253. package/dist/ui/skeleton.d.ts +2 -0
  254. package/dist/ui/skeleton.js +6 -0
  255. package/dist/ui/slider.d.ts +4 -0
  256. package/dist/ui/slider.js +9 -0
  257. package/dist/ui/sonner.d.ts +3 -0
  258. package/dist/ui/sonner.js +24 -0
  259. package/dist/ui/spinner.d.ts +2 -0
  260. package/dist/ui/spinner.js +7 -0
  261. package/dist/ui/switch.d.ts +6 -0
  262. package/dist/ui/switch.js +7 -0
  263. package/dist/ui/table.d.ts +10 -0
  264. package/dist/ui/table.js +31 -0
  265. package/dist/ui/tabs.d.ts +7 -0
  266. package/dist/ui/tabs.js +22 -0
  267. package/dist/ui/textarea.d.ts +3 -0
  268. package/dist/ui/textarea.js +6 -0
  269. package/dist/ui/toggle-group.d.ts +10 -0
  270. package/dist/ui/toggle-group.js +22 -0
  271. package/dist/ui/toggle.d.ts +9 -0
  272. package/dist/ui/toggle.js +25 -0
  273. package/dist/ui/tooltip.d.ts +7 -0
  274. package/dist/ui/tooltip.js +16 -0
  275. package/dist/view-edit-cell.d.ts +49 -0
  276. package/dist/view-edit-cell.js +198 -0
  277. package/dist/view-editor.d.ts +52 -0
  278. package/dist/view-editor.js +940 -0
  279. package/dist/view-formulas.d.ts +61 -0
  280. package/dist/view-formulas.js +247 -0
  281. package/dist/view-icons.d.ts +20 -0
  282. package/dist/view-icons.js +70 -0
  283. package/dist/view-model.d.ts +390 -0
  284. package/dist/view-model.js +316 -0
  285. package/dist/whats-new.d.ts +3 -0
  286. package/dist/whats-new.js +53 -0
  287. package/dist/widget-controls.d.ts +9 -0
  288. package/dist/widget-controls.js +26 -0
  289. package/dist/widget-settings.d.ts +436 -0
  290. package/dist/widget-settings.js +290 -0
  291. package/dist/widget-thumbs.d.ts +3 -0
  292. package/dist/widget-thumbs.js +29 -0
  293. package/dist/workspace-keys.d.ts +12 -0
  294. package/dist/workspace-keys.js +13 -0
  295. package/dist/workspaces.d.ts +57 -0
  296. package/dist/workspaces.js +473 -0
  297. package/package.json +82 -4
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Indicators the chart can draw (moved from an app's Chart tab, 2026-10-12). `lines` are output
3
+ * series keyed like an app's stored `indicators_1d` names, so an app with stored values (an app's
4
+ * daily chart) passes those. The chart dialog makes each one only when it is added: `computed` ones in the browser with indicatorts over warm-up bars before the window,
5
+ * `sourced` ones (P/E, IV, EPS estimate) read once from market data, since bars cannot give them.
6
+ */
7
+ import { builtinParams, parseChartFormula, } from '@columenlabs/lumen-ui-types/chart-formula';
8
+ import { atr, bb, ema, macd, mmax, mmin, mstd, msum, rsi, sma } from 'indicatorts';
9
+ const one = (id, label, where, extra = { computed: true }) => ({ id, label, where, lines: [{ key: id, label }], ...extra });
10
+ /** Every catalog id is in `CHART_INDICATOR_IDS`, which the server checks the assistant's specs against. */
11
+ export const CHART_INDICATORS = [
12
+ one('sma_20', 'SMA 20', 'overlay'),
13
+ one('sma_50', 'SMA 50', 'overlay'),
14
+ one('sma_200', 'SMA 200', 'overlay'),
15
+ one('ema_9', 'EMA 9', 'overlay'),
16
+ one('ema_21', 'EMA 21', 'overlay'),
17
+ one('ema_50', 'EMA 50', 'overlay'),
18
+ {
19
+ id: 'bollinger',
20
+ label: 'Bollinger Bands 20, 2',
21
+ where: 'overlay',
22
+ computed: true,
23
+ lines: [
24
+ { key: 'bb_upper', label: 'Upper' },
25
+ { key: 'bb_mid', label: 'Middle' },
26
+ { key: 'bb_lower', label: 'Lower' },
27
+ ],
28
+ },
29
+ {
30
+ id: 'high_low_52w',
31
+ label: '52-week high / low',
32
+ where: 'overlay',
33
+ computed: true,
34
+ daily: true,
35
+ lines: [
36
+ { key: 'high_52w', label: '52w high' },
37
+ { key: 'low_52w', label: '52w low' },
38
+ ],
39
+ },
40
+ one('rsi_14', 'RSI 14', 'pane', { refs: [30, 70], domain: [0, 100], computed: true }),
41
+ {
42
+ id: 'macd',
43
+ label: 'MACD 12, 26, 9',
44
+ where: 'pane',
45
+ computed: true,
46
+ lines: [
47
+ { key: 'macd_hist', label: 'Histogram', bar: true },
48
+ { key: 'macd_line', label: 'MACD' },
49
+ { key: 'macd_signal', label: 'Signal' },
50
+ ],
51
+ refs: [0],
52
+ },
53
+ one('atr_14', 'ATR 14', 'pane', { computed: true }),
54
+ one('rel_volume_20', 'Relative volume 20', 'pane', { refs: [1] }),
55
+ {
56
+ id: 'vwap_distance',
57
+ label: 'Distance from VWAP %',
58
+ where: 'pane',
59
+ lines: [{ key: 'vwap_dist_pct', label: 'VWAP distance %' }],
60
+ refs: [0],
61
+ },
62
+ one('pe_trailing', 'P/E ttm', 'pane', { sourced: true }),
63
+ one('pe_forward', 'P/E forward', 'pane', { sourced: true }),
64
+ one('iv30', 'IV 30d', 'pane', { sourced: true }),
65
+ one('est_eps', 'EPS estimate', 'pane', { sourced: true }),
66
+ ];
67
+ /** Theme tokens handed out to lines by catalog position. */
68
+ const LINE_COLORS = [
69
+ 'var(--warning)',
70
+ 'var(--chart-2)',
71
+ 'var(--gain)',
72
+ 'var(--chart-6)',
73
+ 'var(--loss)',
74
+ 'var(--chart-4)',
75
+ ];
76
+ /** Colour of each line, fixed by its place in the catalog so a new pick never recolours the others. */
77
+ export const LINE_COLOR = Object.fromEntries(CHART_INDICATORS.flatMap((i) => i.lines).map((l, n) => [
78
+ l.key,
79
+ LINE_COLORS[n % LINE_COLORS.length] ?? 'var(--primary)',
80
+ ]));
81
+ /** A line's colour: its catalog place, else one picked from its key so it stays put. */
82
+ export function lineColor(key) {
83
+ const fixed = LINE_COLOR[key];
84
+ if (fixed)
85
+ return fixed;
86
+ let h = 0;
87
+ for (const c of key)
88
+ h = (h * 31 + c.charCodeAt(0)) >>> 0;
89
+ return LINE_COLORS[h % LINE_COLORS.length] ?? 'var(--primary)';
90
+ }
91
+ const NAMES = { sma: 'SMA', ema: 'EMA', rsi: 'RSI', atr: 'ATR' };
92
+ /** A built-in with its own window (`ema_13`, `bollinger_20_2p5`, `macd_8_21_5`), as the catalog's. */
93
+ function builtin(id) {
94
+ const b = builtinParams(id);
95
+ if (!b)
96
+ return null;
97
+ const [n = 0, k = 0, g = 0] = b.params;
98
+ if (b.kind === 'sma' || b.kind === 'ema')
99
+ return one(id, `${NAMES[b.kind]} ${n}`, 'overlay');
100
+ if (b.kind === 'rsi')
101
+ return one(id, `RSI ${n}`, 'pane', { refs: [30, 70], domain: [0, 100], computed: true });
102
+ if (b.kind === 'atr')
103
+ return one(id, `ATR ${n}`, 'pane', { computed: true });
104
+ if (b.kind === 'bollinger')
105
+ return {
106
+ id,
107
+ label: `Bollinger Bands ${n}, ${k}`,
108
+ where: 'overlay',
109
+ computed: true,
110
+ lines: [
111
+ { key: `${id}_upper`, label: 'Upper' },
112
+ { key: `${id}_mid`, label: 'Middle' },
113
+ { key: `${id}_lower`, label: 'Lower' },
114
+ ],
115
+ };
116
+ return {
117
+ id,
118
+ label: `MACD ${n}, ${k}, ${g}`,
119
+ where: 'pane',
120
+ computed: true,
121
+ lines: [
122
+ { key: `${id}_hist`, label: 'Histogram', bar: true },
123
+ { key: `${id}_line`, label: 'MACD' },
124
+ { key: `${id}_signal`, label: 'Signal' },
125
+ ],
126
+ refs: [0],
127
+ };
128
+ }
129
+ /** A formula's id on the chart: the same label and formula are the same line. */
130
+ export function formulaId(f) {
131
+ let h = 0;
132
+ for (const c of `${f.label}|${f.f}`)
133
+ h = (h * 31 + c.charCodeAt(0)) >>> 0;
134
+ return `f:${h.toString(36)}`;
135
+ }
136
+ /** A formula as a chart line; throws with why it can't run. */
137
+ export function formulaIndicator(f, id = formulaId(f)) {
138
+ const { node } = parseChartFormula(f.f);
139
+ return {
140
+ id,
141
+ label: f.label,
142
+ where: f.where ?? 'pane',
143
+ lines: [{ key: id, label: f.label }],
144
+ refs: f.refs,
145
+ computed: true,
146
+ formula: node,
147
+ f: f.f,
148
+ };
149
+ }
150
+ /**
151
+ * What a spec draws: a catalog indicator, a built-in with any window, a saved formula (`mine:<id>`,
152
+ * looked up in `mine`) or a formula; null when it is none of them.
153
+ */
154
+ export function resolveIndicator(spec, mine = []) {
155
+ try {
156
+ if (typeof spec !== 'string')
157
+ return formulaIndicator(spec);
158
+ return (CHART_INDICATORS.find((i) => i.id === spec) ??
159
+ mine.find((i) => i.id === spec) ??
160
+ builtin(spec));
161
+ }
162
+ catch {
163
+ return null;
164
+ }
165
+ }
166
+ /** `fn` over `x` from its first number on, NaN before; the first `skip` values are left out too. */
167
+ function from(x, fn, skip) {
168
+ const at = x.findIndex(Number.isFinite);
169
+ if (at < 0)
170
+ return x.map(() => Number.NaN);
171
+ const out = fn(x.slice(at));
172
+ return x.map((_, i) => (i - at < skip || i < at ? Number.NaN : (out[i - at] ?? Number.NaN)));
173
+ }
174
+ const BINARY = {
175
+ '+': (a, b) => a + b,
176
+ '-': (a, b) => a - b,
177
+ '*': (a, b) => a * b,
178
+ '/': (a, b) => a / b,
179
+ '%': (a, b) => a % b,
180
+ '^': (a, b) => a ** b,
181
+ '==': (a, b) => +(a === b),
182
+ '!=': (a, b) => +(a !== b),
183
+ '>': (a, b) => +(a > b),
184
+ '>=': (a, b) => +(a >= b),
185
+ '<': (a, b) => +(a < b),
186
+ '<=': (a, b) => +(a <= b),
187
+ '&&': (a, b) => +(!!a && !!b),
188
+ '||': (a, b) => +(!!a || !!b),
189
+ };
190
+ /** A formula's values per bar (NaN where it has none yet), with indicatorts doing the series math. */
191
+ export function runFormula(node, bars) {
192
+ const go = (n) => {
193
+ switch (n.k) {
194
+ case 'num':
195
+ return bars.map(() => n.v);
196
+ case 'field':
197
+ return bars.map((b) => b[n.name]);
198
+ case 'op': {
199
+ const a = go(n.a);
200
+ const b = go(n.b);
201
+ const f = BINARY[n.op];
202
+ return a.map((v, i) => {
203
+ const w = b[i] ?? Number.NaN;
204
+ return Number.isFinite(v) && Number.isFinite(w) ? f(v, w) : Number.NaN;
205
+ });
206
+ }
207
+ case 'not':
208
+ return go(n.a).map((v) => (Number.isFinite(v) ? +!v : Number.NaN));
209
+ case 'if': {
210
+ const t = go(n.test);
211
+ const y = go(n.yes);
212
+ const o = go(n.no);
213
+ return t.map((v, i) => Number.isFinite(v) ? ((v ? y[i] : o[i]) ?? Number.NaN) : Number.NaN);
214
+ }
215
+ case 'call': {
216
+ const [x = [], y = []] = n.args.map(go);
217
+ const period = n.n;
218
+ switch (n.fn) {
219
+ case 'sma':
220
+ return from(x, (v) => sma(v, { period }), period - 1);
221
+ case 'ema':
222
+ return from(x, (v) => ema(v, { period }), period - 1);
223
+ case 'rsi':
224
+ return from(x, (v) => rsi(v, { period }), period);
225
+ case 'stdev':
226
+ return from(x, (v) => mstd(v, { period }), period - 1);
227
+ case 'highest':
228
+ return from(x, (v) => mmax(v, { period }), period - 1);
229
+ case 'lowest':
230
+ return from(x, (v) => mmin(v, { period }), period - 1);
231
+ case 'sum':
232
+ return from(x, (v) => msum(v, { period }), period - 1);
233
+ case 'ref':
234
+ return x.map((_, i) => x[i - period] ?? Number.NaN);
235
+ case 'change':
236
+ return x.map((v, i) => {
237
+ const p = x[i - period] ?? Number.NaN;
238
+ return Number.isFinite(v) && Number.isFinite(p) && p ? (v / p - 1) * 100 : Number.NaN;
239
+ });
240
+ case 'atr':
241
+ return atr(bars.map((b) => b.high), bars.map((b) => b.low), bars.map((b) => b.close), { period }).atrLine.map((v, i) => (i < period ? Number.NaN : v));
242
+ case 'abs':
243
+ return x.map(Math.abs);
244
+ case 'min':
245
+ return x.map((v, i) => Math.min(v, y[i] ?? Number.NaN));
246
+ case 'max':
247
+ return x.map((v, i) => Math.max(v, y[i] ?? Number.NaN));
248
+ }
249
+ }
250
+ }
251
+ };
252
+ return go(node);
253
+ }
254
+ /** A computed indicator's kind and numbers: a built-in by its id, or the catalog's fixed ones. */
255
+ const calcOf = (i) => i.id === 'bollinger'
256
+ ? { kind: 'bollinger', params: [20, 2] }
257
+ : i.id === 'macd'
258
+ ? { kind: 'macd', params: [12, 26, 9] }
259
+ : builtinParams(i.id);
260
+ /** The chosen indicators' lines worked out from `bars`, skipping each one's warm-up bars. */
261
+ export function computeIndicators(bars, chosen) {
262
+ const closes = bars.map((b) => b.close);
263
+ const out = {};
264
+ const put = (key, vs, skip) => {
265
+ if (!key)
266
+ return;
267
+ const m = new Map();
268
+ vs.forEach((v, i) => {
269
+ const b = bars[i];
270
+ if (b && i >= skip && Number.isFinite(v))
271
+ m.set(b.time, v);
272
+ });
273
+ out[key] = m;
274
+ };
275
+ const [k0, k1, k2] = [0, 1, 2];
276
+ for (const i of chosen) {
277
+ const key = (n) => i.lines[n]?.key;
278
+ if (i.formula) {
279
+ put(key(k0), runFormula(i.formula, bars), 0);
280
+ continue;
281
+ }
282
+ if (i.id === 'high_low_52w') {
283
+ // 252 sessions, from the first bar once a year of them is in.
284
+ put('high_52w', mmax(bars.map((b) => b.high), { period: 252 }), 251);
285
+ put('low_52w', mmin(bars.map((b) => b.low), { period: 252 }), 251);
286
+ continue;
287
+ }
288
+ const c = calcOf(i);
289
+ if (!c)
290
+ continue;
291
+ const [n = 0, m = 0, g = 0] = c.params;
292
+ if (c.kind === 'sma' || c.kind === 'ema')
293
+ put(key(k0), (c.kind === 'sma' ? sma : ema)(closes, { period: n }), n - 1);
294
+ else if (c.kind === 'rsi')
295
+ put(key(k0), rsi(closes, { period: n }), n);
296
+ else if (c.kind === 'atr')
297
+ put(key(k0), atr(bars.map((b) => b.high), bars.map((b) => b.low), closes, { period: n }).atrLine, n);
298
+ else if (c.kind === 'bollinger') {
299
+ const r = bb(closes, { period: n });
300
+ // indicatorts' bands are 2 standard deviations; other widths move them from the middle.
301
+ const wide = (side) => side.map((v, k) => (r.middle[k] ?? 0) + ((v - (r.middle[k] ?? 0)) * m) / 2);
302
+ put(key(k0), wide(r.upper), n - 1);
303
+ put(key(k1), r.middle, n - 1);
304
+ put(key(k2), wide(r.lower), n - 1);
305
+ }
306
+ else if (c.kind === 'macd') {
307
+ const r = macd(closes, { fast: n, slow: m, signal: g });
308
+ put(key(k0), r.macdLine.map((v, k) => v - (r.signalLine[k] ?? 0)), m + g - 2);
309
+ put(key(k1), r.macdLine, m - 1);
310
+ put(key(k2), r.signalLine, m + g - 2);
311
+ }
312
+ }
313
+ return out;
314
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Live market data in the browser (design market-live): one hub per tab, one
3
+ * socket per tab however many widgets show, subscriptions counted per symbol across widgets. The socket
4
+ * opens only while the session trades and the tab is visible: it closes 30 s after the tab is hidden
5
+ * and reconnects with backoff, and every (re)connect gets a fresh snapshot, so gaps fill themselves.
6
+ * Outside the session the widgets read the last session's bars over REST (cached by the Worker until
7
+ * the next open). Each device and tab is its own viewer of the person's feed on the server.
8
+ * While the session is shut they also read the trades since its close (pre-market, after hours,
9
+ * overnight) once a minute, which the widgets draw after the session.
10
+ */
11
+ import { type MarketSeries, type MarketSession } from '@columenlabs/lumen-ui-types/market';
12
+ import { type ReactNode } from 'react';
13
+ export type MarketStatus = 'loading' | 'live' | 'closed' | 'connecting' | 'off' | 'error';
14
+ declare class MarketHub {
15
+ private readonly api;
16
+ session: MarketSession | null;
17
+ status: MarketStatus;
18
+ error: string | null;
19
+ readonly series: Map<string, MarketSeries>;
20
+ private want;
21
+ private ws;
22
+ private backoff;
23
+ private retryTimer;
24
+ private hiddenTimer;
25
+ private closedTimer;
26
+ private listeners;
27
+ private version;
28
+ private fetching;
29
+ private onVisibility;
30
+ constructor(api: string);
31
+ subscribe: (cb: () => void) => () => boolean;
32
+ snapshot: () => number;
33
+ private changed;
34
+ private probe;
35
+ private days;
36
+ /** Daily breadth and macro series, read once per set of keys per tab (they change once a day). */
37
+ daily(keys: string[]): Promise<MarketSeries[]>;
38
+ /** Whether the app serves market data at all (a 404 means no feed here), read once. */
39
+ available(): Promise<boolean>;
40
+ /** A widget shows these symbols; returns its release. */
41
+ hold(keys: string[]): () => void;
42
+ private keys;
43
+ /** Reads the session (and its bars when it is not trading), then opens the socket if it trades. */
44
+ private sync;
45
+ /** Symbols asked for and read, so a widget can tell "still loading" from "the server had none". */
46
+ private asked;
47
+ private unasked;
48
+ /** True once `key` was read and the server sent no series for it. */
49
+ missing(key: string): boolean;
50
+ /** Reads again after a failure (a widget's Retry). */
51
+ retry(): void;
52
+ private read;
53
+ /** The trades since the session's close, folded into each series (none without a source). */
54
+ private readExtended;
55
+ /**
56
+ * While closed, check once a minute (a cheap read: no bars) whether the session has opened, and
57
+ * read the trades since the close again.
58
+ */
59
+ private watchClosed;
60
+ private open;
61
+ private sendSub;
62
+ private close;
63
+ private stop;
64
+ private visibility;
65
+ }
66
+ /** The trades since the close, scaled to the series (a stand-in ETF to its index), or null. */
67
+ export declare function extendedOf(s: MarketSeries): {
68
+ via: string | null;
69
+ close: number;
70
+ bars: {
71
+ t: number;
72
+ o: number;
73
+ h: number;
74
+ l: number;
75
+ c: number;
76
+ }[];
77
+ last: number;
78
+ at: number;
79
+ label: string;
80
+ /** Change from the session's close, in percent. */
81
+ change: number;
82
+ } | null;
83
+ /**
84
+ * Up or down today: the price now against the close before today's trading. A broker's trading
85
+ * day starts with the overnight session, so from then on it is the session's close; before (the
86
+ * session, after hours) it is the prior close.
87
+ */
88
+ export declare function todayOf(s: MarketSeries): {
89
+ value: number;
90
+ base: number;
91
+ change: number;
92
+ label: string | null;
93
+ at: number | undefined;
94
+ } | null;
95
+ /** Gives market widgets and the chart dialog live data from `api` (`/api/v1/market`). */
96
+ export declare function MarketProvider({ api, children }: {
97
+ api: string;
98
+ children: ReactNode;
99
+ }): import("react").FunctionComponentElement<import("react").ProviderProps<MarketHub | null>>;
100
+ /** Today's (or the last) session of `symbols`, live while it trades. */
101
+ export declare function useMarket(symbols: string[]): {
102
+ status: MarketStatus;
103
+ session: MarketSession | null;
104
+ series: Record<string, MarketSeries>;
105
+ missing: string[];
106
+ error: string | null;
107
+ retry: () => void;
108
+ };
109
+ /** Daily breadth and macro series of `keys` (never live); `error` when they could not be read. */
110
+ export declare function useMarketDaily(keys: string[]): {
111
+ series: Record<string, MarketSeries>;
112
+ loading: boolean;
113
+ error: string | null;
114
+ retry: () => void;
115
+ };
116
+ /** True where a market provider is mounted (an app, suite or not). */
117
+ export declare const useHasMarket: () => boolean;
118
+ /** Whether this app has a market feed, without subscribing to anything: null while checking. */
119
+ export declare function useMarketFeed(): boolean | null;
120
+ export {};