gloomberb 0.7.2 → 0.7.4
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.
- package/README.md +79 -132
- package/package.json +1 -1
- package/src/app.tsx +73 -67
- package/src/components/chart/chart-data.ts +7 -2
- package/src/components/chart/chart-pane-settings.ts +7 -4
- package/src/components/chart/chart-renderer.ts +158 -47
- package/src/components/chart/chart-types.ts +20 -2
- package/src/components/chart/chart.test.ts +179 -13
- package/src/components/chart/comparison-chart-renderer.test.ts +14 -30
- package/src/components/chart/comparison-chart-renderer.ts +50 -11
- package/src/components/chart/comparison-stock-chart.tsx +129 -129
- package/src/components/chart/market-session.ts +187 -0
- package/src/components/chart/native/chart-rasterizer.ts +108 -19
- package/src/components/chart/price-axis-labels.tsx +25 -9
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +97 -1
- package/src/components/chart/stock-chart.tsx +95 -49
- package/src/components/chart/time-axis-label.tsx +56 -2
- package/src/components/command-bar/command-bar.test.tsx +446 -120
- package/src/components/command-bar/command-bar.tsx +535 -384
- package/src/components/command-bar/command-registry.ts +16 -6
- package/src/components/command-bar/list-model.ts +8 -4
- package/src/components/command-bar/root-shortcuts.ts +4 -5
- package/src/components/command-bar/view-model.test.ts +21 -0
- package/src/components/command-bar/view-model.ts +17 -4
- package/src/components/command-bar/workflow-ops.ts +11 -32
- package/src/components/data-table-view.test.tsx +24 -1
- package/src/components/data-table-view.tsx +3 -2
- package/src/components/feed-data-table-stack-view.tsx +3 -2
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/floating-pane.tsx +9 -2
- package/src/components/layout/pane-footer.tsx +1 -1
- package/src/components/layout/pane-header.tsx +4 -2
- package/src/components/layout/pane-sizing.ts +12 -0
- package/src/components/layout/pane.tsx +24 -2
- package/src/components/layout/shell.tsx +24 -10
- package/src/components/layout/status-bar.test.tsx +4 -1
- package/src/components/layout/status-bar.tsx +4 -24
- package/src/components/markdown-text.tsx +53 -8
- package/src/components/onboarding/onboarding-wizard.tsx +7 -6
- package/src/components/pane-settings-dialog.tsx +9 -6
- package/src/components/pane-template-wizard.tsx +3 -2
- package/src/components/price-selector-dialog.tsx +3 -2
- package/src/components/price-sparkline-view.tsx +463 -0
- package/src/components/price-sparkline.ts +46 -0
- package/src/components/table-view-shared.tsx +21 -3
- package/src/components/ticker-list-table-view.tsx +3 -2
- package/src/components/ticker-list-table.tsx +15 -66
- package/src/components/toggle-list.tsx +3 -3
- package/src/components/ui/choice-dialog.tsx +3 -2
- package/src/components/ui/multi-select-dialog.tsx +3 -2
- package/src/components/ui/native-select.tsx +3 -3
- package/src/core/state/app-state.ts +210 -80
- package/src/core/state/session-persistence.ts +25 -0
- package/src/data/config-store-node.ts +113 -25
- package/src/data/config-store.test.ts +65 -3
- package/src/data/plugin-state-store.ts +39 -13
- package/src/data/sqlite-cache.test.ts +13 -0
- package/src/market-data/coordinator-subscriptions.test.ts +102 -18
- package/src/market-data/coordinator.test.ts +122 -2
- package/src/market-data/coordinator.ts +200 -56
- package/src/market-data/hooks.test.tsx +31 -0
- package/src/market-data/hooks.tsx +1 -0
- package/src/news/aggregator.test.ts +41 -0
- package/src/news/aggregator.ts +61 -9
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +25 -6
- package/src/plugins/builtin/ai/screener-pane.tsx +3 -0
- package/src/plugins/builtin/ai/settings.ts +3 -0
- package/src/plugins/builtin/broker-manager/index.tsx +4 -3
- package/src/plugins/builtin/chat-controller.test.ts +398 -17
- package/src/plugins/builtin/chat-controller.ts +263 -48
- package/src/plugins/builtin/chat.test.tsx +312 -6
- package/src/plugins/builtin/chat.tsx +245 -66
- package/src/plugins/builtin/comparison-chart.test.tsx +117 -3
- package/src/plugins/builtin/correlation/colors.test.ts +39 -0
- package/src/plugins/builtin/correlation/colors.ts +87 -0
- package/src/plugins/builtin/correlation/index.test.tsx +151 -0
- package/src/plugins/builtin/correlation/index.tsx +92 -21
- package/src/plugins/builtin/debug.tsx +4 -3
- package/src/plugins/builtin/econ/index.tsx +3 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/holders/index.tsx +25 -13
- package/src/plugins/builtin/layout-manager.tsx +1 -7
- package/src/plugins/builtin/market-movers/index.tsx +72 -40
- package/src/plugins/builtin/news-wire/breaking-pane.test.tsx +156 -0
- package/src/plugins/builtin/news-wire/breaking-pane.tsx +0 -90
- package/src/plugins/builtin/news-wire/index.tsx +1 -3
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +3 -2
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +4 -1
- package/src/plugins/builtin/news-wire/persisted-articles.ts +7 -1
- package/src/plugins/builtin/options.tsx +9 -17
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +6 -10
- package/src/plugins/builtin/portfolio-list/metrics.ts +13 -2
- package/src/plugins/builtin/portfolio-list/mutations.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +139 -23
- package/src/plugins/builtin/portfolio-list/quick-add.tsx +422 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -12
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -3
- package/src/plugins/builtin/portfolio-list.test.tsx +311 -9
- package/src/plugins/builtin/quote-monitor.test.tsx +158 -25
- package/src/plugins/builtin/sectors/index.tsx +275 -43
- package/src/plugins/builtin/sectors/sector-data.ts +41 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +30 -9
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +18 -7
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +445 -63
- package/src/plugins/builtin/ticker-detail/settings.ts +84 -3
- package/src/plugins/builtin/ticker-detail.test.tsx +2 -2
- package/src/plugins/builtin/world-indices/index.tsx +58 -19
- package/src/plugins/ibkr/trading-pane.tsx +3 -2
- package/src/plugins/plugin-runtime.tsx +2 -2
- package/src/plugins/prediction-markets/settings.ts +4 -0
- package/src/plugins/registry.test.ts +55 -0
- package/src/plugins/registry.ts +11 -4
- package/src/plugins/ticker-navigation.test.ts +35 -5
- package/src/plugins/ticker-navigation.ts +16 -22
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu-click.ts +7 -4
- package/src/renderers/electrobun/bun/application-menu.ts +6 -1
- package/src/renderers/electrobun/bun/desktop-workspace.ts +47 -29
- package/src/renderers/electrobun/bun/index.ts +52 -13
- package/src/renderers/electrobun/view/app-services.ts +51 -9
- package/src/renderers/electrobun/view/backend-rpc.ts +5 -8
- package/src/renderers/electrobun/view/desktop-controls.tsx +4 -4
- package/src/renderers/electrobun/view/main.tsx +0 -16
- package/src/renderers/electrobun/view/styles.css +23 -21
- package/src/renderers/electrobun/view/ui-host.tsx +6 -4
- package/src/renderers/opentui/host.tsx +2 -1
- package/src/renderers/opentui/start.tsx +1 -1
- package/src/renderers/opentui/test-utils.tsx +6 -5
- package/src/sources/gloomberb-cloud.test.ts +55 -2
- package/src/sources/gloomberb-cloud.ts +180 -29
- package/src/sources/provider-router.test.ts +567 -10
- package/src/sources/provider-router.ts +323 -93
- package/src/sources/yahoo-finance.test.ts +37 -0
- package/src/sources/yahoo-finance.ts +48 -2
- package/src/state/app-bootstrap.test.ts +50 -23
- package/src/state/app-bootstrap.ts +29 -13
- package/src/state/app-context.test.ts +113 -0
- package/src/state/app-context.tsx +8 -4
- package/src/state/session-persistence.test.ts +5 -3
- package/src/state/use-inline-tickers.ts +3 -0
- package/src/state/use-quote-streaming.ts +11 -0
- package/src/theme/colors.ts +13 -14
- package/src/types/config.ts +9 -4
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +1 -0
- package/src/types/plugin.ts +8 -1
- package/src/ui/context-menu.tsx +4 -4
- package/src/utils/api-client.test.ts +248 -0
- package/src/utils/api-client.ts +178 -26
- package/src/utils/exchanges.test.ts +16 -0
- package/src/utils/exchanges.ts +33 -0
- package/src/utils/fuzzy-search.ts +12 -1
- package/src/utils/keyboard.ts +31 -0
- package/src/utils/options.ts +1 -1
- package/src/utils/price-history.test.ts +28 -1
- package/src/utils/price-history.ts +16 -0
- package/src/utils/quote-freshness.test.ts +150 -0
- package/src/utils/quote-freshness.ts +42 -1
- package/src/utils/quote-resolution.ts +2 -0
- package/src/utils/quote-time.test.ts +34 -0
- package/src/utils/quote-time.ts +18 -5
- package/src/utils/ticker-search.test.ts +2 -2
- package/src/utils/ticker-search.ts +48 -1
- package/src/version.ts +1 -1
- package/src/plugins/builtin/news-wire/digest-store.ts +0 -44
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { RGBA } from "../../ui";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ChartAxisMode,
|
|
4
|
+
ChartColors,
|
|
5
|
+
ChartIndicatorOverlays,
|
|
6
|
+
ChartMarketSession,
|
|
7
|
+
ChartSessionBackgroundSpan,
|
|
8
|
+
Pixel,
|
|
9
|
+
PixelBuffer,
|
|
10
|
+
ChartRenderMode,
|
|
11
|
+
} from "./chart-types";
|
|
3
12
|
import type { ProjectedChartPoint } from "./chart-data";
|
|
4
13
|
import { formatCompactMarketPriceWithCurrency, formatMarketPrice, resolveAssetDisplayKind } from "../../utils/market-format";
|
|
14
|
+
import { resolveExtendedHoursBackgroundSpans } from "./market-session";
|
|
5
15
|
|
|
6
16
|
// ---------------------------------------------------------------------------
|
|
7
17
|
// Styled output types (unchanged public API)
|
|
@@ -26,7 +36,7 @@ export interface StyledContent {
|
|
|
26
36
|
const LAYER_GRID = 0;
|
|
27
37
|
const LAYER_FILL = 1;
|
|
28
38
|
const LAYER_DATA = 2;
|
|
29
|
-
const LAYER_OVERLAY =
|
|
39
|
+
const LAYER_OVERLAY = 1;
|
|
30
40
|
const LAYER_CROSSHAIR = 3;
|
|
31
41
|
|
|
32
42
|
function normalizeCount(value: number, min: number): number {
|
|
@@ -135,6 +145,8 @@ export function resolveChartPalette(
|
|
|
135
145
|
axisColor: baseColors.textDim,
|
|
136
146
|
activeRangeColor: baseColors.text,
|
|
137
147
|
inactiveRangeColor: blendHex(baseColors.bg, baseColors.textDim, 0.75),
|
|
148
|
+
preMarketBgColor: blendHex(baseColors.bg, "#8a641f", 0.28),
|
|
149
|
+
postMarketBgColor: blendHex(baseColors.bg, "#7a4624", 0.28),
|
|
138
150
|
candleUp: baseColors.positive,
|
|
139
151
|
candleDown: baseColors.negative,
|
|
140
152
|
wickUp: blendHex(baseColors.positive, baseColors.text, 0.35),
|
|
@@ -150,10 +162,12 @@ export function createPixelBuffer(width: number, heightPixels: number): PixelBuf
|
|
|
150
162
|
const bufferWidth = normalizeCount(width, 0);
|
|
151
163
|
const bufferHeight = normalizeCount(heightPixels, 0);
|
|
152
164
|
const pixels: (Pixel | null)[][] = [];
|
|
165
|
+
const backgrounds: (string | null)[][] = [];
|
|
153
166
|
for (let y = 0; y < bufferHeight; y++) {
|
|
154
167
|
pixels.push(new Array(bufferWidth).fill(null));
|
|
168
|
+
backgrounds.push(new Array(bufferWidth).fill(null));
|
|
155
169
|
}
|
|
156
|
-
return { width: bufferWidth, height: bufferHeight, pixels };
|
|
170
|
+
return { width: bufferWidth, height: bufferHeight, pixels, backgrounds };
|
|
157
171
|
}
|
|
158
172
|
|
|
159
173
|
function setPixel(buf: PixelBuffer, x: number, y: number, color: string, layer: number) {
|
|
@@ -224,6 +238,27 @@ function fillRect(buf: PixelBuffer, x0: number, y0: number, x1: number, y1: numb
|
|
|
224
238
|
}
|
|
225
239
|
}
|
|
226
240
|
|
|
241
|
+
function setBackgroundPixel(buf: PixelBuffer, x: number, y: number, color: string) {
|
|
242
|
+
const px = Math.round(x);
|
|
243
|
+
const py = Math.round(y);
|
|
244
|
+
if (px < 0 || px >= buf.width || py < 0 || py >= buf.height) return;
|
|
245
|
+
const row = buf.backgrounds[py];
|
|
246
|
+
if (!row) return;
|
|
247
|
+
row[px] = color;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function fillBackgroundRect(buf: PixelBuffer, x0: number, y0: number, x1: number, y1: number, color: string) {
|
|
251
|
+
const left = Math.max(Math.min(Math.round(x0), Math.round(x1)), 0);
|
|
252
|
+
const right = Math.min(Math.max(Math.round(x0), Math.round(x1)), Math.max(buf.width - 1, 0));
|
|
253
|
+
const top = Math.max(Math.min(Math.round(y0), Math.round(y1)), 0);
|
|
254
|
+
const bottom = Math.min(Math.max(Math.round(y0), Math.round(y1)), Math.max(buf.height - 1, 0));
|
|
255
|
+
for (let y = top; y <= bottom; y += 1) {
|
|
256
|
+
for (let x = left; x <= right; x += 1) {
|
|
257
|
+
setBackgroundPixel(buf, x, y, color);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
227
262
|
function drawWickOutsideBody(
|
|
228
263
|
buf: PixelBuffer,
|
|
229
264
|
x: number,
|
|
@@ -278,15 +313,41 @@ function getBarHalfWidth(pointCount: number, bufWidth: number): number {
|
|
|
278
313
|
return Math.floor(barW / 2);
|
|
279
314
|
}
|
|
280
315
|
|
|
316
|
+
function isOhlcLikeMode(mode: ChartRenderMode): boolean {
|
|
317
|
+
return mode === "ohlc" || mode === "hlc";
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function isHighLowMode(mode: ChartRenderMode): boolean {
|
|
321
|
+
return mode === "candles" || isOhlcLikeMode(mode);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function getOhlcTickLength(pointCount: number, bufWidth: number): number {
|
|
325
|
+
if (pointCount <= 1) return 4;
|
|
326
|
+
const spacing = bufWidth / pointCount;
|
|
327
|
+
return Math.min(Math.max(Math.round(spacing * 0.45), 3), 5);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function getOhlcStemWidth(pointCount: number, bufWidth: number): number {
|
|
331
|
+
if (pointCount <= 1) return 3;
|
|
332
|
+
const spacing = bufWidth / pointCount;
|
|
333
|
+
return Math.min(Math.max(Math.round(spacing * 0.2), 2), 3);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function getOhlcHorizontalPad(pointCount: number, bufWidth: number): number {
|
|
337
|
+
const tickLen = getOhlcTickLength(pointCount, bufWidth);
|
|
338
|
+
const stemWidth = getOhlcStemWidth(pointCount, bufWidth);
|
|
339
|
+
return Math.max(tickLen - 1, Math.ceil((stemWidth - 1) / 2), 0);
|
|
340
|
+
}
|
|
341
|
+
|
|
281
342
|
function getDotX(index: number, pointCount: number, width: number, mode: ChartRenderMode): number {
|
|
282
343
|
switch (mode) {
|
|
283
344
|
case "candles": {
|
|
284
345
|
const pad = getCandleHalfWidth(pointCount, width);
|
|
285
346
|
return getSeriesPosition(index, pointCount, width, pad, pad);
|
|
286
347
|
}
|
|
287
|
-
case "ohlc":
|
|
288
|
-
|
|
289
|
-
const pad =
|
|
348
|
+
case "ohlc":
|
|
349
|
+
case "hlc": {
|
|
350
|
+
const pad = getOhlcHorizontalPad(pointCount, width);
|
|
290
351
|
return getSeriesPosition(index, pointCount, width, pad, pad);
|
|
291
352
|
}
|
|
292
353
|
default:
|
|
@@ -303,6 +364,38 @@ export function getPointTerminalColumn(index: number, pointCount: number, width:
|
|
|
303
364
|
return Math.min(Math.max(Math.floor(getPointDotX(index, pointCount, width, mode) / 2), 0), width - 1);
|
|
304
365
|
}
|
|
305
366
|
|
|
367
|
+
function getTimePointBand(index: number, pointCount: number, width: number): { left: number; right: number } {
|
|
368
|
+
const currentX = getSeriesPosition(index, pointCount, width);
|
|
369
|
+
const previousX = index > 0 ? getSeriesPosition(index - 1, pointCount, width) : currentX;
|
|
370
|
+
const nextX = index < pointCount - 1 ? getSeriesPosition(index + 1, pointCount, width) : currentX;
|
|
371
|
+
return {
|
|
372
|
+
left: index === 0 ? 0 : Math.floor((previousX + currentX) / 2) + 1,
|
|
373
|
+
right: index === pointCount - 1 ? Math.max(width - 1, 0) : Math.floor((currentX + nextX) / 2),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function getSessionBackgroundColor(span: ChartSessionBackgroundSpan, colors: ChartColors): string {
|
|
378
|
+
return span.kind === "pre" ? colors.preMarketBgColor : colors.postMarketBgColor;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function drawSessionBackgrounds(
|
|
382
|
+
buf: PixelBuffer,
|
|
383
|
+
points: ProjectedChartPoint[],
|
|
384
|
+
spans: readonly ChartSessionBackgroundSpan[],
|
|
385
|
+
yTop: number,
|
|
386
|
+
yBottom: number,
|
|
387
|
+
colors: ChartColors,
|
|
388
|
+
) {
|
|
389
|
+
if (spans.length === 0 || points.length === 0) return;
|
|
390
|
+
for (const span of spans) {
|
|
391
|
+
const startIndex = Math.max(Math.min(span.startIndex, points.length - 1), 0);
|
|
392
|
+
const endIndex = Math.max(Math.min(span.endIndex, points.length - 1), startIndex);
|
|
393
|
+
const startBand = getTimePointBand(startIndex, points.length, buf.width);
|
|
394
|
+
const endBand = getTimePointBand(endIndex, points.length, buf.width);
|
|
395
|
+
fillBackgroundRect(buf, startBand.left, yTop, endBand.right, yBottom, getSessionBackgroundColor(span, colors));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
306
399
|
// ---------------------------------------------------------------------------
|
|
307
400
|
// Chart mode renderers
|
|
308
401
|
// ---------------------------------------------------------------------------
|
|
@@ -415,12 +508,16 @@ function drawOhlcChart(
|
|
|
415
508
|
palette: ResolvedChartPalette,
|
|
416
509
|
min: number,
|
|
417
510
|
max: number,
|
|
511
|
+
mode: Extract<ChartRenderMode, "ohlc" | "hlc">,
|
|
418
512
|
) {
|
|
419
|
-
const tickLen =
|
|
513
|
+
const tickLen = getOhlcTickLength(points.length, buf.width);
|
|
514
|
+
const stemWidth = getOhlcStemWidth(points.length, buf.width);
|
|
515
|
+
const stemLeft = Math.floor((stemWidth - 1) / 2);
|
|
516
|
+
const stemRight = Math.ceil((stemWidth - 1) / 2);
|
|
420
517
|
|
|
421
518
|
for (let i = 0; i < points.length; i++) {
|
|
422
519
|
const point = points[i]!;
|
|
423
|
-
const x = getDotX(i, points.length, buf.width,
|
|
520
|
+
const x = getDotX(i, points.length, buf.width, mode);
|
|
424
521
|
const highY = getScaledY(point.high, min, max, chartTop, chartBottom);
|
|
425
522
|
const lowY = getScaledY(point.low, min, max, chartTop, chartBottom);
|
|
426
523
|
const openY = getScaledY(point.open, min, max, chartTop, chartBottom);
|
|
@@ -428,14 +525,15 @@ function drawOhlcChart(
|
|
|
428
525
|
const isUp = point.close >= point.open;
|
|
429
526
|
const color = isUp ? palette.candleUp : palette.candleDown;
|
|
430
527
|
|
|
431
|
-
|
|
432
|
-
|
|
528
|
+
for (let dx = -stemLeft; dx <= stemRight; dx++) {
|
|
529
|
+
drawLine(buf, x + dx, highY, x + dx, lowY, color, LAYER_DATA);
|
|
530
|
+
}
|
|
433
531
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
532
|
+
if (mode === "ohlc") {
|
|
533
|
+
for (let dx = 0; dx < tickLen; dx++) {
|
|
534
|
+
setPixel(buf, x - dx, openY, color, LAYER_DATA);
|
|
535
|
+
}
|
|
437
536
|
}
|
|
438
|
-
// Close tick extends right
|
|
439
537
|
for (let dx = 0; dx < tickLen; dx++) {
|
|
440
538
|
setPixel(buf, x + dx, closeY, color, LAYER_DATA);
|
|
441
539
|
}
|
|
@@ -542,14 +640,19 @@ export function bufferToBrailleLines(buf: PixelBuffer): StyledContent[] {
|
|
|
542
640
|
let topLayer = -1;
|
|
543
641
|
|
|
544
642
|
// Track dots per layer so we can render only the top layer's dots
|
|
545
|
-
const dotsByLayer: Map<number, number> = new Map(); // layer
|
|
643
|
+
const dotsByLayer: Map<number, number> = new Map(); // layer -> braille bits
|
|
546
644
|
const colorByLayer: Map<number, Map<string, number>> = new Map();
|
|
645
|
+
const backgroundCounts: Map<string, number> = new Map();
|
|
547
646
|
|
|
548
647
|
for (let dy = 0; dy < 4; dy++) {
|
|
549
648
|
for (let dx = 0; dx < 2; dx++) {
|
|
550
649
|
const px = col * 2 + dx;
|
|
551
650
|
const py = row * 4 + dy;
|
|
552
651
|
if (px >= buf.width || py >= buf.height) continue;
|
|
652
|
+
const bg = buf.backgrounds[py]?.[px] ?? null;
|
|
653
|
+
if (bg) {
|
|
654
|
+
backgroundCounts.set(bg, (backgroundCounts.get(bg) || 0) + 1);
|
|
655
|
+
}
|
|
553
656
|
const pixel = buf.pixels[py]?.[px] ?? null;
|
|
554
657
|
if (pixel) {
|
|
555
658
|
const bit = BRAILLE_DOT[dy]![dx]!;
|
|
@@ -567,14 +670,19 @@ export function bufferToBrailleLines(buf: PixelBuffer): StyledContent[] {
|
|
|
567
670
|
let char: string;
|
|
568
671
|
let cellFg: string | undefined;
|
|
569
672
|
let cellBg: string | undefined;
|
|
673
|
+
let bestBackgroundCount = 0;
|
|
674
|
+
for (const [bg, count] of backgroundCounts) {
|
|
675
|
+
if (count > bestBackgroundCount) {
|
|
676
|
+
bestBackgroundCount = count;
|
|
677
|
+
cellBg = bg;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
570
680
|
|
|
571
681
|
if (topLayer < 0) {
|
|
572
682
|
char = " ";
|
|
573
683
|
cellFg = undefined;
|
|
574
|
-
cellBg = undefined;
|
|
575
684
|
} else {
|
|
576
|
-
// Only show dots from the highest layer
|
|
577
|
-
// cells (e.g. area chart where line and fill overlap).
|
|
685
|
+
// Only show dots from the highest layer so lines stay thin in mixed cells.
|
|
578
686
|
const pattern = dotsByLayer.get(topLayer) || 0;
|
|
579
687
|
char = String.fromCharCode(BRAILLE_BASE + pattern);
|
|
580
688
|
|
|
@@ -586,7 +694,6 @@ export function bufferToBrailleLines(buf: PixelBuffer): StyledContent[] {
|
|
|
586
694
|
}
|
|
587
695
|
|
|
588
696
|
cellFg = topColor;
|
|
589
|
-
cellBg = undefined;
|
|
590
697
|
}
|
|
591
698
|
|
|
592
699
|
if (char === runChar && cellFg === runFg && cellBg === runBg) {
|
|
@@ -792,6 +899,10 @@ function formatClockTime(date: Date, unit: AxisLabelUnit): string {
|
|
|
792
899
|
}
|
|
793
900
|
}
|
|
794
901
|
|
|
902
|
+
function formatFullCalendarDate(date: Date): string {
|
|
903
|
+
return `${MONTHS[date.getMonth()]} ${date.getDate()} ${date.getFullYear()}`;
|
|
904
|
+
}
|
|
905
|
+
|
|
795
906
|
function isSameCalendarDay(a: Date, b: Date): boolean {
|
|
796
907
|
return a.getFullYear() === b.getFullYear()
|
|
797
908
|
&& a.getMonth() === b.getMonth()
|
|
@@ -954,6 +1065,10 @@ function formatCursorTimeAxisValue(
|
|
|
954
1065
|
const minGapMs = getMinPositiveGapMs(validDates);
|
|
955
1066
|
const effectiveStepMs = Math.max(spanMs / Math.max(roughLabelCount - 1, 1), minGapMs || 0);
|
|
956
1067
|
const unit = resolveAxisLabelUnit(effectiveStepMs);
|
|
1068
|
+
if (unit === "year" || unit === "month") {
|
|
1069
|
+
return formatFullCalendarDate(date);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
957
1072
|
const counterpart = isSameCalendarDay(first, last)
|
|
958
1073
|
? first
|
|
959
1074
|
: isSameCalendarDay(date, first)
|
|
@@ -1121,6 +1236,7 @@ export interface RenderChartOptions {
|
|
|
1121
1236
|
colors: ResolvedChartPalette;
|
|
1122
1237
|
timeAxisDates?: Array<Date | string | number>;
|
|
1123
1238
|
indicators?: ChartIndicatorOverlays | null;
|
|
1239
|
+
marketSession?: ChartMarketSession | null;
|
|
1124
1240
|
}
|
|
1125
1241
|
|
|
1126
1242
|
export interface ChartScene {
|
|
@@ -1133,6 +1249,7 @@ export interface ChartScene {
|
|
|
1133
1249
|
mode: ChartRenderMode;
|
|
1134
1250
|
colors: ResolvedChartPalette;
|
|
1135
1251
|
indicators: ChartIndicatorOverlays | null;
|
|
1252
|
+
sessionBackgroundSpans: ChartSessionBackgroundSpan[];
|
|
1136
1253
|
min: number;
|
|
1137
1254
|
max: number;
|
|
1138
1255
|
activeIdx: number;
|
|
@@ -1194,27 +1311,6 @@ export function getActivePointIndex(
|
|
|
1194
1311
|
return bestIndex;
|
|
1195
1312
|
}
|
|
1196
1313
|
|
|
1197
|
-
function getIndicatorOverlayValues(indicators: ChartIndicatorOverlays | null | undefined): number[] {
|
|
1198
|
-
if (!indicators) return [];
|
|
1199
|
-
|
|
1200
|
-
const values: number[] = [];
|
|
1201
|
-
for (const line of indicators.smaLines) {
|
|
1202
|
-
values.push(...line.points.map((point) => point.value));
|
|
1203
|
-
}
|
|
1204
|
-
for (const line of indicators.emaLines) {
|
|
1205
|
-
values.push(...line.points.map((point) => point.value));
|
|
1206
|
-
}
|
|
1207
|
-
if (indicators.bollinger) {
|
|
1208
|
-
values.push(
|
|
1209
|
-
...indicators.bollinger.upper.map((point) => point.value),
|
|
1210
|
-
...indicators.bollinger.middle.map((point) => point.value),
|
|
1211
|
-
...indicators.bollinger.lower.map((point) => point.value),
|
|
1212
|
-
);
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
return values.filter((value) => Number.isFinite(value));
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
1314
|
export function buildChartScene(
|
|
1219
1315
|
points: ProjectedChartPoint[],
|
|
1220
1316
|
opts: RenderChartOptions,
|
|
@@ -1222,15 +1318,14 @@ export function buildChartScene(
|
|
|
1222
1318
|
if (points.length === 0) return null;
|
|
1223
1319
|
|
|
1224
1320
|
const dimensions = normalizeRenderDimensions(opts);
|
|
1225
|
-
const dataMin = opts.mode
|
|
1321
|
+
const dataMin = isHighLowMode(opts.mode)
|
|
1226
1322
|
? Math.min(...points.map((point) => point.low))
|
|
1227
1323
|
: Math.min(...points.map((point) => point.close));
|
|
1228
|
-
const dataMax = opts.mode
|
|
1324
|
+
const dataMax = isHighLowMode(opts.mode)
|
|
1229
1325
|
? Math.max(...points.map((point) => point.high))
|
|
1230
1326
|
: Math.max(...points.map((point) => point.close));
|
|
1231
|
-
const
|
|
1232
|
-
const
|
|
1233
|
-
const max = indicatorValues.length > 0 ? Math.max(dataMax, ...indicatorValues) : dataMax;
|
|
1327
|
+
const min = dataMin;
|
|
1328
|
+
const max = dataMax;
|
|
1234
1329
|
const activeIdx = getActivePointIndex(points.length, dimensions.width, opts.cursorX, opts.mode);
|
|
1235
1330
|
const activePoint = points[activeIdx]!;
|
|
1236
1331
|
const range = max - min || 1;
|
|
@@ -1259,6 +1354,10 @@ export function buildChartScene(
|
|
|
1259
1354
|
: max - (cursorY / Math.max(dimensions.chartRows - 1, 1)) * range;
|
|
1260
1355
|
|
|
1261
1356
|
const timeAxisDates = opts.timeAxisDates ?? points.map((point) => point.date);
|
|
1357
|
+
const sessionBackgroundSpans = resolveExtendedHoursBackgroundSpans(
|
|
1358
|
+
points.map((point) => point.date),
|
|
1359
|
+
opts.marketSession,
|
|
1360
|
+
);
|
|
1262
1361
|
|
|
1263
1362
|
return {
|
|
1264
1363
|
points,
|
|
@@ -1270,6 +1369,7 @@ export function buildChartScene(
|
|
|
1270
1369
|
mode: opts.mode,
|
|
1271
1370
|
colors: opts.colors,
|
|
1272
1371
|
indicators: opts.indicators ?? null,
|
|
1372
|
+
sessionBackgroundSpans,
|
|
1273
1373
|
min,
|
|
1274
1374
|
max,
|
|
1275
1375
|
activeIdx,
|
|
@@ -1381,6 +1481,14 @@ export function renderChart(
|
|
|
1381
1481
|
getAxisFractionDigitFloor(assetCategory, priceRange),
|
|
1382
1482
|
)
|
|
1383
1483
|
: null;
|
|
1484
|
+
drawSessionBackgrounds(
|
|
1485
|
+
buf,
|
|
1486
|
+
points,
|
|
1487
|
+
scene.sessionBackgroundSpans,
|
|
1488
|
+
0,
|
|
1489
|
+
showVolume ? volDotBottom : chartDotBottom,
|
|
1490
|
+
palette,
|
|
1491
|
+
);
|
|
1384
1492
|
drawGridLines(buf, gridLines.map((line) => line.y), palette.gridColor);
|
|
1385
1493
|
|
|
1386
1494
|
switch (mode) {
|
|
@@ -1394,7 +1502,10 @@ export function renderChart(
|
|
|
1394
1502
|
drawCandlestickChart(buf, points, 0, chartDotBottom, palette, min, max);
|
|
1395
1503
|
break;
|
|
1396
1504
|
case "ohlc":
|
|
1397
|
-
drawOhlcChart(buf, points, 0, chartDotBottom, palette, min, max);
|
|
1505
|
+
drawOhlcChart(buf, points, 0, chartDotBottom, palette, min, max, "ohlc");
|
|
1506
|
+
break;
|
|
1507
|
+
case "hlc":
|
|
1508
|
+
drawOhlcChart(buf, points, 0, chartDotBottom, palette, min, max, "hlc");
|
|
1398
1509
|
break;
|
|
1399
1510
|
}
|
|
1400
1511
|
|
|
@@ -1406,7 +1517,7 @@ export function renderChart(
|
|
|
1406
1517
|
volDotBottom,
|
|
1407
1518
|
palette.volumeUp,
|
|
1408
1519
|
palette.volumeDown,
|
|
1409
|
-
mode
|
|
1520
|
+
isHighLowMode(mode) ? "openClose" : "previousClose",
|
|
1410
1521
|
mode,
|
|
1411
1522
|
);
|
|
1412
1523
|
}
|
|
@@ -2,14 +2,15 @@ import type { PricePoint } from "../../types/financials";
|
|
|
2
2
|
|
|
3
3
|
export type TimeRange = "1D" | "1W" | "1M" | "3M" | "6M" | "1Y" | "5Y" | "ALL";
|
|
4
4
|
export type ChartResolution = "auto" | "1m" | "5m" | "15m" | "30m" | "45m" | "1h" | "1d" | "1wk" | "1mo";
|
|
5
|
-
export type ChartRenderMode = "area" | "line" | "candles" | "ohlc";
|
|
5
|
+
export type ChartRenderMode = "area" | "line" | "candles" | "ohlc" | "hlc";
|
|
6
6
|
export type ChartAxisMode = "price" | "percent";
|
|
7
7
|
export type ChartRendererPreference = "auto" | "kitty" | "braille";
|
|
8
8
|
export type ResolvedChartRenderer = "kitty" | "braille";
|
|
9
9
|
export type ComparisonChartRenderMode = "area" | "line";
|
|
10
|
+
export type ChartSessionBackgroundKind = "pre" | "post";
|
|
10
11
|
|
|
11
12
|
export const TIME_RANGES: TimeRange[] = ["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"];
|
|
12
|
-
export const CHART_RENDER_MODES: ChartRenderMode[] = ["area", "line", "candles", "ohlc"];
|
|
13
|
+
export const CHART_RENDER_MODES: ChartRenderMode[] = ["area", "line", "candles", "ohlc", "hlc"];
|
|
13
14
|
export const CHART_RENDERER_PREFERENCES: ChartRendererPreference[] = ["auto", "kitty", "braille"];
|
|
14
15
|
export const COMPARISON_RENDER_MODES: ComparisonChartRenderMode[] = ["area", "line"];
|
|
15
16
|
|
|
@@ -46,6 +47,7 @@ export interface PixelBuffer {
|
|
|
46
47
|
width: number;
|
|
47
48
|
height: number; // virtual pixel rows (4x terminal rows for braille rendering)
|
|
48
49
|
pixels: (Pixel | null)[][]; // [y][x] = pixel with color and layer, or null
|
|
50
|
+
backgrounds: (string | null)[][];
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
export interface ChartColors {
|
|
@@ -59,6 +61,22 @@ export interface ChartColors {
|
|
|
59
61
|
axisColor: string;
|
|
60
62
|
activeRangeColor: string;
|
|
61
63
|
inactiveRangeColor: string;
|
|
64
|
+
preMarketBgColor: string;
|
|
65
|
+
postMarketBgColor: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ChartMarketSession {
|
|
69
|
+
timeZone: string;
|
|
70
|
+
regularStartMinutes: number;
|
|
71
|
+
regularEndMinutes: number;
|
|
72
|
+
preMarketStartMinutes: number;
|
|
73
|
+
postMarketEndMinutes: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ChartSessionBackgroundSpan {
|
|
77
|
+
kind: ChartSessionBackgroundKind;
|
|
78
|
+
startIndex: number;
|
|
79
|
+
endIndex: number;
|
|
62
80
|
}
|
|
63
81
|
|
|
64
82
|
export interface VisibleWindow {
|