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
|
@@ -6,7 +6,6 @@ import { useShortcut } from "../../react/input";
|
|
|
6
6
|
import { useAppDispatch, useAppSelector, usePaneInstance, usePaneInstanceId, usePaneSettingValue, usePaneTicker } from "../../state/app-context";
|
|
7
7
|
import { usePaneFooter, type PaneFooterSegment, type PaneHint } from "../layout/pane-footer";
|
|
8
8
|
import { ShortcutHint } from "../ui";
|
|
9
|
-
import { saveConfig } from "../../data/config-store";
|
|
10
9
|
import { getSharedMarketData } from "../../plugins/registry";
|
|
11
10
|
import { colors } from "../../theme/colors";
|
|
12
11
|
import { useThemeColors } from "../../theme/theme-context";
|
|
@@ -129,12 +128,14 @@ import type { PricePoint, TickerFinancials } from "../../types/financials";
|
|
|
129
128
|
import type { TickerRecord } from "../../types/ticker";
|
|
130
129
|
import { TimeAxisLabel } from "./time-axis-label";
|
|
131
130
|
import { PriceAxisLabels } from "./price-axis-labels";
|
|
131
|
+
import { getChartMarketSessionKey, resolveChartMarketSession } from "./market-session";
|
|
132
132
|
|
|
133
133
|
const MODE_CHIPS: Record<ChartRenderMode, string> = {
|
|
134
134
|
area: "A",
|
|
135
135
|
line: "L",
|
|
136
136
|
candles: "C",
|
|
137
137
|
ohlc: "O",
|
|
138
|
+
hlc: "H",
|
|
138
139
|
};
|
|
139
140
|
|
|
140
141
|
const MODE_LABELS: Record<ChartRenderMode, string> = {
|
|
@@ -142,6 +143,7 @@ const MODE_LABELS: Record<ChartRenderMode, string> = {
|
|
|
142
143
|
line: "LINE",
|
|
143
144
|
candles: "CANDLES",
|
|
144
145
|
ohlc: "OHLC",
|
|
146
|
+
hlc: "HLC",
|
|
145
147
|
};
|
|
146
148
|
|
|
147
149
|
const AXIS_MEASURE_PALETTE = resolveChartPalette({
|
|
@@ -1085,6 +1087,7 @@ function buildNativeBitmapKey(
|
|
|
1085
1087
|
showVolume: boolean,
|
|
1086
1088
|
paletteId: string,
|
|
1087
1089
|
indicatorKey: string,
|
|
1090
|
+
marketSessionKey: string,
|
|
1088
1091
|
): string {
|
|
1089
1092
|
const fingerprint = points
|
|
1090
1093
|
.map((point) => {
|
|
@@ -1092,7 +1095,7 @@ function buildNativeBitmapKey(
|
|
|
1092
1095
|
return `${date}:${point.open}:${point.high}:${point.low}:${point.close}:${point.volume ?? 0}`;
|
|
1093
1096
|
})
|
|
1094
1097
|
.join("|");
|
|
1095
|
-
return [pointCount, pixelWidth, pixelHeight, mode, showVolume ? "1" : "0", paletteId, indicatorKey, fingerprint].join("::");
|
|
1098
|
+
return [pointCount, pixelWidth, pixelHeight, mode, showVolume ? "1" : "0", paletteId, indicatorKey, marketSessionKey, fingerprint].join("::");
|
|
1096
1099
|
}
|
|
1097
1100
|
|
|
1098
1101
|
function buildNativeCrosshairBitmapKey(
|
|
@@ -1151,9 +1154,18 @@ function resolveSelectionCursor(
|
|
|
1151
1154
|
};
|
|
1152
1155
|
}
|
|
1153
1156
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
+
function getIndicatorColor(index: number): string {
|
|
1158
|
+
const indicatorColors = [
|
|
1159
|
+
colors.negative,
|
|
1160
|
+
colors.positive,
|
|
1161
|
+
colors.borderFocused,
|
|
1162
|
+
colors.warning,
|
|
1163
|
+
colors.textBright,
|
|
1164
|
+
colors.neutral,
|
|
1165
|
+
colors.textMuted,
|
|
1166
|
+
];
|
|
1167
|
+
return indicatorColors[((index % indicatorColors.length) + indicatorColors.length) % indicatorColors.length]!;
|
|
1168
|
+
}
|
|
1157
1169
|
|
|
1158
1170
|
function getIndicatorWarmupPeriod(config: IndicatorConfig): number {
|
|
1159
1171
|
const periods = [
|
|
@@ -1269,7 +1281,7 @@ function computeIndicatorOverlays(
|
|
|
1269
1281
|
config: IndicatorConfig,
|
|
1270
1282
|
): ChartIndicatorOverlays {
|
|
1271
1283
|
let colorIdx = 0;
|
|
1272
|
-
const nextColor = () =>
|
|
1284
|
+
const nextColor = () => getIndicatorColor(colorIdx++);
|
|
1273
1285
|
|
|
1274
1286
|
const smaLines = (config.sma ?? []).map((period) => ({
|
|
1275
1287
|
period,
|
|
@@ -1439,7 +1451,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
1439
1451
|
}: ResolvedStockChartProps) {
|
|
1440
1452
|
const themeColors = useThemeColors();
|
|
1441
1453
|
const renderer = useNativeRenderer();
|
|
1442
|
-
const { canvasCharts, cellWidthPx = 8, cellHeightPx = 18, pixelRatio = 1 } = useUiCapabilities();
|
|
1454
|
+
const { canvasCharts, cellWidthPx = 8, cellHeightPx = 18, pixelRatio = 1, fractionalViewport = false } = useUiCapabilities();
|
|
1443
1455
|
const dispatch = useAppDispatch();
|
|
1444
1456
|
const config = useAppSelector((state) => state.config);
|
|
1445
1457
|
const paneId = usePaneInstanceId();
|
|
@@ -1449,6 +1461,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
1449
1461
|
const preferredRenderer = config.chartPreferences.renderer;
|
|
1450
1462
|
const [storedRangePreset] = usePaneSettingValue("chartRangePreset", DEFAULT_TICKER_CHART_RANGE_PRESET);
|
|
1451
1463
|
const [storedResolution] = usePaneSettingValue<ChartResolution>("chartResolution", DEFAULT_TICKER_CHART_RESOLUTION);
|
|
1464
|
+
const [storedRenderMode, setStoredRenderMode] = usePaneSettingValue<ChartRenderMode>("chartRenderMode", defaultRenderMode);
|
|
1452
1465
|
const persistChartControls = usePersistChartControlSelection("chartRangePreset");
|
|
1453
1466
|
const [viewState, setViewState] = useState<StockChartViewportState>({
|
|
1454
1467
|
presetRange: compact ? "1Y" : storedRangePreset,
|
|
@@ -1458,7 +1471,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
1458
1471
|
zoomLevel: 1,
|
|
1459
1472
|
cursorX: null,
|
|
1460
1473
|
cursorY: null,
|
|
1461
|
-
renderMode:
|
|
1474
|
+
renderMode: storedRenderMode,
|
|
1462
1475
|
});
|
|
1463
1476
|
// Read indicator config from the chart caller, falling back to legacy pane settings.
|
|
1464
1477
|
const indicatorConfig: IndicatorConfig = indicatorConfigOverride ?? ((pane?.settings?.indicators as IndicatorConfig) ?? EMPTY_INDICATOR_CONFIG);
|
|
@@ -1683,6 +1696,12 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
1683
1696
|
});
|
|
1684
1697
|
updateDisplayCursorTarget(EMPTY_DISPLAY_CURSOR, "discrete");
|
|
1685
1698
|
}, [compact, selectionSupportMap, storedRangePreset, storedResolution]);
|
|
1699
|
+
useEffect(() => {
|
|
1700
|
+
if (compact) return;
|
|
1701
|
+
setViewState((current) => (
|
|
1702
|
+
current.renderMode === storedRenderMode ? current : { ...current, renderMode: storedRenderMode }
|
|
1703
|
+
));
|
|
1704
|
+
}, [compact, storedRenderMode]);
|
|
1686
1705
|
const effectiveResolution: ChartResolution = !compact
|
|
1687
1706
|
&& requestedResolution !== "auto"
|
|
1688
1707
|
&& resolutionSupport !== null
|
|
@@ -1698,7 +1717,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
1698
1717
|
!compact && instrumentRef
|
|
1699
1718
|
? {
|
|
1700
1719
|
instrument: instrumentRef,
|
|
1701
|
-
bufferRange:
|
|
1720
|
+
bufferRange: viewState.bufferRange,
|
|
1702
1721
|
granularity: "range",
|
|
1703
1722
|
}
|
|
1704
1723
|
: null,
|
|
@@ -1724,9 +1743,9 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
1724
1743
|
const axisSectionWidthBudget = compact
|
|
1725
1744
|
? axisMode === "percent" ? 11 : 8
|
|
1726
1745
|
: axisMode === "percent" ? 11 : 10;
|
|
1727
|
-
const axisRightPadding = 1;
|
|
1746
|
+
const axisRightPadding = compact || fractionalViewport ? 0 : 1;
|
|
1728
1747
|
const minimumAxisWidth = axisMode === "percent" ? 5 : 4;
|
|
1729
|
-
const axisGap = axisSectionWidthBudget > 0 ? 1 : 0;
|
|
1748
|
+
const axisGap = axisSectionWidthBudget > 0 ? fractionalViewport ? 0.5 : 1 : 0;
|
|
1730
1749
|
const minChartWidth = compact ? 12 : 20;
|
|
1731
1750
|
const measurementChartWidth = Math.max(width - axisSectionWidthBudget - axisGap, minChartWidth);
|
|
1732
1751
|
const headerRows = compact ? 0 : 2;
|
|
@@ -1734,7 +1753,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
1734
1753
|
const timeAxisRow = 1;
|
|
1735
1754
|
// Native/canvas surfaces can cover the last physical row after pixel rounding;
|
|
1736
1755
|
// keep the text x-axis one row clear of the pane footer.
|
|
1737
|
-
const nativeSurfaceGuardRow = !compact && (useCanvasChart || effectiveRenderer === "kitty") ? 1 : 0;
|
|
1756
|
+
const nativeSurfaceGuardRow = !compact && fractionalViewport && (useCanvasChart || effectiveRenderer === "kitty") ? 1 : 0;
|
|
1738
1757
|
const volumeHeight = showVolume && !compact ? 3 : 0;
|
|
1739
1758
|
const chartHeight = Math.max(height - headerRows - helpRow - timeAxisRow - nativeSurfaceGuardRow, 4);
|
|
1740
1759
|
const chartCurrency = currencyOverride ?? financials?.quote?.currency ?? ticker?.metadata.currency ?? "USD";
|
|
@@ -2360,7 +2379,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
2360
2379
|
colors: AXIS_MEASURE_PALETTE,
|
|
2361
2380
|
timeAxisDates: measuredTimeAxisDates,
|
|
2362
2381
|
});
|
|
2363
|
-
const cursorSamples = measuredScene
|
|
2382
|
+
const cursorSamples = !compact && measuredScene
|
|
2364
2383
|
? [
|
|
2365
2384
|
formatCursorAxisValue(
|
|
2366
2385
|
measuredScene.min,
|
|
@@ -2401,6 +2420,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
2401
2420
|
chartHeight,
|
|
2402
2421
|
compact,
|
|
2403
2422
|
displayedDateWindow,
|
|
2423
|
+
fractionalViewport,
|
|
2404
2424
|
history,
|
|
2405
2425
|
historyOverride,
|
|
2406
2426
|
minChartWidth,
|
|
@@ -2545,18 +2565,11 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
2545
2565
|
};
|
|
2546
2566
|
}, [preferredRenderer, renderer]);
|
|
2547
2567
|
|
|
2548
|
-
const
|
|
2549
|
-
if (nextMode
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
...config.chartPreferences,
|
|
2554
|
-
defaultRenderMode: nextMode,
|
|
2555
|
-
},
|
|
2556
|
-
};
|
|
2557
|
-
dispatch({ type: "SET_CONFIG", config: nextConfig });
|
|
2558
|
-
saveConfig(nextConfig).catch(() => {});
|
|
2559
|
-
};
|
|
2568
|
+
const persistRenderMode = useCallback((nextMode: ChartRenderMode) => {
|
|
2569
|
+
if (!compact && nextMode !== storedRenderMode) {
|
|
2570
|
+
setStoredRenderMode(nextMode);
|
|
2571
|
+
}
|
|
2572
|
+
}, [compact, setStoredRenderMode, storedRenderMode]);
|
|
2560
2573
|
|
|
2561
2574
|
const expandBufferRange = (action: PendingExpansionAction): boolean => {
|
|
2562
2575
|
if (compact) return false;
|
|
@@ -2658,7 +2671,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
2658
2671
|
};
|
|
2659
2672
|
|
|
2660
2673
|
const setRenderMode = (mode: ChartRenderMode) => {
|
|
2661
|
-
|
|
2674
|
+
persistRenderMode(mode);
|
|
2662
2675
|
setViewState((current) => ({ ...current, renderMode: mode }));
|
|
2663
2676
|
};
|
|
2664
2677
|
|
|
@@ -2872,7 +2885,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
2872
2885
|
const activeMode = current.renderMode ?? "area";
|
|
2873
2886
|
const index = CHART_RENDER_MODES.indexOf(activeMode);
|
|
2874
2887
|
const nextMode = CHART_RENDER_MODES[(index + 1) % CHART_RENDER_MODES.length]!;
|
|
2875
|
-
|
|
2888
|
+
persistRenderMode(nextMode);
|
|
2876
2889
|
return { ...current, renderMode: nextMode };
|
|
2877
2890
|
});
|
|
2878
2891
|
return;
|
|
@@ -3033,6 +3046,14 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3033
3046
|
themeColors.text,
|
|
3034
3047
|
themeColors.textDim,
|
|
3035
3048
|
]);
|
|
3049
|
+
const marketSession = useMemo(() => resolveChartMarketSession(ticker
|
|
3050
|
+
? [{
|
|
3051
|
+
exchange: ticker.metadata.exchange,
|
|
3052
|
+
currency: ticker.metadata.currency,
|
|
3053
|
+
assetCategory: ticker.metadata.assetCategory,
|
|
3054
|
+
}]
|
|
3055
|
+
: []), [ticker]);
|
|
3056
|
+
const marketSessionKey = useMemo(() => getChartMarketSessionKey(marketSession), [marketSession]);
|
|
3036
3057
|
|
|
3037
3058
|
const cursorX = viewState.cursorX !== null ? clamp(viewState.cursorX, 0, chartWidth - 1) : null;
|
|
3038
3059
|
const cursorY = viewState.cursorY !== null ? clamp(viewState.cursorY, 0, chartHeight - 1) : null;
|
|
@@ -3060,7 +3081,8 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3060
3081
|
axisMode,
|
|
3061
3082
|
colors: chartColors,
|
|
3062
3083
|
timeAxisDates,
|
|
3063
|
-
|
|
3084
|
+
marketSession,
|
|
3085
|
+
}), [axisMode, chartColors, chartHeight, chartWidth, compact, marketSession, projection.effectiveMode, projection.points, selectionCursorX, selectionCursorY, showVolume, timeAxisDates, volumeHeight]);
|
|
3064
3086
|
const displayScene = useMemo(() => buildChartScene(projection.points, {
|
|
3065
3087
|
width: chartWidth,
|
|
3066
3088
|
height: chartHeight,
|
|
@@ -3073,7 +3095,8 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3073
3095
|
colors: chartColors,
|
|
3074
3096
|
timeAxisDates,
|
|
3075
3097
|
indicators,
|
|
3076
|
-
|
|
3098
|
+
marketSession,
|
|
3099
|
+
}), [axisMode, chartColors, chartHeight, chartWidth, compact, displayCursorX, displayCursorY, indicators, marketSession, projection.effectiveMode, projection.points, showVolume, timeAxisDates, volumeHeight]);
|
|
3077
3100
|
const snappedSelectionCursorX = selectionScene
|
|
3078
3101
|
? getPointTerminalColumn(selectionScene.activeIdx, projection.points.length, chartWidth, projection.effectiveMode)
|
|
3079
3102
|
: null;
|
|
@@ -3119,7 +3142,8 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3119
3142
|
colors: chartColors,
|
|
3120
3143
|
timeAxisDates,
|
|
3121
3144
|
indicators,
|
|
3122
|
-
|
|
3145
|
+
marketSession,
|
|
3146
|
+
}), [axisMode, chartColors, chartHeight, chartWidth, compact, indicators, marketSession, projection.effectiveMode, projection.points, showVolume, timeAxisDates, volumeHeight]);
|
|
3123
3147
|
|
|
3124
3148
|
const staticResult = useMemo(() => renderChart(projection.points, {
|
|
3125
3149
|
width: chartWidth,
|
|
@@ -3135,7 +3159,8 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3135
3159
|
colors: chartColors,
|
|
3136
3160
|
timeAxisDates,
|
|
3137
3161
|
indicators,
|
|
3138
|
-
|
|
3162
|
+
marketSession,
|
|
3163
|
+
}), [axisMode, chartAssetCategory, chartColors, chartCurrency, chartHeight, chartWidth, compact, indicators, marketSession, projection.effectiveMode, projection.points, showVolume, timeAxisDates, volumeHeight]);
|
|
3139
3164
|
|
|
3140
3165
|
const interactiveResult = useMemo(() => (
|
|
3141
3166
|
effectiveRenderer === "kitty" || useCanvasChart
|
|
@@ -3154,8 +3179,9 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3154
3179
|
colors: chartColors,
|
|
3155
3180
|
timeAxisDates,
|
|
3156
3181
|
indicators,
|
|
3182
|
+
marketSession,
|
|
3157
3183
|
})
|
|
3158
|
-
), [axisMode, chartAssetCategory, chartColors, chartCurrency, chartHeight, chartWidth, compact, displayCursorX, displayCursorY, effectiveRenderer, indicators, projection.effectiveMode, projection.points, showVolume, timeAxisDates, useCanvasChart, volumeHeight]);
|
|
3184
|
+
), [axisMode, chartAssetCategory, chartColors, chartCurrency, chartHeight, chartWidth, compact, displayCursorX, displayCursorY, effectiveRenderer, indicators, marketSession, projection.effectiveMode, projection.points, showVolume, timeAxisDates, useCanvasChart, volumeHeight]);
|
|
3159
3185
|
|
|
3160
3186
|
const result = effectiveRenderer === "kitty" || useCanvasChart ? staticResult : interactiveResult!;
|
|
3161
3187
|
|
|
@@ -3289,8 +3315,11 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3289
3315
|
chartColors.volumeDown,
|
|
3290
3316
|
chartColors.candleUp,
|
|
3291
3317
|
chartColors.candleDown,
|
|
3318
|
+
chartColors.preMarketBgColor,
|
|
3319
|
+
chartColors.postMarketBgColor,
|
|
3292
3320
|
].join(","),
|
|
3293
3321
|
indicatorRenderKey,
|
|
3322
|
+
marketSessionKey,
|
|
3294
3323
|
);
|
|
3295
3324
|
const cachedBitmap = lastNativeBaseBitmapRef.current?.key === bitmapKey
|
|
3296
3325
|
? lastNativeBaseBitmapRef.current.bitmap
|
|
@@ -3320,11 +3349,14 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3320
3349
|
chartColors.fillColor,
|
|
3321
3350
|
chartColors.gridColor,
|
|
3322
3351
|
chartColors.lineColor,
|
|
3352
|
+
chartColors.postMarketBgColor,
|
|
3353
|
+
chartColors.preMarketBgColor,
|
|
3323
3354
|
chartColors.volumeDown,
|
|
3324
3355
|
chartColors.volumeUp,
|
|
3325
3356
|
compact,
|
|
3326
3357
|
effectiveRenderer,
|
|
3327
3358
|
indicatorRenderKey,
|
|
3359
|
+
marketSessionKey,
|
|
3328
3360
|
nativeBaseScene,
|
|
3329
3361
|
nativeBaseSurfaceId,
|
|
3330
3362
|
nativeSurfaceManager,
|
|
@@ -3412,7 +3444,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3412
3444
|
|
|
3413
3445
|
const hasHistory = chartWindow.points.length > 0;
|
|
3414
3446
|
const requestedMode = projection.requestedMode;
|
|
3415
|
-
const showOhlcSummary = projection.effectiveMode === "candles" || projection.effectiveMode === "ohlc";
|
|
3447
|
+
const showOhlcSummary = projection.effectiveMode === "candles" || projection.effectiveMode === "ohlc" || projection.effectiveMode === "hlc";
|
|
3416
3448
|
const hasDisplayCursor = displayCursorX !== null && displayCursorY !== null;
|
|
3417
3449
|
const activePoint = showOhlcSummary ? (selectionScene?.activePoint ?? null) : null;
|
|
3418
3450
|
const visiblePriceRange = selectionScene
|
|
@@ -3463,7 +3495,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3463
3495
|
const activeMode = current.renderMode ?? "area";
|
|
3464
3496
|
const index = CHART_RENDER_MODES.indexOf(activeMode);
|
|
3465
3497
|
const nextMode = CHART_RENDER_MODES[(index + 1) % CHART_RENDER_MODES.length]!;
|
|
3466
|
-
|
|
3498
|
+
persistRenderMode(nextMode);
|
|
3467
3499
|
return { ...current, renderMode: nextMode };
|
|
3468
3500
|
});
|
|
3469
3501
|
};
|
|
@@ -3535,6 +3567,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3535
3567
|
footerHints,
|
|
3536
3568
|
history,
|
|
3537
3569
|
navigableDateWindow,
|
|
3570
|
+
persistRenderMode,
|
|
3538
3571
|
resolutionChips,
|
|
3539
3572
|
selectedResolution,
|
|
3540
3573
|
selectionSupportMap,
|
|
@@ -3552,26 +3585,33 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3552
3585
|
priceRange: visiblePriceRange,
|
|
3553
3586
|
});
|
|
3554
3587
|
|
|
3588
|
+
const parts: PaneFooterSegment["parts"] = [
|
|
3589
|
+
...(projection.effectiveMode === "hlc"
|
|
3590
|
+
? []
|
|
3591
|
+
: [
|
|
3592
|
+
{ text: "O", tone: "label" as const },
|
|
3593
|
+
{ text: formatPrice(activePoint.open), tone: "value" as const },
|
|
3594
|
+
]),
|
|
3595
|
+
{ text: "H", tone: "label" },
|
|
3596
|
+
{ text: formatPrice(activePoint.high), tone: "value" },
|
|
3597
|
+
{ text: "L", tone: "label" },
|
|
3598
|
+
{ text: formatPrice(activePoint.low), tone: "value" },
|
|
3599
|
+
{ text: "C", tone: "label" },
|
|
3600
|
+
{ text: formatPrice(activePoint.close), tone: "value" },
|
|
3601
|
+
{ text: "V", tone: "label" },
|
|
3602
|
+
{ text: formatCompact(activePoint.volume), tone: "value" },
|
|
3603
|
+
];
|
|
3604
|
+
|
|
3555
3605
|
return [{
|
|
3556
|
-
id: "ohlc",
|
|
3557
|
-
parts
|
|
3558
|
-
{ text: "O", tone: "label" },
|
|
3559
|
-
{ text: formatPrice(activePoint.open), tone: "value" },
|
|
3560
|
-
{ text: "H", tone: "label" },
|
|
3561
|
-
{ text: formatPrice(activePoint.high), tone: "value" },
|
|
3562
|
-
{ text: "L", tone: "label" },
|
|
3563
|
-
{ text: formatPrice(activePoint.low), tone: "value" },
|
|
3564
|
-
{ text: "C", tone: "label" },
|
|
3565
|
-
{ text: formatPrice(activePoint.close), tone: "value" },
|
|
3566
|
-
{ text: "V", tone: "label" },
|
|
3567
|
-
{ text: formatCompact(activePoint.volume), tone: "value" },
|
|
3568
|
-
],
|
|
3606
|
+
id: projection.effectiveMode === "hlc" ? "hlc" : "ohlc",
|
|
3607
|
+
parts,
|
|
3569
3608
|
}];
|
|
3570
3609
|
}, [
|
|
3571
3610
|
activePoint,
|
|
3572
3611
|
chartAssetCategory,
|
|
3573
3612
|
chartCurrency,
|
|
3574
3613
|
compact,
|
|
3614
|
+
projection.effectiveMode,
|
|
3575
3615
|
showOhlcSummary,
|
|
3576
3616
|
visiblePriceRange,
|
|
3577
3617
|
]);
|
|
@@ -3810,9 +3850,10 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3810
3850
|
colors: chartColors,
|
|
3811
3851
|
timeAxisDates,
|
|
3812
3852
|
indicators: canvasIndicators,
|
|
3853
|
+
marketSession,
|
|
3813
3854
|
})
|
|
3814
3855
|
: null
|
|
3815
|
-
), [axisMode, canvasIndicators, canvasProjection, chartColors, chartHeight, compact, showVolume, timeAxisDates, volumeHeight]);
|
|
3856
|
+
), [axisMode, canvasIndicators, canvasProjection, chartColors, chartHeight, compact, marketSession, showVolume, timeAxisDates, volumeHeight]);
|
|
3816
3857
|
|
|
3817
3858
|
const canvasBaseBitmapKey = useMemo(() => {
|
|
3818
3859
|
if (!canvasBitmapSize || !canvasProjection || !hasHistory || isBlockingBody || bodyMessage) return null;
|
|
@@ -3831,8 +3872,11 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3831
3872
|
chartColors.volumeDown,
|
|
3832
3873
|
chartColors.candleUp,
|
|
3833
3874
|
chartColors.candleDown,
|
|
3875
|
+
chartColors.preMarketBgColor,
|
|
3876
|
+
chartColors.postMarketBgColor,
|
|
3834
3877
|
].join(","),
|
|
3835
3878
|
canvasIndicatorRenderKey,
|
|
3879
|
+
marketSessionKey,
|
|
3836
3880
|
);
|
|
3837
3881
|
}, [
|
|
3838
3882
|
bodyMessage,
|
|
@@ -3844,11 +3888,14 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3844
3888
|
chartColors.fillColor,
|
|
3845
3889
|
chartColors.gridColor,
|
|
3846
3890
|
chartColors.lineColor,
|
|
3891
|
+
chartColors.postMarketBgColor,
|
|
3892
|
+
chartColors.preMarketBgColor,
|
|
3847
3893
|
chartColors.volumeDown,
|
|
3848
3894
|
chartColors.volumeUp,
|
|
3849
3895
|
compact,
|
|
3850
3896
|
hasHistory,
|
|
3851
3897
|
isBlockingBody,
|
|
3898
|
+
marketSessionKey,
|
|
3852
3899
|
showVolume,
|
|
3853
3900
|
]);
|
|
3854
3901
|
|
|
@@ -4062,7 +4109,6 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
4062
4109
|
</Box>
|
|
4063
4110
|
) : (
|
|
4064
4111
|
<Box flexDirection="row" gap={1}>
|
|
4065
|
-
<Text fg={colors.textDim}>mode:{MODE_LABELS[requestedMode]}</Text>
|
|
4066
4112
|
{projection.fallbackMode && (
|
|
4067
4113
|
<Text fg={colors.textDim}>auto:{MODE_LABELS[projection.fallbackMode]}</Text>
|
|
4068
4114
|
)}
|
|
@@ -23,6 +23,51 @@ function clamp(value: number, min: number, max: number): number {
|
|
|
23
23
|
return Math.max(min, Math.min(max, value));
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function normalizeDate(value: Date | string | number): Date | null {
|
|
27
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
28
|
+
return Number.isFinite(date.getTime()) ? date : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function resolveCursorDateFromAxis({
|
|
32
|
+
dates,
|
|
33
|
+
width,
|
|
34
|
+
cursorColumn,
|
|
35
|
+
cursorPixelX,
|
|
36
|
+
cellWidthPx,
|
|
37
|
+
}: {
|
|
38
|
+
dates: Array<Date | string | number>;
|
|
39
|
+
width: number;
|
|
40
|
+
cursorColumn: number | null;
|
|
41
|
+
cursorPixelX?: number | null;
|
|
42
|
+
cellWidthPx?: number | null;
|
|
43
|
+
}): Date | null {
|
|
44
|
+
const normalizedDates = dates
|
|
45
|
+
.map(normalizeDate)
|
|
46
|
+
.filter((date): date is Date => date !== null);
|
|
47
|
+
if (normalizedDates.length === 0) return null;
|
|
48
|
+
if (normalizedDates.length === 1) return normalizedDates[0]!;
|
|
49
|
+
|
|
50
|
+
let ratio: number | null = null;
|
|
51
|
+
if (
|
|
52
|
+
cursorPixelX !== null
|
|
53
|
+
&& cursorPixelX !== undefined
|
|
54
|
+
&& Number.isFinite(cursorPixelX)
|
|
55
|
+
&& cellWidthPx !== null
|
|
56
|
+
&& cellWidthPx !== undefined
|
|
57
|
+
&& Number.isFinite(cellWidthPx)
|
|
58
|
+
&& cellWidthPx > 0
|
|
59
|
+
) {
|
|
60
|
+
const pixelWidth = Math.max(width * cellWidthPx, 1);
|
|
61
|
+
ratio = clamp(cursorPixelX / Math.max(pixelWidth - 1, 1), 0, 1);
|
|
62
|
+
} else if (cursorColumn !== null && Number.isFinite(cursorColumn) && width > 1) {
|
|
63
|
+
ratio = clamp(cursorColumn / Math.max(width - 1, 1), 0, 1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (ratio === null) return null;
|
|
67
|
+
const index = Math.round(ratio * (normalizedDates.length - 1));
|
|
68
|
+
return normalizedDates[clamp(index, 0, normalizedDates.length - 1)] ?? null;
|
|
69
|
+
}
|
|
70
|
+
|
|
26
71
|
export function buildCursorTimeAxisOverlay({
|
|
27
72
|
segments,
|
|
28
73
|
width,
|
|
@@ -61,13 +106,22 @@ export function TimeAxisLabel({
|
|
|
61
106
|
cursorColor,
|
|
62
107
|
}: TimeAxisLabelProps) {
|
|
63
108
|
const { cellWidthPx = 8, fractionalViewport = false } = useUiCapabilities();
|
|
109
|
+
const resolvedCursorDate = useMemo(() => {
|
|
110
|
+
return resolveCursorDateFromAxis({
|
|
111
|
+
dates,
|
|
112
|
+
width,
|
|
113
|
+
cursorColumn,
|
|
114
|
+
cursorPixelX: fractionalViewport ? cursorPixelX : null,
|
|
115
|
+
cellWidthPx,
|
|
116
|
+
}) ?? cursorDate;
|
|
117
|
+
}, [cellWidthPx, cursorColumn, cursorDate, cursorPixelX, dates, fractionalViewport, width]);
|
|
64
118
|
const segments = useMemo(() => buildCursorTimeAxisSegments({
|
|
65
119
|
timeLabels,
|
|
66
120
|
width,
|
|
67
121
|
cursorColumn,
|
|
68
|
-
cursorDate,
|
|
122
|
+
cursorDate: resolvedCursorDate,
|
|
69
123
|
dates,
|
|
70
|
-
}), [cursorColumn,
|
|
124
|
+
}), [cursorColumn, dates, resolvedCursorDate, timeLabels, width]);
|
|
71
125
|
const overlay = useMemo(() => buildCursorTimeAxisOverlay({
|
|
72
126
|
segments,
|
|
73
127
|
width,
|