@matchstick-trading/primitives 0.0.10

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 (216) hide show
  1. package/AGENTS.md +44 -0
  2. package/CHANGELOG.md +19 -0
  3. package/LICENSE +201 -0
  4. package/README.md +636 -0
  5. package/dist/Chart.d.ts +18 -0
  6. package/dist/Chart.js +6 -0
  7. package/dist/ChartParts.d.ts +151 -0
  8. package/dist/ChartParts.js +389 -0
  9. package/dist/CsvDrop.d.ts +8 -0
  10. package/dist/CsvDrop.js +5 -0
  11. package/dist/CsvDropParts.d.ts +33 -0
  12. package/dist/CsvDropParts.js +77 -0
  13. package/dist/Cursor.d.ts +6 -0
  14. package/dist/Cursor.js +5 -0
  15. package/dist/CursorParts.d.ts +51 -0
  16. package/dist/CursorParts.js +40 -0
  17. package/dist/DepthProfile.d.ts +13 -0
  18. package/dist/DepthProfile.js +5 -0
  19. package/dist/DepthProfileParts.d.ts +45 -0
  20. package/dist/DepthProfileParts.js +96 -0
  21. package/dist/DiffTable.d.ts +13 -0
  22. package/dist/DiffTable.js +5 -0
  23. package/dist/DiffTableParts.d.ts +57 -0
  24. package/dist/DiffTableParts.js +103 -0
  25. package/dist/Drawings.d.ts +13 -0
  26. package/dist/Drawings.js +5 -0
  27. package/dist/DrawingsParts.d.ts +194 -0
  28. package/dist/DrawingsParts.js +775 -0
  29. package/dist/Fullscreen.d.ts +7 -0
  30. package/dist/Fullscreen.js +5 -0
  31. package/dist/FullscreenParts.d.ts +54 -0
  32. package/dist/FullscreenParts.js +250 -0
  33. package/dist/Ladder.d.ts +13 -0
  34. package/dist/Ladder.js +5 -0
  35. package/dist/LadderParts.d.ts +49 -0
  36. package/dist/LadderParts.js +56 -0
  37. package/dist/Menu.d.ts +11 -0
  38. package/dist/Menu.js +5 -0
  39. package/dist/MenuParts.d.ts +52 -0
  40. package/dist/MenuParts.js +202 -0
  41. package/dist/Notice.d.ts +18 -0
  42. package/dist/Notice.js +9 -0
  43. package/dist/Panels.d.ts +9 -0
  44. package/dist/Panels.js +5 -0
  45. package/dist/PanelsParts.d.ts +62 -0
  46. package/dist/PanelsParts.js +387 -0
  47. package/dist/Receipt.d.ts +11 -0
  48. package/dist/Receipt.js +5 -0
  49. package/dist/ReceiptParts.d.ts +45 -0
  50. package/dist/ReceiptParts.js +71 -0
  51. package/dist/Scrubber.d.ts +9 -0
  52. package/dist/Scrubber.js +5 -0
  53. package/dist/ScrubberParts.d.ts +74 -0
  54. package/dist/ScrubberParts.js +126 -0
  55. package/dist/Selection.d.ts +14 -0
  56. package/dist/Selection.js +7 -0
  57. package/dist/SelectionParts.d.ts +63 -0
  58. package/dist/SelectionParts.js +208 -0
  59. package/dist/Studies.d.ts +9 -0
  60. package/dist/Studies.js +5 -0
  61. package/dist/StudiesParts.d.ts +45 -0
  62. package/dist/StudiesParts.js +153 -0
  63. package/dist/Submenu.d.ts +7 -0
  64. package/dist/Submenu.js +4 -0
  65. package/dist/SymbolSearch.d.ts +10 -0
  66. package/dist/SymbolSearch.js +5 -0
  67. package/dist/SymbolSearchParts.d.ts +39 -0
  68. package/dist/SymbolSearchParts.js +86 -0
  69. package/dist/Tape.d.ts +15 -0
  70. package/dist/Tape.js +5 -0
  71. package/dist/TapeParts.d.ts +69 -0
  72. package/dist/TapeParts.js +139 -0
  73. package/dist/Toolbar.d.ts +8 -0
  74. package/dist/Toolbar.js +5 -0
  75. package/dist/ToolbarParts.d.ts +61 -0
  76. package/dist/ToolbarParts.js +122 -0
  77. package/dist/Transport.d.ts +15 -0
  78. package/dist/Transport.js +5 -0
  79. package/dist/TransportParts.d.ts +52 -0
  80. package/dist/TransportParts.js +47 -0
  81. package/dist/VolumeProfile.d.ts +7 -0
  82. package/dist/VolumeProfile.js +5 -0
  83. package/dist/VolumeProfileParts.d.ts +37 -0
  84. package/dist/VolumeProfileParts.js +57 -0
  85. package/dist/accessible-name.d.ts +26 -0
  86. package/dist/accessible-name.js +14 -0
  87. package/dist/attributes.d.ts +8 -0
  88. package/dist/attributes.js +9 -0
  89. package/dist/core/adapter.d.ts +38 -0
  90. package/dist/core/adapter.js +64 -0
  91. package/dist/core/attributes.d.ts +36 -0
  92. package/dist/core/attributes.js +22 -0
  93. package/dist/core/candles.d.ts +6 -0
  94. package/dist/core/candles.js +19 -0
  95. package/dist/core/chart-gesture.d.ts +109 -0
  96. package/dist/core/chart-gesture.js +161 -0
  97. package/dist/core/chart-scale.d.ts +141 -0
  98. package/dist/core/chart-scale.js +302 -0
  99. package/dist/core/csv-bars.d.ts +54 -0
  100. package/dist/core/csv-bars.js +325 -0
  101. package/dist/core/depth-profile.d.ts +43 -0
  102. package/dist/core/depth-profile.js +66 -0
  103. package/dist/core/diff-table.d.ts +57 -0
  104. package/dist/core/diff-table.js +77 -0
  105. package/dist/core/drawing-fields.d.ts +11 -0
  106. package/dist/core/drawing-fields.js +15 -0
  107. package/dist/core/drawings.d.ts +194 -0
  108. package/dist/core/drawings.js +403 -0
  109. package/dist/core/fullscreen.d.ts +32 -0
  110. package/dist/core/fullscreen.js +16 -0
  111. package/dist/core/index.d.ts +41 -0
  112. package/dist/core/index.js +23 -0
  113. package/dist/core/ladder-keys.d.ts +35 -0
  114. package/dist/core/ladder-keys.js +43 -0
  115. package/dist/core/ladder.d.ts +65 -0
  116. package/dist/core/ladder.js +93 -0
  117. package/dist/core/panels.d.ts +72 -0
  118. package/dist/core/panels.js +151 -0
  119. package/dist/core/receipt-rules.d.ts +42 -0
  120. package/dist/core/receipt-rules.js +20 -0
  121. package/dist/core/scrubber-gesture.d.ts +66 -0
  122. package/dist/core/scrubber-gesture.js +69 -0
  123. package/dist/core/selection.d.ts +24 -0
  124. package/dist/core/selection.js +33 -0
  125. package/dist/core/series.d.ts +161 -0
  126. package/dist/core/series.js +331 -0
  127. package/dist/core/studies.d.ts +80 -0
  128. package/dist/core/studies.js +237 -0
  129. package/dist/core/symbol-search.d.ts +89 -0
  130. package/dist/core/symbol-search.js +223 -0
  131. package/dist/core/tape.d.ts +77 -0
  132. package/dist/core/tape.js +136 -0
  133. package/dist/core/volume-profile.d.ts +47 -0
  134. package/dist/core/volume-profile.js +86 -0
  135. package/dist/index.d.ts +82 -0
  136. package/dist/index.js +42 -0
  137. package/dist/ladder-keys.d.ts +16 -0
  138. package/dist/ladder-keys.js +86 -0
  139. package/dist/panels-context.d.ts +47 -0
  140. package/dist/panels-context.js +3 -0
  141. package/dist/selection-context.d.ts +41 -0
  142. package/dist/selection-context.js +4 -0
  143. package/examples/csv-to-chart-frame.mjs +17 -0
  144. package/examples/studies.mjs +12 -0
  145. package/llms.txt +16 -0
  146. package/package.json +67 -0
  147. package/src/Chart.tsx +7 -0
  148. package/src/ChartParts.tsx +529 -0
  149. package/src/CsvDrop.tsx +6 -0
  150. package/src/CsvDropParts.tsx +128 -0
  151. package/src/Cursor.tsx +6 -0
  152. package/src/CursorParts.tsx +77 -0
  153. package/src/DepthProfile.tsx +6 -0
  154. package/src/DepthProfileParts.tsx +154 -0
  155. package/src/DiffTable.tsx +6 -0
  156. package/src/DiffTableParts.tsx +160 -0
  157. package/src/Drawings.tsx +6 -0
  158. package/src/DrawingsParts.tsx +1080 -0
  159. package/src/Fullscreen.tsx +6 -0
  160. package/src/FullscreenParts.tsx +318 -0
  161. package/src/Ladder.tsx +6 -0
  162. package/src/LadderParts.tsx +114 -0
  163. package/src/Menu.tsx +6 -0
  164. package/src/MenuParts.tsx +364 -0
  165. package/src/Notice.tsx +27 -0
  166. package/src/Panels.tsx +6 -0
  167. package/src/PanelsParts.tsx +454 -0
  168. package/src/Receipt.tsx +6 -0
  169. package/src/ReceiptParts.tsx +118 -0
  170. package/src/Scrubber.tsx +6 -0
  171. package/src/ScrubberParts.tsx +207 -0
  172. package/src/Selection.tsx +12 -0
  173. package/src/SelectionParts.tsx +251 -0
  174. package/src/Studies.tsx +6 -0
  175. package/src/StudiesParts.tsx +226 -0
  176. package/src/Submenu.tsx +5 -0
  177. package/src/SymbolSearch.tsx +6 -0
  178. package/src/SymbolSearchParts.tsx +166 -0
  179. package/src/Tape.tsx +6 -0
  180. package/src/TapeParts.tsx +208 -0
  181. package/src/Toolbar.tsx +6 -0
  182. package/src/ToolbarParts.tsx +179 -0
  183. package/src/Transport.tsx +6 -0
  184. package/src/TransportParts.tsx +128 -0
  185. package/src/VolumeProfile.tsx +6 -0
  186. package/src/VolumeProfileParts.tsx +106 -0
  187. package/src/accessible-name.tsx +34 -0
  188. package/src/attributes.ts +12 -0
  189. package/src/core/adapter.ts +91 -0
  190. package/src/core/attributes.ts +51 -0
  191. package/src/core/candles.ts +22 -0
  192. package/src/core/chart-gesture.ts +228 -0
  193. package/src/core/chart-scale.ts +368 -0
  194. package/src/core/csv-bars.ts +328 -0
  195. package/src/core/depth-profile.ts +84 -0
  196. package/src/core/diff-table.ts +105 -0
  197. package/src/core/drawing-fields.ts +17 -0
  198. package/src/core/drawings.ts +478 -0
  199. package/src/core/fullscreen.ts +32 -0
  200. package/src/core/index.ts +42 -0
  201. package/src/core/ladder-keys.ts +61 -0
  202. package/src/core/ladder.ts +156 -0
  203. package/src/core/panels.ts +177 -0
  204. package/src/core/receipt-rules.ts +45 -0
  205. package/src/core/scrubber-gesture.ts +102 -0
  206. package/src/core/selection.ts +41 -0
  207. package/src/core/series.ts +374 -0
  208. package/src/core/studies.ts +260 -0
  209. package/src/core/symbol-search.ts +256 -0
  210. package/src/core/tape.ts +159 -0
  211. package/src/core/volume-profile.ts +116 -0
  212. package/src/index.ts +83 -0
  213. package/src/ladder-keys.ts +83 -0
  214. package/src/panels-context.ts +46 -0
  215. package/src/selection-context.ts +38 -0
  216. package/src/tokens.css +235 -0
