gloomberb 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/app.tsx +168 -65
- package/src/cli/commands/ticker.ts +45 -15
- package/src/cli/helpers.ts +4 -2
- package/src/cli/registry.test.ts +42 -35
- package/src/cli.test.ts +4 -2
- package/src/components/chart/chart-control-hint.tsx +39 -0
- package/src/components/chart/chart-controller.test.ts +110 -0
- package/src/components/chart/chart-controller.ts +364 -2
- package/src/components/chart/chart-data.ts +177 -29
- package/src/components/chart/chart-indicator-selector.tsx +53 -0
- package/src/components/chart/chart-pane-settings.ts +20 -0
- package/src/components/chart/chart-renderer.ts +220 -30
- package/src/components/chart/chart-resolution.test.ts +67 -0
- package/src/components/chart/chart-resolution.ts +74 -4
- package/src/components/chart/chart-scroll.test.ts +69 -0
- package/src/components/chart/chart-scroll.ts +104 -0
- package/src/components/chart/chart-types.ts +12 -0
- package/src/components/chart/chart-viewport.test.ts +5 -1
- package/src/components/chart/chart-viewport.ts +1 -1
- package/src/components/chart/chart.test.ts +249 -10
- package/src/components/chart/comparison-chart-renderer.test.ts +71 -1
- package/src/components/chart/comparison-chart-renderer.ts +40 -3
- package/src/components/chart/comparison-stock-chart.tsx +218 -123
- package/src/components/chart/indicators/bands.test.ts +50 -0
- package/src/components/chart/indicators/bands.ts +29 -0
- package/src/components/chart/indicators/moving-averages.test.ts +54 -0
- package/src/components/chart/indicators/moving-averages.ts +42 -0
- package/src/components/chart/indicators/options.test.ts +32 -0
- package/src/components/chart/indicators/options.ts +107 -0
- package/src/components/chart/indicators/oscillators.test.ts +79 -0
- package/src/components/chart/indicators/oscillators.ts +92 -0
- package/src/components/chart/indicators/types.ts +36 -0
- package/src/components/chart/native/chart-rasterizer.ts +51 -0
- package/src/components/chart/native/kitty.test.ts +68 -0
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +740 -9
- package/src/components/chart/stock-chart.test.ts +239 -0
- package/src/components/chart/stock-chart.tsx +2257 -372
- package/src/components/command-bar/command-bar.test.tsx +337 -5
- package/src/components/command-bar/command-bar.tsx +292 -120
- package/src/components/command-bar/helpers.ts +1 -1
- package/src/components/command-bar/root-shortcuts.ts +33 -4
- package/src/components/command-bar/view-model.test.ts +3 -3
- package/src/components/command-bar/view-model.ts +4 -4
- package/src/components/command-bar/workflow-ops.ts +10 -0
- package/src/components/layout/header.tsx +2 -1
- package/src/components/layout/shell.test.tsx +186 -27
- package/src/components/layout/shell.tsx +5 -4
- package/src/components/layout/status-bar.test.tsx +16 -7
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-editor.tsx +1 -0
- package/src/components/pane-settings-dialog.tsx +13 -152
- package/src/components/pane-template-wizard.tsx +1 -0
- package/src/components/price-selector-dialog.tsx +12 -7
- package/src/components/ticker-badge-text.test.tsx +50 -0
- package/src/components/ticker-badge-text.tsx +17 -2
- package/src/components/ticker-badge.tsx +2 -2
- package/src/components/ticker-list-table.tsx +54 -1
- package/src/components/toggle-list.tsx +15 -2
- package/src/components/ui/data-table.tsx +60 -3
- package/src/components/ui/external-link.tsx +48 -11
- package/src/components/ui/fields.tsx +2 -1
- package/src/components/ui/index.ts +8 -1
- package/src/components/ui/multi-select-chips.tsx +69 -0
- package/src/components/ui/multi-select-dialog.tsx +267 -0
- package/src/components/ui/multi-select.ts +78 -0
- package/src/components/ui/ui.test.tsx +343 -1
- package/src/data/plugin-state-store.ts +30 -19
- package/src/data/resource-store.ts +133 -103
- package/src/data/session-store.ts +19 -12
- package/src/data/sqlite-cache.test.ts +37 -2
- package/src/data/sqlite-database.ts +8 -2
- package/src/data/sqlite-retry.test.ts +63 -0
- package/src/data/sqlite-retry.ts +66 -0
- package/src/data/ticker-store.ts +29 -18
- package/src/index.tsx +6 -1
- package/src/market-data/coordinator.test.ts +114 -5
- package/src/market-data/coordinator.ts +70 -1
- package/src/market-data/hooks.test.tsx +73 -2
- package/src/market-data/hooks.tsx +16 -4
- package/src/notifications/app-notifier.test.ts +177 -0
- package/src/notifications/app-notifier.ts +241 -0
- package/src/pane-settings.test.ts +2 -0
- package/src/plugins/builtin/ai/index.test.tsx +1 -1
- package/src/plugins/builtin/ai/screener-pane.test.tsx +2 -0
- package/src/plugins/builtin/ai/screener-pane.tsx +1 -0
- package/src/plugins/builtin/ai/ticker-context.ts +3 -2
- package/src/plugins/builtin/alerts/alert-engine.test.ts +90 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +55 -0
- package/src/plugins/builtin/alerts/index.test.tsx +357 -0
- package/src/plugins/builtin/alerts/index.tsx +519 -0
- package/src/plugins/builtin/alerts/types.ts +14 -0
- package/src/plugins/builtin/analytics/index.test.tsx +300 -0
- package/src/plugins/builtin/analytics/index.tsx +775 -0
- package/src/plugins/builtin/analytics/metrics.test.ts +125 -0
- package/src/plugins/builtin/analytics/metrics.ts +155 -0
- package/src/plugins/builtin/ask-ai.test.tsx +2 -0
- package/src/plugins/builtin/chat-controller.test.ts +62 -18
- package/src/plugins/builtin/chat-controller.ts +23 -12
- package/src/plugins/builtin/chat.test.tsx +715 -4
- package/src/plugins/builtin/chat.tsx +415 -119
- package/src/plugins/builtin/comparison-chart.test.tsx +97 -57
- package/src/plugins/builtin/comparison-chart.tsx +1 -2
- package/src/plugins/builtin/correlation/compute.test.ts +147 -0
- package/src/plugins/builtin/correlation/compute.ts +115 -0
- package/src/plugins/builtin/correlation/index.tsx +385 -0
- package/src/plugins/builtin/correlation/plugin.test.ts +44 -0
- package/src/plugins/builtin/correlation/settings.test.ts +34 -0
- package/src/plugins/builtin/correlation/settings.ts +95 -0
- package/src/plugins/builtin/debug.tsx +36 -50
- package/src/plugins/builtin/earnings/earnings-cache.test.ts +181 -0
- package/src/plugins/builtin/earnings/earnings-cache.ts +136 -0
- package/src/plugins/builtin/earnings/earnings-data.test.ts +59 -0
- package/src/plugins/builtin/earnings/earnings-data.ts +50 -0
- package/src/plugins/builtin/earnings/index.tsx +337 -0
- package/src/plugins/builtin/econ/calendar-source.test.ts +219 -0
- package/src/plugins/builtin/econ/calendar-source.ts +121 -0
- package/src/plugins/builtin/econ/fred-client.test.ts +63 -0
- package/src/plugins/builtin/econ/fred-client.ts +79 -0
- package/src/plugins/builtin/econ/fred-series-map.ts +88 -0
- package/src/plugins/builtin/econ/index.tsx +999 -0
- package/src/plugins/builtin/econ/types.ts +13 -0
- package/src/plugins/builtin/fred-settings.ts +10 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +178 -0
- package/src/plugins/builtin/fx-matrix/pairs.ts +30 -0
- package/src/plugins/builtin/help.test.tsx +1 -5
- package/src/plugins/builtin/help.tsx +1 -16
- package/src/plugins/builtin/insider/index.tsx +417 -0
- package/src/plugins/builtin/insider/insider-data.test.ts +55 -0
- package/src/plugins/builtin/insider/insider-data.ts +49 -0
- package/src/plugins/builtin/layout-manager.tsx +24 -16
- package/src/plugins/builtin/market-movers/index.tsx +573 -0
- package/src/plugins/builtin/market-movers/screener.test.ts +143 -0
- package/src/plugins/builtin/market-movers/screener.ts +105 -0
- package/src/plugins/builtin/options.tsx +4 -3
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +11 -6
- package/src/plugins/builtin/portfolio-list/command-bar.ts +6 -3
- package/src/plugins/builtin/portfolio-list/header.tsx +3 -2
- package/src/plugins/builtin/portfolio-list/metrics.ts +22 -11
- package/src/plugins/builtin/portfolio-list/pane.tsx +3 -9
- package/src/plugins/builtin/portfolio-list/settings.ts +9 -0
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -0
- package/src/plugins/builtin/portfolio-list.test.tsx +3 -1
- package/src/plugins/builtin/portfolio-metrics.test.ts +13 -1
- package/src/plugins/builtin/sec.tsx +114 -7
- package/src/plugins/builtin/sectors/index.tsx +359 -0
- package/src/plugins/builtin/sectors/sector-data.ts +18 -0
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +75 -13
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +316 -80
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +4 -6
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +17 -12
- package/src/plugins/builtin/ticker-detail.test.tsx +3 -3
- package/src/plugins/builtin/world-indices/index.tsx +410 -0
- package/src/plugins/builtin/world-indices/indices.ts +52 -0
- package/src/plugins/builtin/yield-curve/index.tsx +242 -0
- package/src/plugins/builtin/yield-curve/treasury-data.test.ts +212 -0
- package/src/plugins/builtin/yield-curve/treasury-data.ts +107 -0
- package/src/plugins/catalog.ts +22 -0
- package/src/plugins/gridlock-notification.ts +15 -0
- package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
- package/src/plugins/ibkr/index.test.tsx +1 -1
- package/src/plugins/ibkr/index.tsx +1 -1
- package/src/plugins/ibkr/trade-tab.tsx +23 -5
- package/src/plugins/ibkr/trade-utils.test.ts +1 -1
- package/src/plugins/ibkr/trade-utils.ts +7 -6
- package/src/plugins/ibkr/trading-pane.tsx +8 -7
- package/src/plugins/plugin-runtime.test.tsx +2 -0
- package/src/plugins/plugin-runtime.tsx +10 -0
- package/src/plugins/prediction-markets/controller-data.ts +8 -0
- package/src/plugins/prediction-markets/detail/shared.tsx +7 -6
- package/src/plugins/prediction-markets/plugin.test.tsx +48 -1
- package/src/plugins/prediction-markets/services/fetch.ts +13 -7
- package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
- package/src/plugins/prediction-markets/services/polymarket-ws.ts +20 -0
- package/src/plugins/prediction-markets/test-helpers.tsx +2 -0
- package/src/plugins/registry.ts +22 -13
- package/src/sources/provider-router.test.ts +3 -2
- package/src/sources/sec-edgar.ts +13 -1
- package/src/sources/yahoo-finance.ts +75 -1
- package/src/state/app-context-selector.test.tsx +17 -1
- package/src/state/app-context.test.tsx +24 -0
- package/src/state/app-context.tsx +29 -6
- package/src/theme/colors.ts +5 -0
- package/src/theme/themes.ts +25 -0
- package/src/types/config.test.ts +107 -0
- package/src/types/config.ts +30 -1
- package/src/types/data-provider.ts +13 -0
- package/src/types/plugin.ts +35 -1
- package/src/utils/format.test.ts +11 -1
- package/src/utils/format.ts +86 -8
- package/src/utils/inline-content-tokenizer.test.ts +22 -0
- package/src/utils/inline-content-tokenizer.ts +76 -0
- package/src/utils/link-tokenizer.test.ts +27 -0
- package/src/utils/link-tokenizer.ts +93 -0
- package/src/utils/market-format.test.ts +109 -0
- package/src/utils/market-format.ts +299 -0
- package/src/utils/market-status.ts +3 -2
- package/src/utils/selection-clipboard.test.ts +43 -0
- package/src/utils/selection-clipboard.ts +88 -0
- package/src/utils/terminal-input-reset.test.ts +50 -0
- package/src/utils/terminal-input-reset.ts +24 -0
- package/src/utils/throttled-fetch.test.ts +145 -0
- package/src/utils/throttled-fetch.ts +212 -0
- package/src/utils/ticker-tokenizer.test.ts +31 -0
- package/src/utils/ticker-tokenizer.ts +7 -42
- package/src/version.ts +1 -1
|
@@ -19,6 +19,23 @@ export interface ChartProjection {
|
|
|
19
19
|
fallbackMode: ChartRenderMode | null;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export interface ProjectChartDataOptions {
|
|
23
|
+
ohlcBucketWidth?: number;
|
|
24
|
+
ohlcSourceBucketSize?: number;
|
|
25
|
+
ohlcTargetBucketCount?: number;
|
|
26
|
+
sourceIndexOffset?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface StableOhlcProjectionOptionsInput {
|
|
30
|
+
pointCount: number;
|
|
31
|
+
sourceIndexOffset: number;
|
|
32
|
+
bucketWidth: number;
|
|
33
|
+
navigationPointCount?: number;
|
|
34
|
+
countRatioTolerance?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const DEFAULT_OHLC_COUNT_RATIO_TOLERANCE = 1.2;
|
|
38
|
+
|
|
22
39
|
function coerceDate(value: Date | string | number): Date {
|
|
23
40
|
return value instanceof Date ? value : new Date(value);
|
|
24
41
|
}
|
|
@@ -113,6 +130,104 @@ function normalizePoint(point: PricePoint): ProjectedChartPoint {
|
|
|
113
130
|
};
|
|
114
131
|
}
|
|
115
132
|
|
|
133
|
+
function getMinimumPositiveDateGapMs(points: readonly ProjectedChartPoint[]): number | null {
|
|
134
|
+
let minimum = Number.POSITIVE_INFINITY;
|
|
135
|
+
for (let index = 1; index < points.length; index += 1) {
|
|
136
|
+
const previous = points[index - 1]!.date.getTime();
|
|
137
|
+
const current = points[index]!.date.getTime();
|
|
138
|
+
const gap = current - previous;
|
|
139
|
+
if (Number.isFinite(gap) && gap > 0 && gap < minimum) {
|
|
140
|
+
minimum = gap;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return Number.isFinite(minimum) ? minimum : null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function aggregateOhlcBucket(bucket: readonly ProjectedChartPoint[]): ProjectedChartPoint {
|
|
147
|
+
const first = bucket[0]!;
|
|
148
|
+
const last = bucket[bucket.length - 1]!;
|
|
149
|
+
let high = first.high;
|
|
150
|
+
let low = first.low;
|
|
151
|
+
let volume = 0;
|
|
152
|
+
|
|
153
|
+
for (const point of bucket) {
|
|
154
|
+
high = Math.max(high, point.high);
|
|
155
|
+
low = Math.min(low, point.low);
|
|
156
|
+
volume += point.volume;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
date: last.date,
|
|
161
|
+
open: first.open,
|
|
162
|
+
high,
|
|
163
|
+
low,
|
|
164
|
+
close: last.close,
|
|
165
|
+
volume,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function bucketOhlcBySourceIndex(
|
|
170
|
+
normalizedPoints: readonly ProjectedChartPoint[],
|
|
171
|
+
bucketSize: number,
|
|
172
|
+
sourceIndexOffset: number,
|
|
173
|
+
targetBucketCount?: number,
|
|
174
|
+
): ProjectedChartPoint[] {
|
|
175
|
+
const normalizedBucketSize = Math.max(Math.floor(bucketSize), 1);
|
|
176
|
+
const normalizedSourceIndexOffset = Math.max(Math.floor(sourceIndexOffset), 0);
|
|
177
|
+
const maximumBucketCount = targetBucketCount === undefined
|
|
178
|
+
? Number.POSITIVE_INFINITY
|
|
179
|
+
: Math.max(Math.floor(targetBucketCount), 1);
|
|
180
|
+
const result: ProjectedChartPoint[] = [];
|
|
181
|
+
const firstBucketStart = Math.floor(normalizedSourceIndexOffset / normalizedBucketSize) * normalizedBucketSize;
|
|
182
|
+
const endSourceIndex = normalizedSourceIndexOffset + normalizedPoints.length;
|
|
183
|
+
|
|
184
|
+
for (
|
|
185
|
+
let bucketStart = firstBucketStart;
|
|
186
|
+
bucketStart < endSourceIndex && result.length < maximumBucketCount;
|
|
187
|
+
bucketStart += normalizedBucketSize
|
|
188
|
+
) {
|
|
189
|
+
const bucketEnd = bucketStart + normalizedBucketSize;
|
|
190
|
+
const start = Math.max(bucketStart - normalizedSourceIndexOffset, 0);
|
|
191
|
+
const end = Math.min(bucketEnd - normalizedSourceIndexOffset, normalizedPoints.length);
|
|
192
|
+
if (end <= start) continue;
|
|
193
|
+
const bucket = normalizedPoints.slice(start, end);
|
|
194
|
+
if (bucket.length === 0) continue;
|
|
195
|
+
result.push(aggregateOhlcBucket(bucket));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function resolveStableOhlcProjectionOptions({
|
|
202
|
+
pointCount,
|
|
203
|
+
sourceIndexOffset,
|
|
204
|
+
bucketWidth,
|
|
205
|
+
navigationPointCount = 0,
|
|
206
|
+
countRatioTolerance = DEFAULT_OHLC_COUNT_RATIO_TOLERANCE,
|
|
207
|
+
}: StableOhlcProjectionOptionsInput): ProjectChartDataOptions {
|
|
208
|
+
const ohlcBucketWidth = Math.max(Math.floor(bucketWidth), 1);
|
|
209
|
+
const ohlcProjectionWidth = Math.max(Math.floor(ohlcBucketWidth / 2), 1);
|
|
210
|
+
const normalizedPointCount = Math.max(Math.floor(pointCount), 0);
|
|
211
|
+
const normalizedNavigationPointCount = Math.max(Math.floor(navigationPointCount), 0);
|
|
212
|
+
const canUseNavigationCount = normalizedNavigationPointCount > 0
|
|
213
|
+
&& normalizedPointCount > 0
|
|
214
|
+
&& Math.max(normalizedNavigationPointCount, normalizedPointCount)
|
|
215
|
+
/ Math.max(Math.min(normalizedNavigationPointCount, normalizedPointCount), 1)
|
|
216
|
+
<= countRatioTolerance;
|
|
217
|
+
const stableSourceCount = canUseNavigationCount
|
|
218
|
+
? normalizedNavigationPointCount
|
|
219
|
+
: normalizedPointCount;
|
|
220
|
+
const ohlcSourceBucketSize = Math.max(Math.ceil(stableSourceCount / ohlcProjectionWidth), 1);
|
|
221
|
+
const ohlcTargetBucketCount = Math.max(Math.ceil(stableSourceCount / ohlcSourceBucketSize), 1);
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
ohlcBucketWidth,
|
|
225
|
+
ohlcSourceBucketSize,
|
|
226
|
+
ohlcTargetBucketCount,
|
|
227
|
+
sourceIndexOffset,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
116
231
|
/**
|
|
117
232
|
* Downsample close-driven chart data to fit the target width.
|
|
118
233
|
* Uses representative closes so line/area charts preserve the broad shape.
|
|
@@ -156,44 +271,75 @@ export function projectCloseSeries(
|
|
|
156
271
|
|
|
157
272
|
/**
|
|
158
273
|
* Bucket OHLC data so each rendered column preserves open, close, high, low, and volume.
|
|
274
|
+
*
|
|
275
|
+
* The default path aligns buckets by timestamp. Interactive manual panning can pass
|
|
276
|
+
* ohlcSourceBucketSize/ohlcTargetBucketCount to align buckets by source bar index,
|
|
277
|
+
* which keeps the rendered candle count stable as partial edge buckets enter/leave.
|
|
159
278
|
*/
|
|
160
279
|
export function bucketOhlcSeries(
|
|
161
280
|
points: PricePoint[],
|
|
162
281
|
targetWidth: number,
|
|
282
|
+
options: ProjectChartDataOptions = {},
|
|
163
283
|
): ProjectedChartPoint[] {
|
|
164
284
|
if (points.length === 0) return [];
|
|
165
|
-
|
|
285
|
+
const normalizedPoints = points.map(normalizePoint);
|
|
286
|
+
if (points.length <= targetWidth) return normalizedPoints;
|
|
287
|
+
if (options.ohlcSourceBucketSize !== undefined) {
|
|
288
|
+
return bucketOhlcBySourceIndex(
|
|
289
|
+
normalizedPoints,
|
|
290
|
+
options.ohlcSourceBucketSize,
|
|
291
|
+
options.sourceIndexOffset ?? 0,
|
|
292
|
+
options.ohlcTargetBucketCount,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
166
295
|
|
|
167
296
|
const result: ProjectedChartPoint[] = [];
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
let
|
|
181
|
-
let
|
|
297
|
+
const minimumGapMs = getMinimumPositiveDateGapMs(normalizedPoints);
|
|
298
|
+
const firstTime = normalizedPoints[0]!.date.getTime();
|
|
299
|
+
const lastTime = normalizedPoints[normalizedPoints.length - 1]!.date.getTime();
|
|
300
|
+
if (
|
|
301
|
+
minimumGapMs
|
|
302
|
+
&& Number.isFinite(firstTime)
|
|
303
|
+
&& Number.isFinite(lastTime)
|
|
304
|
+
&& lastTime > firstTime
|
|
305
|
+
) {
|
|
306
|
+
const spanMs = Math.max(lastTime - firstTime, minimumGapMs);
|
|
307
|
+
const rawBucketDurationMs = spanMs / Math.max(targetWidth, 1);
|
|
308
|
+
const bucketDurationMs = Math.max(Math.ceil(rawBucketDurationMs / minimumGapMs) * minimumGapMs, minimumGapMs);
|
|
309
|
+
let currentBucketKey: number | null = null;
|
|
310
|
+
let currentBucket: ProjectedChartPoint[] = [];
|
|
311
|
+
|
|
312
|
+
for (const point of normalizedPoints) {
|
|
313
|
+
const time = point.date.getTime();
|
|
314
|
+
const bucketKey = Math.floor(time / bucketDurationMs);
|
|
315
|
+
if (currentBucketKey !== null && bucketKey !== currentBucketKey) {
|
|
316
|
+
result.push(aggregateOhlcBucket(currentBucket));
|
|
317
|
+
currentBucket = [];
|
|
318
|
+
}
|
|
319
|
+
currentBucketKey = bucketKey;
|
|
320
|
+
currentBucket.push(point);
|
|
321
|
+
}
|
|
182
322
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
low = Math.min(low, point.low ?? point.close);
|
|
186
|
-
volume += point.volume ?? 0;
|
|
323
|
+
if (currentBucket.length > 0) {
|
|
324
|
+
result.push(aggregateOhlcBucket(currentBucket));
|
|
187
325
|
}
|
|
188
326
|
|
|
189
|
-
result
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const bucketSize = Math.max(Math.ceil(normalizedPoints.length / Math.max(targetWidth, 1)), 1);
|
|
331
|
+
const sourceIndexOffset = Math.max(Math.floor(options.sourceIndexOffset ?? 0), 0);
|
|
332
|
+
const firstBucketStart = Math.floor(sourceIndexOffset / bucketSize) * bucketSize;
|
|
333
|
+
const endSourceIndex = sourceIndexOffset + normalizedPoints.length;
|
|
334
|
+
|
|
335
|
+
for (let bucketStart = firstBucketStart; bucketStart < endSourceIndex; bucketStart += bucketSize) {
|
|
336
|
+
const bucketEnd = bucketStart + bucketSize;
|
|
337
|
+
const start = Math.max(bucketStart - sourceIndexOffset, 0);
|
|
338
|
+
const end = Math.min(bucketEnd - sourceIndexOffset, normalizedPoints.length);
|
|
339
|
+
if (end <= start) continue;
|
|
340
|
+
const bucket = normalizedPoints.slice(start, end);
|
|
341
|
+
if (bucket.length === 0) continue;
|
|
342
|
+
result.push(aggregateOhlcBucket(bucket));
|
|
197
343
|
}
|
|
198
344
|
|
|
199
345
|
return result;
|
|
@@ -219,14 +365,16 @@ export function projectChartData(
|
|
|
219
365
|
targetWidth: number,
|
|
220
366
|
mode: ChartRenderMode | undefined,
|
|
221
367
|
compact = false,
|
|
368
|
+
options: ProjectChartDataOptions = {},
|
|
222
369
|
): ChartProjection {
|
|
223
370
|
const { requestedMode, effectiveMode, fallbackMode } = resolveRenderMode(mode, targetWidth, compact);
|
|
371
|
+
const ohlcBucketWidth = options.ohlcBucketWidth ?? targetWidth;
|
|
224
372
|
const projectionWidth = effectiveMode === "candles" || effectiveMode === "ohlc"
|
|
225
|
-
? Math.max(Math.floor(
|
|
373
|
+
? Math.max(Math.floor(ohlcBucketWidth / 2), 1)
|
|
226
374
|
: targetWidth;
|
|
227
375
|
|
|
228
376
|
const projectedPoints = effectiveMode === "candles" || effectiveMode === "ohlc"
|
|
229
|
-
? bucketOhlcSeries(points, projectionWidth)
|
|
377
|
+
? bucketOhlcSeries(points, projectionWidth, options)
|
|
230
378
|
: projectCloseSeries(points, projectionWidth);
|
|
231
379
|
|
|
232
380
|
return {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { MultiSelectDialogButton } from "../ui";
|
|
2
|
+
import { ChartControlHint } from "./chart-control-hint";
|
|
3
|
+
import {
|
|
4
|
+
CHART_INDICATOR_OPTIONS,
|
|
5
|
+
normalizeChartIndicatorSelection,
|
|
6
|
+
type ChartIndicatorId,
|
|
7
|
+
} from "./indicators/options";
|
|
8
|
+
|
|
9
|
+
interface ChartIndicatorSelectorProps {
|
|
10
|
+
selectedIds: ChartIndicatorId[];
|
|
11
|
+
onChange: (selectedIds: ChartIndicatorId[]) => void;
|
|
12
|
+
width: number;
|
|
13
|
+
variant?: "button" | "hint";
|
|
14
|
+
shortcutActive?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ChartIndicatorSelector({
|
|
18
|
+
selectedIds,
|
|
19
|
+
onChange,
|
|
20
|
+
width,
|
|
21
|
+
variant = "button",
|
|
22
|
+
shortcutActive = false,
|
|
23
|
+
}: ChartIndicatorSelectorProps) {
|
|
24
|
+
const compact = width < 72;
|
|
25
|
+
const options = CHART_INDICATOR_OPTIONS.map((option) => ({
|
|
26
|
+
value: option.id,
|
|
27
|
+
label: compact ? option.compactLabel : option.label,
|
|
28
|
+
description: option.description,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<MultiSelectDialogButton
|
|
33
|
+
label={compact ? "IND" : "Indicators"}
|
|
34
|
+
title="Chart Indicators"
|
|
35
|
+
options={options}
|
|
36
|
+
selectedValues={selectedIds}
|
|
37
|
+
onChange={(values) => onChange(normalizeChartIndicatorSelection(values))}
|
|
38
|
+
idPrefix="chart-indicators"
|
|
39
|
+
shortcutKey={variant === "hint" ? "i" : undefined}
|
|
40
|
+
shortcutActive={shortcutActive}
|
|
41
|
+
renderTrigger={variant === "hint"
|
|
42
|
+
? ({ disabled, openDialog }) => (
|
|
43
|
+
<ChartControlHint
|
|
44
|
+
hotkey="i"
|
|
45
|
+
label="ndicators"
|
|
46
|
+
disabled={disabled}
|
|
47
|
+
onPress={openDialog}
|
|
48
|
+
/>
|
|
49
|
+
)
|
|
50
|
+
: undefined}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -2,6 +2,7 @@ import { saveConfig } from "../../data/config-store";
|
|
|
2
2
|
import { setPaneSettings } from "../../pane-settings";
|
|
3
3
|
import { useAppState, usePaneInstance, usePaneInstanceId } from "../../state/app-context";
|
|
4
4
|
import type { ChartResolution, TimeRange } from "./chart-types";
|
|
5
|
+
import type { IndicatorConfig } from "./indicators/types";
|
|
5
6
|
|
|
6
7
|
export function usePersistChartControlSelection(rangePresetKey: string): (
|
|
7
8
|
range: TimeRange,
|
|
@@ -25,3 +26,22 @@ export function usePersistChartControlSelection(rangePresetKey: string): (
|
|
|
25
26
|
saveConfig(nextConfig).catch(() => {});
|
|
26
27
|
};
|
|
27
28
|
}
|
|
29
|
+
|
|
30
|
+
export function usePersistIndicatorConfig(): (config: IndicatorConfig) => void {
|
|
31
|
+
const { state, dispatch } = useAppState();
|
|
32
|
+
const paneId = usePaneInstanceId();
|
|
33
|
+
const pane = usePaneInstance();
|
|
34
|
+
|
|
35
|
+
return (indicatorConfig) => {
|
|
36
|
+
const layout = setPaneSettings(state.config.layout, paneId, {
|
|
37
|
+
...(pane?.settings ?? {}),
|
|
38
|
+
indicators: indicatorConfig,
|
|
39
|
+
});
|
|
40
|
+
const layouts = state.config.layouts.map((savedLayout, index) => (
|
|
41
|
+
index === state.config.activeLayoutIndex ? { ...savedLayout, layout } : savedLayout
|
|
42
|
+
));
|
|
43
|
+
const nextConfig = { ...state.config, layout, layouts };
|
|
44
|
+
dispatch({ type: "SET_CONFIG", config: nextConfig });
|
|
45
|
+
saveConfig(nextConfig).catch(() => {});
|
|
46
|
+
};
|
|
47
|
+
}
|