@@ -0,0 +1,161 @@
1
+ /**
2
+ * The chart's pointer and wheel gestures as plain data in, plain data out: press and drag to pan, a drag on the price
3
+ * labels, the wheel, a two-finger pinch and the double tap that gives the price back to the bars. The host reads
4
+ * coordinates off its DOM events, passes them in with the frame, viewport and bar count of the moment, and applies the
5
+ * change the result names. Nothing here touches a DOM, so the rules run and test without one.
6
+ */
7
+ import { panViewport, zoomPriceRange, zoomViewport } from './chart-scale.js';
8
+ export const INITIAL_CHART_GESTURE = { touches: [], drag: null, pinch: null, lastTap: null };
9
+ const same = (state) => ({ state });
10
+ const inGutter = (frame, point) => point.x >= frame.plotWidth && point.y >= 0 && point.y < frame.plotHeight;
11
+ const heldRange = (frame) => [frame.priceMin, frame.priceMax];
12
+ /** Whether a one-pointer drag (a pan or a price-label drag) is in progress. A two-finger pinch is separate: read `state.pinch`. The Plot keeps the hover price while a drag runs, as before the reducer moved to core. */
13
+ export function isChartGestureActive(state) {
14
+ return state.drag !== null;
15
+ }
16
+ export function reduceChartGesture(event, state) {
17
+ switch (event.type) {
18
+ case 'down': return press(event, state);
19
+ case 'move': return move(event, state);
20
+ case 'up':
21
+ case 'cancel': return release(event, state);
22
+ case 'wheel': return wheel(event, state);
23
+ }
24
+ }
25
+ function press(event, state) {
26
+ const { pointerId, touch, point, view } = event;
27
+ const { frame, viewport } = view;
28
+ const touches = !touch ? state.touches
29
+ : state.touches.some((t) => t.id === pointerId) ? state.touches.map((t) => (t.id === pointerId ? { id: pointerId, ...point } : t))
30
+ : [...state.touches, { id: pointerId, ...point }];
31
+ if (touch && touches.length === 2) {
32
+ // The second finger turns the drag into a pinch.
33
+ const [a, b] = touches;
34
+ return {
35
+ state: {
36
+ ...state, touches, drag: null,
37
+ pinch: {
38
+ ids: [a.id, b.id], frame, viewport, range: heldRange(frame),
39
+ mid: { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 },
40
+ dx: Math.abs(a.x - b.x), dy: Math.abs(a.y - b.y),
41
+ },
42
+ },
43
+ capture: true,
44
+ };
45
+ }
46
+ // A press on the price labels moves the price range instead of the time.
47
+ const price = inGutter(frame, point) && frame.priceBottom > frame.priceTop
48
+ ? { range: heldRange(frame), perPixel: (frame.priceMax - frame.priceMin) / (frame.priceBottom - frame.priceTop), moved: false }
49
+ : null;
50
+ return {
51
+ state: { ...state, touches, drag: { id: pointerId, x: point.x, y: point.y, viewport, touch, decided: false, vertical: false, price } },
52
+ capture: true,
53
+ };
54
+ }
55
+ function move(event, state) {
56
+ const { pointerId, point, view } = event;
57
+ const { frame, total, hoverBottom = frame.plotHeight } = view;
58
+ const touches = state.touches.some((t) => t.id === pointerId) ? state.touches.map((t) => (t.id === pointerId ? { id: pointerId, ...point } : t)) : state.touches;
59
+ state = touches === state.touches ? state : { ...state, touches };
60
+ const pinched = state.pinch;
61
+ if (pinched)
62
+ return pinched.ids.includes(pointerId) ? { state, ...movePinch(pinched, state.touches, view) } : same(state);
63
+ const held = state.drag;
64
+ if (held && held.id === pointerId && held.price) {
65
+ const moved = held.price.moved || Math.abs(point.y - held.y) > 4;
66
+ const next = moved === held.price.moved ? state : { ...state, drag: { ...held, price: { ...held.price, moved } } };
67
+ if (!moved)
68
+ return same(next);
69
+ // Dragging the labels down brings higher prices into view, as if the price scale were grabbed.
70
+ const shift = (point.y - held.y) * held.price.perPixel;
71
+ return { state: next, priceRange: [held.price.range[0] + shift, held.price.range[1] + shift] };
72
+ }
73
+ if (held && held.id === pointerId) {
74
+ // A finger drifts sideways while it scrolls the page, so a touch drag waits for 8 px, then pans only if it reads as
75
+ // horizontal. A mostly vertical drag never pans.
76
+ let drag = held;
77
+ if (held.touch && !held.decided) {
78
+ if (Math.hypot(point.x - held.x, point.y - held.y) <= 8)
79
+ return same(state);
80
+ drag = { ...held, decided: true, vertical: Math.abs(point.y - held.y) > Math.abs(point.x - held.x) };
81
+ state = { ...state, drag };
82
+ }
83
+ if (drag.vertical)
84
+ return same(state);
85
+ return { state, viewport: panViewport(drag.viewport, -(point.x - drag.x) / frame.barWidth, total) };
86
+ }
87
+ if (total === 0 || frame.last < frame.first)
88
+ return same(state);
89
+ // The gutters show labels, not bars: a pointer there would pick a bar that is not drawn.
90
+ if (point.x < 0 || point.x >= frame.plotWidth || point.y < 0 || point.y >= hoverBottom)
91
+ return { state, hover: { price: null } };
92
+ return {
93
+ state,
94
+ hover: {
95
+ bar: Math.min(frame.last, Math.max(frame.first, Math.round(frame.indexAt(point.x)))),
96
+ price: point.y >= frame.priceTop && point.y <= frame.priceBottom ? frame.priceAt(point.y) : null,
97
+ },
98
+ };
99
+ }
100
+ function movePinch(p, touches, view) {
101
+ const a = touches.find((t) => t.id === p.ids[0]);
102
+ const b = touches.find((t) => t.id === p.ids[1]);
103
+ if (!a || !b)
104
+ return {};
105
+ const { total, dataSpan } = view;
106
+ const dx = Math.abs(a.x - b.x);
107
+ const dy = Math.abs(a.y - b.y);
108
+ const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
109
+ // Fingers spread sideways zoom the time, fingers spread up and down zoom the price. Two fingers that start close
110
+ // together on one axis leave that axis alone, so a sideways pinch never fixes the price range by accident.
111
+ const factorX = p.dx >= 24 ? p.dx / Math.max(dx, 1) : 1;
112
+ let next = zoomViewport(p.viewport, factorX, p.frame.indexAt(p.mid.x), total);
113
+ const barWidth = p.frame.plotWidth / (next.end - next.start);
114
+ next = panViewport(next, -(mid.x - p.mid.x) / barWidth, total);
115
+ if (p.dy < 40)
116
+ return { viewport: next };
117
+ const factorY = p.dy / Math.max(dy, 1);
118
+ const zoomed = zoomPriceRange(p.range, factorY, p.frame.priceAt(p.mid.y), dataSpan);
119
+ const perPixel = (zoomed[1] - zoomed[0]) / (p.frame.priceBottom - p.frame.priceTop);
120
+ const shift = (mid.y - p.mid.y) * perPixel;
121
+ return { viewport: next, priceRange: [zoomed[0] + shift, zoomed[1] + shift] };
122
+ }
123
+ function release(event, state) {
124
+ const touches = state.touches.filter((t) => t.id !== event.pointerId);
125
+ state = { ...state, touches };
126
+ if (state.pinch?.ids.includes(event.pointerId))
127
+ return same({ ...state, pinch: null, drag: null }); // the finger left behind must not jump the chart
128
+ const held = state.drag;
129
+ if (held?.id !== event.pointerId)
130
+ return same(state);
131
+ state = { ...state, drag: null };
132
+ // A quick second tap on the price labels gives the price back to the bars.
133
+ if (held.price && !held.price.moved && event.type === 'up') {
134
+ const before = state.lastTap;
135
+ if (before && event.time - before.time < 400 && Math.hypot(held.x - before.x, held.y - before.y) < 24) {
136
+ return { state: { ...state, lastTap: null }, priceRange: null };
137
+ }
138
+ return same({ ...state, lastTap: { time: event.time, x: held.x, y: held.y } });
139
+ }
140
+ return same(state);
141
+ }
142
+ function wheel(event, state) {
143
+ const { point, deltaY, view } = event;
144
+ const { frame, viewport, total, dataSpan } = view;
145
+ if (deltaY === 0 || total === 0)
146
+ return same(state);
147
+ // Over the price labels the wheel zooms the price instead of the time.
148
+ if (inGutter(frame, point)) {
149
+ return {
150
+ state,
151
+ preventDefault: true,
152
+ priceRange: zoomPriceRange(heldRange(frame), deltaY > 0 ? 1.1 : 0.9, frame.priceAt(Math.min(frame.priceBottom, Math.max(frame.priceTop, point.y))), dataSpan),
153
+ };
154
+ }
155
+ // Only a wheel that zooms is kept from the page: over an empty chart, or at the zoom limit, the page scrolls.
156
+ const next = zoomViewport(viewport, deltaY > 0 ? 1.1 : 0.9, frame.indexAt(point.x), total);
157
+ // At the zoom limit the span stays the same but the anchor can still shift the start: that is not a zoom.
158
+ if (Math.abs((next.end - next.start) - (viewport.end - viewport.start)) < 1e-9)
159
+ return same(state);
160
+ return { state, preventDefault: true, viewport: next };
161
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The shared geometry of the chart primitives (Candles, Volume, Studies, VolumeProfile, Drawings) as plain
3
+ * functions. Bars are OpenCandle-shaped records: a Unix-millisecond `timestamp` and numeric OHLCV. Extra fields on a
4
+ * record are ignored.
5
+ *
6
+ * The x axis is the bar index, not the clock, so gaps (nights, weekends) take no space, as traders expect. A point
7
+ * in time between or beyond bars maps to a fractional index, so drawings anchored to a time keep their place when
8
+ * the chart scrolls. Nothing here throws on empty or bad input: an empty chart has a frame with no bars.
9
+ */
10
+ export interface CandleBar {
11
+ /** Unix milliseconds, ascending and unique across the series. */
12
+ timestamp: number;
13
+ open: number;
14
+ high: number;
15
+ low: number;
16
+ close: number;
17
+ volume: number;
18
+ }
19
+ /** The bars on screen: indexes [start, end), fractional while panning. */
20
+ export interface ChartViewport {
21
+ start: number;
22
+ end: number;
23
+ }
24
+ export interface ChartSize {
25
+ width: number;
26
+ height: number;
27
+ }
28
+ export type PriceSpan = readonly [number, number];
29
+ export type ChartInclude = readonly PriceSpan[] | ((visible: {
30
+ first: number;
31
+ last: number;
32
+ }) => readonly PriceSpan[]);
33
+ export interface ChartFrameOptions {
34
+ /** Share of the height kept for the volume pane under the prices, 0 for none. Default 0.2. */
35
+ volumeShare?: number;
36
+ /** Empty pixels above the highest and below the lowest price. Default 8. */
37
+ pricePadding?: number;
38
+ /** Width kept on the right for price labels, so they never sit on the bars. Default 56. */
39
+ axisRight?: number;
40
+ /** Height kept at the bottom for time labels. Default 18. */
41
+ axisBottom?: number;
42
+ /**
43
+ * Extra prices that must stay in view (study lines, drawings), as [low, high] pairs, or a function of the whole
44
+ * bars in view that returns them (so a study can measure only what is on screen, e.g. with studyRange).
45
+ */
46
+ include?: ChartInclude;
47
+ /**
48
+ * A fixed price range [low, high] in place of the autoscale. With it set, the bars and `include` no longer decide
49
+ * the range, so the view can be moved to any price (a drawing placed above or below the bars).
50
+ */
51
+ priceRange?: PriceSpan | null;
52
+ }
53
+ export interface ChartFrame {
54
+ width: number;
55
+ height: number;
56
+ /** Where the bars end and the price-label gutter begins (width minus axisRight). */
57
+ plotWidth: number;
58
+ /** Where the panes end and the time-label gutter begins (height minus axisBottom). */
59
+ plotHeight: number;
60
+ viewport: ChartViewport;
61
+ /** The whole bars inside the viewport, clamped to the series. */
62
+ first: number;
63
+ last: number;
64
+ /** Pixels per bar. */
65
+ barWidth: number;
66
+ /** Price pane: y runs from priceTop to priceBottom. */
67
+ priceTop: number;
68
+ priceBottom: number;
69
+ priceMin: number;
70
+ priceMax: number;
71
+ /** True when the price range is a fixed one a person set, false when it follows the bars. */
72
+ manualPrice: boolean;
73
+ /** Volume pane under the prices; empty (volumeTop === volumeBottom) when volumeShare is 0. */
74
+ volumeTop: number;
75
+ volumeBottom: number;
76
+ volumeMax: number;
77
+ /** Bar index (fractional allowed) to the x of that bar's centre. */
78
+ x: (index: number) => number;
79
+ /** x to a fractional bar index. */
80
+ indexAt: (x: number) => number;
81
+ y: (price: number) => number;
82
+ priceAt: (y: number) => number;
83
+ /** Volume to the y of the top of its bar in the volume pane. */
84
+ volumeY: (volume: number) => number;
85
+ }
86
+ /** A viewport showing the last `count` bars (all of them when there are fewer). */
87
+ export declare function lastBars(total: number, count: number): ChartViewport;
88
+ /**
89
+ * The view after the series grows (or shrinks) from `seenTotal` to `total` bars. A view that showed the whole series reopens on the
90
+ * default window (a chart that starts with 1 or 2 bars must not stay 1 or 2 bars wide), a view at the newest bar keeps its span and
91
+ * follows, and a view the user moved away from the end stays put. A view past the data is pulled back.
92
+ */
93
+ export declare function growViewport(view: ChartViewport, seenTotal: number, total: number, span?: number): ChartViewport;
94
+ export declare function chartFrame(bars: readonly CandleBar[], viewport: ChartViewport, size: ChartSize, options?: ChartFrameOptions): ChartFrame;
95
+ /** The usual gap between bars in milliseconds (the median), or 60 000 when there are fewer than two bars. */
96
+ export declare function barInterval(bars: readonly CandleBar[]): number;
97
+ /**
98
+ * A time to a fractional bar index: exact on a bar, interpolated between two bars, and extrapolated by the usual
99
+ * interval before the first or after the last bar. This is what keeps a drawing on its time as bars arrive.
100
+ */
101
+ export declare function indexOfTime(bars: readonly CandleBar[], timestamp: number): number;
102
+ /** The inverse of indexOfTime. */
103
+ export declare function timeOfIndex(bars: readonly CandleBar[], index: number): number;
104
+ /** Round, readable price ticks for the axis: about `count` steps of 1, 2 or 5 times a power of ten. */
105
+ export declare function priceTicks(min: number, max: number, count?: number): number[];
106
+ /** Decimals needed to print a tick step without noise (0.25 needs 2, 5 needs 0), up to the chart's 20. */
107
+ export declare function tickDecimals(ticks: readonly number[]): number;
108
+ export type ChartKeyAction = 'pan-left' | 'pan-right' | 'zoom-in' | 'zoom-out' | 'price-up' | 'price-down' | 'price-zoom-in' | 'price-zoom-out' | 'first' | 'last' | 'previous-bar' | 'next-bar' | 'clear';
109
+ /**
110
+ * Chart keys: ArrowLeft/Right move the crosshair bar by bar, Shift+Arrow pans a screen tenth, + and - zoom, ArrowUp/Down move the price range a tenth and Shift+ArrowUp/Down zoom it (the first of them leaves the autoscale), Home and
111
+ * End jump to the first and last bar, Escape clears the crosshair.
112
+ */
113
+ export declare function resolveChartKey(event: {
114
+ key: string;
115
+ altKey: boolean;
116
+ ctrlKey: boolean;
117
+ metaKey: boolean;
118
+ shiftKey: boolean;
119
+ }): ChartKeyAction | null;
120
+ /**
121
+ * Zoom around a point in bar-index space, where bar i is centred on the integer i (the space frame.x() and
122
+ * frame.indexAt() use). A pointer passes frame.indexAt(x); the keyboard passes the active bar's index, which is that
123
+ * bar's centre, so neither needs converting. The point keeps its share of the plot width, so whatever is under the
124
+ * pointer, and any drawing anchored there, stays put. factor < 1 zooms in. Keeps 5..total+50 bars.
125
+ */
126
+ export declare function zoomViewport(viewport: ChartViewport, factor: number, around: number, total: number): ChartViewport;
127
+ /** Move the viewport by `bars` (negative is back in time), keeping at least 5 bars of the series in view. */
128
+ export declare function panViewport(viewport: ChartViewport, bars: number, total: number): ChartViewport;
129
+ /** The lowest low and highest high of the whole series (padded like the autoscale when they are equal), or null when it has no finite prices. */
130
+ export declare function dataPriceSpan(bars: readonly CandleBar[]): PriceSpan | null;
131
+ /**
132
+ * Keeps a hand-set price range usable: its height between a fiftieth and twenty times the height of all the bars, and
133
+ * its middle no further from the bars than half its own height, so the bars can always be found again. With no bars
134
+ * there is nothing to measure against and the range passes through unchanged.
135
+ */
136
+ export declare function clampPriceRange(range: PriceSpan, data: PriceSpan | null): PriceSpan;
137
+ /** Zoom a price range around a price: factor < 1 zooms in, and `around` keeps its share of the height. */
138
+ export declare function zoomPriceRange(range: PriceSpan, factor: number, around: number, data: PriceSpan | null): PriceSpan;
139
+ /** Move a price range by `delta` price units (positive is up). */
140
+ export declare function panPriceRange(range: PriceSpan, delta: number, data: PriceSpan | null): PriceSpan;
141
+ //# sourceMappingURL=chart-scale.d.ts.map
@@ -0,0 +1,302 @@
1
+ /**
2
+ * The shared geometry of the chart primitives (Candles, Volume, Studies, VolumeProfile, Drawings) as plain
3
+ * functions. Bars are OpenCandle-shaped records: a Unix-millisecond `timestamp` and numeric OHLCV. Extra fields on a
4
+ * record are ignored.
5
+ *
6
+ * The x axis is the bar index, not the clock, so gaps (nights, weekends) take no space, as traders expect. A point
7
+ * in time between or beyond bars maps to a fractional index, so drawings anchored to a time keep their place when
8
+ * the chart scrolls. Nothing here throws on empty or bad input: an empty chart has a frame with no bars.
9
+ */
10
+ const finite = (n, fallback) => (Number.isFinite(n) ? n : fallback);
11
+ /** A viewport showing the last `count` bars (all of them when there are fewer). */
12
+ export function lastBars(total, count) {
13
+ const n = Math.max(1, Math.floor(finite(count, 1)));
14
+ const end = Math.max(0, Math.floor(finite(total, 0)));
15
+ // A short history shows just its bars; an empty one keeps a nonzero span so the frame stays finite.
16
+ return { start: Math.max(0, end - n), end: end === 0 ? n : end };
17
+ }
18
+ /**
19
+ * The view after the series grows (or shrinks) from `seenTotal` to `total` bars. A view that showed the whole series reopens on the
20
+ * default window (a chart that starts with 1 or 2 bars must not stay 1 or 2 bars wide), a view at the newest bar keeps its span and
21
+ * follows, and a view the user moved away from the end stays put. A view past the data is pulled back.
22
+ */
23
+ export function growViewport(view, seenTotal, total, span = 120) {
24
+ if (view.start <= 0 && view.end >= seenTotal)
25
+ return lastBars(total, span);
26
+ if (view.end >= seenTotal || view.end > total)
27
+ return lastBars(total, view.end - view.start);
28
+ return view;
29
+ }
30
+ export function chartFrame(bars, viewport, size, options = {}) {
31
+ const width = Math.max(1, finite(size.width, 1));
32
+ const height = Math.max(1, finite(size.height, 1));
33
+ let start = finite(viewport.start, 0);
34
+ let end = finite(viewport.end, start + 1);
35
+ if (end - start < 1)
36
+ end = start + 1;
37
+ const span = end - start;
38
+ const plotWidth = Math.max(1, width - Math.max(0, finite(options.axisRight ?? 56, 56)));
39
+ const plotHeight = Math.max(1, height - Math.max(0, finite(options.axisBottom ?? 18, 18)));
40
+ const barWidth = plotWidth / span;
41
+ const first = Math.max(0, Math.floor(start));
42
+ const last = Math.min(bars.length - 1, Math.ceil(end) - 1);
43
+ const volumeShare = Math.min(0.6, Math.max(0, finite(options.volumeShare ?? 0.2, 0.2)));
44
+ const padding = Math.max(0, finite(options.pricePadding ?? 8, 8));
45
+ const volumeHeight = Math.round(plotHeight * volumeShare);
46
+ const priceTop = padding;
47
+ const priceBottom = Math.max(priceTop + 1, plotHeight - volumeHeight - padding);
48
+ const volumeTop = plotHeight - volumeHeight;
49
+ const volumeBottom = plotHeight;
50
+ const fixed = options.priceRange;
51
+ const manualPrice = !!fixed && Number.isFinite(fixed[0]) && Number.isFinite(fixed[1]) && fixed[1] > fixed[0];
52
+ let priceMin = Infinity;
53
+ let priceMax = -Infinity;
54
+ let volumeMax = 0;
55
+ for (let i = first; i <= last; i++) {
56
+ const bar = bars[i];
57
+ if (!manualPrice) {
58
+ if (Number.isFinite(bar.low) && bar.low < priceMin)
59
+ priceMin = bar.low;
60
+ if (Number.isFinite(bar.high) && bar.high > priceMax)
61
+ priceMax = bar.high;
62
+ }
63
+ if (Number.isFinite(bar.volume) && bar.volume > volumeMax)
64
+ volumeMax = bar.volume;
65
+ }
66
+ if (manualPrice) {
67
+ priceMin = fixed[0];
68
+ priceMax = fixed[1];
69
+ }
70
+ else {
71
+ const include = typeof options.include === 'function' ? safeInclude(options.include, first, last) : options.include ?? [];
72
+ for (const [low, high] of include) {
73
+ if (Number.isFinite(low) && low < priceMin)
74
+ priceMin = low;
75
+ if (Number.isFinite(high) && high > priceMax)
76
+ priceMax = high;
77
+ }
78
+ if (!Number.isFinite(priceMin) || !Number.isFinite(priceMax)) {
79
+ priceMin = 0;
80
+ priceMax = 1;
81
+ }
82
+ else if (priceMin === priceMax) {
83
+ const pad = Math.abs(priceMin) * 0.01 || 1;
84
+ priceMin -= pad;
85
+ priceMax += pad;
86
+ }
87
+ }
88
+ if (volumeMax <= 0)
89
+ volumeMax = 1;
90
+ const priceRange = priceMax - priceMin;
91
+ const priceSpan = priceBottom - priceTop;
92
+ const x = (index) => (index - start + 0.5) * barWidth;
93
+ const indexAt = (px) => px / barWidth + start - 0.5;
94
+ const y = (price) => priceBottom - ((price - priceMin) / priceRange) * priceSpan;
95
+ const priceAt = (py) => priceMin + ((priceBottom - py) / priceSpan) * priceRange;
96
+ const volumeY = (volume) => volumeBottom - (Math.max(0, volume) / volumeMax) * (volumeBottom - volumeTop);
97
+ return {
98
+ width, height, plotWidth, plotHeight, viewport: { start, end }, first, last, barWidth,
99
+ priceTop, priceBottom, priceMin, priceMax, manualPrice, volumeTop, volumeBottom, volumeMax,
100
+ x, indexAt, y, priceAt, volumeY,
101
+ };
102
+ }
103
+ function safeInclude(include, first, last) {
104
+ try {
105
+ const out = include({ first, last });
106
+ return Array.isArray(out) ? out : [];
107
+ }
108
+ catch {
109
+ return [];
110
+ }
111
+ }
112
+ const intervals = new WeakMap();
113
+ /** The usual gap between bars in milliseconds (the median), or 60 000 when there are fewer than two bars. */
114
+ export function barInterval(bars) {
115
+ if (bars.length < 2)
116
+ return 60000;
117
+ const known = intervals.get(bars);
118
+ if (known?.length === bars.length)
119
+ return known.interval; // a host that pushes onto the same array gets a fresh one
120
+ // Every gap, not a sample: gaps that repeat (a weekend every fifth bar) would fool a sample taken at a fixed stride.
121
+ // Sorted once per series and remembered, so the drawings and the readout can ask as often as they like.
122
+ const gaps = new Float64Array(bars.length - 1);
123
+ for (let i = 1; i < bars.length; i++)
124
+ gaps[i - 1] = bars[i].timestamp - bars[i - 1].timestamp;
125
+ gaps.sort();
126
+ const median = gaps[gaps.length >> 1];
127
+ const interval = median > 0 && Number.isFinite(median) ? median : 60000;
128
+ intervals.set(bars, { length: bars.length, interval });
129
+ return interval;
130
+ }
131
+ /**
132
+ * A time to a fractional bar index: exact on a bar, interpolated between two bars, and extrapolated by the usual
133
+ * interval before the first or after the last bar. This is what keeps a drawing on its time as bars arrive.
134
+ */
135
+ export function indexOfTime(bars, timestamp) {
136
+ const n = bars.length;
137
+ if (n === 0 || !Number.isFinite(timestamp))
138
+ return 0;
139
+ if (timestamp <= bars[0].timestamp)
140
+ return (timestamp - bars[0].timestamp) / barInterval(bars);
141
+ if (timestamp >= bars[n - 1].timestamp)
142
+ return n - 1 + (timestamp - bars[n - 1].timestamp) / barInterval(bars);
143
+ let lo = 0;
144
+ let hi = n - 1;
145
+ while (hi - lo > 1) {
146
+ const mid = (lo + hi) >> 1;
147
+ if (bars[mid].timestamp <= timestamp)
148
+ lo = mid;
149
+ else
150
+ hi = mid;
151
+ }
152
+ const a = bars[lo].timestamp;
153
+ const b = bars[hi].timestamp;
154
+ return lo + (b === a ? 0 : (timestamp - a) / (b - a));
155
+ }
156
+ /** The inverse of indexOfTime. */
157
+ export function timeOfIndex(bars, index) {
158
+ const n = bars.length;
159
+ if (n === 0 || !Number.isFinite(index))
160
+ return 0;
161
+ if (index <= 0)
162
+ return bars[0].timestamp + index * barInterval(bars);
163
+ if (index >= n - 1)
164
+ return bars[n - 1].timestamp + (index - (n - 1)) * barInterval(bars);
165
+ const lo = Math.floor(index);
166
+ const a = bars[lo].timestamp;
167
+ return a + (index - lo) * (bars[lo + 1].timestamp - a);
168
+ }
169
+ /** Round, readable price ticks for the axis: about `count` steps of 1, 2 or 5 times a power of ten. */
170
+ export function priceTicks(min, max, count = 6) {
171
+ if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min)
172
+ return [];
173
+ const raw = (max - min) / Math.max(1, count);
174
+ const power = 10 ** Math.floor(Math.log10(raw));
175
+ const step = [1, 2, 5, 10].map((m) => m * power).find((s) => s >= raw) ?? 10 * power;
176
+ // Each tick is a whole number of steps, rounded to the step's own places, so float noise goes and a range of
177
+ // 1e-14 still gets distinct ticks (a fixed 12 places would round them all to 0).
178
+ const places = Math.min(100, Math.max(0, 1 - Math.floor(Math.log10(step))));
179
+ const ticks = [];
180
+ for (let i = Math.ceil(min / step); i * step <= max + step * 1e-9 && ticks.length < 50; i += 1)
181
+ ticks.push(Number((i * step).toFixed(places)));
182
+ return ticks;
183
+ }
184
+ /** Decimals needed to print a tick step without noise (0.25 needs 2, 5 needs 0), up to the chart's 20. */
185
+ export function tickDecimals(ticks) {
186
+ if (ticks.length < 2)
187
+ return 2;
188
+ const step = Math.abs(ticks[1] - ticks[0]);
189
+ if (!Number.isFinite(step) || step === 0)
190
+ return 2;
191
+ // The fewest places that print the step exactly (0.25 needs 2, 2.5 needs 1), not only its order of magnitude. A
192
+ // step below one at these places is not yet printed, however small it is (2e-10 needs 10, not 0).
193
+ for (let places = 0; places < 20; places += 1) {
194
+ const scaled = step * 10 ** places;
195
+ if (Math.round(scaled) >= 1 && Math.abs(scaled - Math.round(scaled)) < 1e-6 * scaled)
196
+ return places;
197
+ }
198
+ return 20;
199
+ }
200
+ /**
201
+ * Chart keys: ArrowLeft/Right move the crosshair bar by bar, Shift+Arrow pans a screen tenth, + and - zoom, ArrowUp/Down move the price range a tenth and Shift+ArrowUp/Down zoom it (the first of them leaves the autoscale), Home and
202
+ * End jump to the first and last bar, Escape clears the crosshair.
203
+ */
204
+ export function resolveChartKey(event) {
205
+ if (event.altKey || event.ctrlKey || event.metaKey)
206
+ return null;
207
+ switch (event.key) {
208
+ case 'ArrowLeft': return event.shiftKey ? 'pan-left' : 'previous-bar';
209
+ case 'ArrowRight': return event.shiftKey ? 'pan-right' : 'next-bar';
210
+ case '+':
211
+ case '=': return 'zoom-in';
212
+ case '-':
213
+ case '_': return 'zoom-out';
214
+ case 'ArrowUp': return event.shiftKey ? 'price-zoom-in' : 'price-up';
215
+ case 'ArrowDown': return event.shiftKey ? 'price-zoom-out' : 'price-down';
216
+ case 'Home': return event.shiftKey ? null : 'first';
217
+ case 'End': return event.shiftKey ? null : 'last';
218
+ case 'Escape': return 'clear';
219
+ default: return null;
220
+ }
221
+ }
222
+ /**
223
+ * Zoom around a point in bar-index space, where bar i is centred on the integer i (the space frame.x() and
224
+ * frame.indexAt() use). A pointer passes frame.indexAt(x); the keyboard passes the active bar's index, which is that
225
+ * bar's centre, so neither needs converting. The point keeps its share of the plot width, so whatever is under the
226
+ * pointer, and any drawing anchored there, stays put. factor < 1 zooms in. Keeps 5..total+50 bars.
227
+ */
228
+ export function zoomViewport(viewport, factor, around, total) {
229
+ const span = viewport.end - viewport.start;
230
+ const next = Math.min(Math.max(5, total + 50), Math.max(5, span * (Number.isFinite(factor) && factor > 0 ? factor : 1)));
231
+ // x() puts bar i's centre half a bar in from the left edge, so the share is measured from start - 0.5.
232
+ const share = span > 0 ? (around - viewport.start + 0.5) / span : 0.5;
233
+ const start = around + 0.5 - next * Math.min(1, Math.max(0, share));
234
+ return clampViewport({ start, end: start + next }, total);
235
+ }
236
+ /** Move the viewport by `bars` (negative is back in time), keeping at least 5 bars of the series in view. */
237
+ export function panViewport(viewport, bars, total) {
238
+ const shift = Number.isFinite(bars) ? bars : 0;
239
+ return clampViewport({ start: viewport.start + shift, end: viewport.end + shift }, total);
240
+ }
241
+ function clampViewport(viewport, total) {
242
+ const span = viewport.end - viewport.start;
243
+ const keep = Math.min(5, Math.max(1, total));
244
+ let start = viewport.start;
245
+ if (start > total - keep)
246
+ start = total - keep;
247
+ if (start + span < keep)
248
+ start = keep - span;
249
+ return { start, end: start + span };
250
+ }
251
+ /** The lowest low and highest high of the whole series (padded like the autoscale when they are equal), or null when it has no finite prices. */
252
+ export function dataPriceSpan(bars) {
253
+ let low = Infinity;
254
+ let high = -Infinity;
255
+ for (const bar of bars) {
256
+ if (Number.isFinite(bar.low) && bar.low < low)
257
+ low = bar.low;
258
+ if (Number.isFinite(bar.high) && bar.high > high)
259
+ high = bar.high;
260
+ }
261
+ if (!(low <= high))
262
+ return null;
263
+ // Flat data gets the same padding the autoscale gives it, so a first pan or zoom keeps its direction and size.
264
+ if (low === high) {
265
+ const pad = Math.abs(low) * 0.01 || 1;
266
+ return [low - pad, high + pad];
267
+ }
268
+ return [low, high];
269
+ }
270
+ /**
271
+ * Keeps a hand-set price range usable: its height between a fiftieth and twenty times the height of all the bars, and
272
+ * its middle no further from the bars than half its own height, so the bars can always be found again. With no bars
273
+ * there is nothing to measure against and the range passes through unchanged.
274
+ */
275
+ export function clampPriceRange(range, data) {
276
+ let [low, high] = range;
277
+ if (!Number.isFinite(low) || !Number.isFinite(high) || high <= low)
278
+ return range;
279
+ if (!data)
280
+ return [low, high];
281
+ const dataHeight = Math.max(data[1] - data[0], Math.abs(data[1]) * 1e-6, 1e-9);
282
+ const height = Math.min(dataHeight * 20, Math.max(dataHeight / 50, high - low));
283
+ let middle = (low + high) / 2;
284
+ middle = Math.min(data[1] + height / 2, Math.max(data[0] - height / 2, middle));
285
+ low = middle - height / 2;
286
+ high = middle + height / 2;
287
+ return [low, high];
288
+ }
289
+ /** Zoom a price range around a price: factor < 1 zooms in, and `around` keeps its share of the height. */
290
+ export function zoomPriceRange(range, factor, around, data) {
291
+ const height = range[1] - range[0];
292
+ const f = Number.isFinite(factor) && factor > 0 ? factor : 1;
293
+ const share = height > 0 ? (around - range[0]) / height : 0.5;
294
+ const next = height * f;
295
+ const low = around - next * Math.min(1, Math.max(0, share));
296
+ return clampPriceRange([low, low + next], data);
297
+ }
298
+ /** Move a price range by `delta` price units (positive is up). */
299
+ export function panPriceRange(range, delta, data) {
300
+ const d = Number.isFinite(delta) ? delta : 0;
301
+ return clampPriceRange([range[0] + d, range[1] + d], data);
302
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Turns a CSV file the user drops in into chart bars, so a chart can show the user's own data with no feed behind
3
+ * it. It reads the common shapes: a header row with names in any case and order (Date, Open, High, Low, Close,
4
+ * Adj Close, Volume, ...), separate Date and Time columns, a header-less file in time-open-high-low-close-volume
5
+ * order, comma, semicolon or tab delimiters, and decimal commas in semicolon or tab files.
6
+ *
7
+ * Times are read as UTC: an ISO date or date-time (an explicit offset is honoured), or a Unix time. A Unix time is read
8
+ * in the first unit that puts it before the year 2100: seconds, then milliseconds, then microseconds; anything later is
9
+ * refused. So a millisecond time in the first seven weeks of 1970 would be misread as seconds. Day-first or month-first dates like 02/01/2024 are refused rather than guessed. A row that doesn't
10
+ * make sense (a missing number, a high below the close, a repeated time) is reported by line, never fixed; the rest
11
+ * of the file still loads. Nothing here throws.
12
+ */
13
+ import type { CandleBar } from './chart-scale.js';
14
+ export interface CsvBarsError {
15
+ /** The 1-based line in the file. 0 for the file as a whole. */
16
+ line: number;
17
+ message: string;
18
+ }
19
+ /** `sorted`: the file was newest first. `no-volume`: no volume column, so volume is 0. `no-header`: read by position. */
20
+ export type CsvBarsNote = 'sorted' | 'no-volume' | 'no-header';
21
+ /** A column besides time and the prices that holds only numbers (an indicator, a spread), read beside the bars. */
22
+ export interface CsvExtraColumn {
23
+ /** The header text as written. */
24
+ header: string;
25
+ /** One entry per kept bar, in time order: a number, or `null` when the cell said nothing (empty, `-`, `NA`, `NaN`, `null`). */
26
+ values: Array<number | null>;
27
+ }
28
+ export interface CsvBarsResult {
29
+ bars: CandleBar[];
30
+ errors: CsvBarsError[];
31
+ notes: CsvBarsNote[];
32
+ /** Only when `extraColumns` was asked for. */
33
+ extras?: CsvExtraColumn[];
34
+ /** Only when `extraColumns` was asked for: headers of columns left out because a cell was not a number or a "no value" word. */
35
+ skippedColumns?: string[];
36
+ /** How many bars had a time written without a zone (so `utcOffsetMinutes` applied to them). */
37
+ zoneless?: number;
38
+ }
39
+ export interface CsvBarsOptions {
40
+ /** More data rows than this are refused, so a wrong file can't freeze the page. Default 500,000. */
41
+ maxRows?: number;
42
+ /**
43
+ * Minutes east of UTC for times written without a zone (a time with an explicit offset or `Z`, and a Unix time, keep
44
+ * their own meaning). Default 0: UTC. For a file in New York winter time, pass -300. A fixed offset only; a file that
45
+ * crosses a daylight-saving change needs the zone written on each row.
46
+ */
47
+ utcOffsetMinutes?: number;
48
+ /** Also read every other all-number column as a series beside the bars. Default false. */
49
+ extraColumns?: boolean;
50
+ }
51
+ export declare function parseBarsCsv(text: string, options?: CsvBarsOptions): CsvBarsResult;
52
+ /** One plain sentence or two about a parse, for a status line: how many bars loaded and what was skipped. */
53
+ export declare function describeBarsCsv(result: CsvBarsResult, fileName?: string): string;
54
+ //# sourceMappingURL=csv-bars.d.ts.map