gloomberb 0.18.1 → 0.18.2
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/package.json +1 -1
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +6 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/types.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/market.ts +281 -154
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +100 -50
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +4 -1
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +37 -2
- package/src/cli/pane-functions/catalog.ts +3 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +9 -4
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +27 -3
- package/src/cli/pane-functions/headless.ts +6 -5
- package/src/cli/pane-functions/index.ts +3 -2
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +2 -1
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/series-search.ts +2 -1
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +11 -2
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +58 -8
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/index.ts +2 -0
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/status.tsx +17 -3
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +5 -5
- package/src/i18n/es.ts +9 -3
- package/src/i18n/ja.ts +9 -3
- package/src/i18n/ko.ts +9 -3
- package/src/i18n/zh-cn.ts +10 -4
- package/src/i18n/zh-tw.ts +10 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +182 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +505 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +573 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +3 -2
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +31 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +36 -5
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +26 -12
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +35 -0
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +20 -5
- package/src/plugins/builtin/equity-screener/model.ts +40 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +37 -16
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/insider/base-values.ts +72 -0
- package/src/plugins/builtin/insider/headless.ts +29 -4
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/view.tsx +77 -22
- package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
- package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +62 -0
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +2 -2
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +5 -28
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +39 -7
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +6 -0
- package/src/renderers/dom/styles.css +12 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/index.ts +50 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +2 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +62 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/tickers/listing-fate.ts +27 -8
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +12 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +2 -1
- package/src/utils/currency-units.ts +32 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
|
@@ -4,147 +4,214 @@ import { formatNumber } from "../../../utils/format";
|
|
|
4
4
|
|
|
5
5
|
export const PRIMARY_DEALERS_PANE_ID = "primary-dealers";
|
|
6
6
|
|
|
7
|
-
/** Aggregate totals only. Tenor and CUSIP buckets stay off the wire. */
|
|
8
|
-
const SERIES_CAP = 12;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* The current release names settlement fails PDFTD (deliver) and PDFTR
|
|
12
|
-
* (receive). PDFAIL and PDFAS are included when a break still publishes them.
|
|
13
|
-
*/
|
|
14
|
-
const FAIL_PREFIXES = ["PDFAIL", "PDFAS", "PDFTD", "PDFTR"] as const;
|
|
15
|
-
|
|
16
7
|
export type DealerTab = "positions" | "fails";
|
|
17
8
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
/** One observation of a series. */
|
|
10
|
+
export interface SeriesPoint {
|
|
11
|
+
asOf: string;
|
|
12
|
+
value: number;
|
|
22
13
|
}
|
|
23
14
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
/** What the "latest" read holds per series: its newest observation. */
|
|
16
|
+
export type LatestBySeries = ReadonlyMap<string, SeriesPoint>;
|
|
17
|
+
|
|
18
|
+
/** A row of the board: one series, or the sum of several that the release does not total. */
|
|
19
|
+
interface RowDef {
|
|
20
|
+
id: string;
|
|
21
|
+
label: string;
|
|
22
|
+
tab: DealerTab;
|
|
23
|
+
/** 1 for a breakdown of the total above it. */
|
|
24
|
+
depth: 0 | 1;
|
|
25
|
+
/** The levels the row sums, in the release's series ids. */
|
|
26
|
+
keys: readonly string[];
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
export interface DealerRow {
|
|
31
|
-
|
|
30
|
+
id: string;
|
|
32
31
|
label: string;
|
|
33
32
|
tab: DealerTab;
|
|
33
|
+
depth: 0 | 1;
|
|
34
|
+
/** The total a breakdown row belongs to; null for a total. */
|
|
35
|
+
parent: string | null;
|
|
36
|
+
/** The release's series behind the row; its history is their sum. */
|
|
37
|
+
keys: readonly string[];
|
|
34
38
|
asOf: string | null;
|
|
35
39
|
latest: number | null;
|
|
36
40
|
previous: number | null;
|
|
37
41
|
change: number | null;
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return match ? Number(match[1]) : 0;
|
|
43
|
-
}
|
|
44
|
+
const total = (id: string, label: string, ...keys: string[]): RowDef => ({ id, label, tab: "positions", depth: 0, keys });
|
|
45
|
+
const part = (id: string, label: string, ...keys: string[]): RowDef => ({ id, label, tab: "positions", depth: 1, keys });
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
/**
|
|
48
|
+
* The positions the board shows, in reading order: a total, then what it is
|
|
49
|
+
* made of. Every total is the exact sum of its breakdown in each week of the
|
|
50
|
+
* release (the Fed's own description of the Treasury total says it leaves TIPS
|
|
51
|
+
* out, and its numbers include them), so the breakdown always adds up.
|
|
52
|
+
*/
|
|
53
|
+
const POSITION_ROWS: readonly RowDef[] = [
|
|
54
|
+
total("treasuries", "Treasuries", "PDPOSGST-TOT"),
|
|
55
|
+
part("bills", "Bills", "PDPOSGS-B"),
|
|
56
|
+
part("coupons-0-2", "Coupons 0-2y", "PDPOSGSC-L2"),
|
|
57
|
+
part("coupons-2-3", "Coupons 2-3y", "PDPOSGSC-G2L3"),
|
|
58
|
+
part("coupons-3-6", "Coupons 3-6y", "PDPOSGSC-G3L6"),
|
|
59
|
+
part("coupons-6-7", "Coupons 6-7y", "PDPOSGSC-G6L7"),
|
|
60
|
+
part("coupons-7-11", "Coupons 7-11y", "PDPOSGSC-G7L11"),
|
|
61
|
+
part("coupons-11-21", "Coupons 11-21y", "PDPOSGSC-G11L21"),
|
|
62
|
+
part("coupons-21", "Coupons 21y+", "PDPOSGSC-G21"),
|
|
63
|
+
part("tips", "TIPS", "PDPOSTIPS-L2", "PDPOSTIPS-G2", "PDPOSTIPS-G6L11", "PDPOSTIPS-G11"),
|
|
64
|
+
part("frn", "FRN", "PDPOSGS-BFRN"),
|
|
65
|
+
total("agency", "Agency & GSE", "PDPOSFGS-TOT"),
|
|
66
|
+
part("agency-coupons", "Agency coupons", "PDPOSFGS-C"),
|
|
67
|
+
part("discount-notes", "Discount notes", "PDPOSFGS-DN"),
|
|
68
|
+
total("mbs", "MBS", "PDPOSMBS-TOT"),
|
|
69
|
+
part("tba", "TBA", "PDPOSMBSFGS-TBA"),
|
|
70
|
+
part("pools", "Specified pools", "PDPOSMBSFGS-ST"),
|
|
71
|
+
part("agency-resi", "Other agency MBS", "PDPOSMBSFGS-OR"),
|
|
72
|
+
part("agency-cmbs", "Agency CMBS", "PDPOSMBSFGS-C"),
|
|
73
|
+
part("nonagency-resi", "Non-agency residential", "PDPOSMBSNA-R"),
|
|
74
|
+
part("nonagency-cmbs", "Non-agency CMBS", "PDPOSMBSNA-O"),
|
|
75
|
+
total("corporates", "Corporates", "PDPOSCS-TOT"),
|
|
76
|
+
part("ig", "IG bonds", "PDPOSCSBND-L13", "PDPOSCSBND-G13", "PDPOSCSBND-G5L10", "PDPOSCSBND-G10"),
|
|
77
|
+
part("hy", "HY bonds", "PDPOSCSBND-BELL13", "PDPOSCSBND-BELG13", "PDPOSCSBND-BELG5L10", "PDPOSCSBND-BELG10"),
|
|
78
|
+
part("cp", "Commercial paper", "PDPOSCSCP"),
|
|
79
|
+
total("munis", "Munis", "PDPOSSMGO-TOT"),
|
|
80
|
+
total("abs", "ABS", "PDPOSABS-TOT"),
|
|
81
|
+
];
|
|
57
82
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Settlement fails: deliver, then receive, for each asset, as the release
|
|
85
|
+
* names them (PDFTD and PDFTR). Corporate and agency-debt fails stay off: they
|
|
86
|
+
* are a different order of size and not what the tab is read for.
|
|
87
|
+
*/
|
|
88
|
+
const FAIL_ASSETS = [
|
|
89
|
+
["USTET", "Treasuries ex-TIPS"],
|
|
90
|
+
["UST", "TIPS"],
|
|
91
|
+
["FGM", "Agency MBS"],
|
|
92
|
+
["OM", "Other MBS"],
|
|
93
|
+
] as const;
|
|
61
94
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
95
|
+
const FAIL_ROWS: readonly RowDef[] = FAIL_ASSETS.flatMap(([asset, label]) => [
|
|
96
|
+
{ id: `PDFTD-${asset}`, label: `${label}, fails to deliver`, tab: "fails" as const, depth: 0 as const, keys: [`PDFTD-${asset}`] },
|
|
97
|
+
{ id: `PDFTR-${asset}`, label: `${label}, fails to receive`, tab: "fails" as const, depth: 0 as const, keys: [`PDFTR-${asset}`] },
|
|
98
|
+
]);
|
|
65
99
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return /\bMBS\b/i.test(text) || /mortgage/i.test(text);
|
|
69
|
-
}
|
|
100
|
+
/** Every series a board needs, with the series that holds each one's change from the week before. */
|
|
101
|
+
export const changeSeriesId = (key: string): string => `${key}C`;
|
|
70
102
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return
|
|
103
|
+
/** Sums with the float noise of fractional $mn figures rounded away. */
|
|
104
|
+
function sum(values: readonly number[]): number {
|
|
105
|
+
return Math.round(values.reduce((all, value) => all + value, 0) * 1e6) / 1e6;
|
|
74
106
|
}
|
|
75
107
|
|
|
76
|
-
function
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
108
|
+
function parentLabels(defs: readonly RowDef[]): Map<string, string | null> {
|
|
109
|
+
const parents = new Map<string, string | null>();
|
|
110
|
+
let current: string | null = null;
|
|
111
|
+
for (const def of defs) {
|
|
112
|
+
if (def.depth === 0) current = def.label;
|
|
113
|
+
parents.set(def.id, def.depth === 0 ? null : current);
|
|
114
|
+
}
|
|
115
|
+
return parents;
|
|
82
116
|
}
|
|
83
117
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
118
|
+
/**
|
|
119
|
+
* The board from the latest observation of every series. A row needs every
|
|
120
|
+
* series behind it, all from the same week (a sum with a part missing or from
|
|
121
|
+
* another week would be a different number), and is left out otherwise. Its
|
|
122
|
+
* change is the sum of the series' own published changes, equal to this week
|
|
123
|
+
* minus last week, and is empty when one of them is suppressed or from another
|
|
124
|
+
* week.
|
|
125
|
+
*/
|
|
126
|
+
export function buildDealerRows(latest: LatestBySeries): DealerRow[] {
|
|
127
|
+
const defs = [...POSITION_ROWS, ...FAIL_ROWS];
|
|
128
|
+
const parents = parentLabels(defs);
|
|
129
|
+
const rows: DealerRow[] = [];
|
|
130
|
+
for (const def of defs) {
|
|
131
|
+
const levels = def.keys.map((key) => latest.get(key));
|
|
132
|
+
const asOf = levels[0]?.asOf;
|
|
133
|
+
if (!asOf || levels.some((point) => point?.asOf !== asOf)) continue;
|
|
134
|
+
const changes = def.keys.map((key) => latest.get(changeSeriesId(key)));
|
|
135
|
+
const level = sum(levels.map((point) => point!.value));
|
|
136
|
+
const change = changes.every((point) => point?.asOf === asOf) ? sum(changes.map((point) => point!.value)) : null;
|
|
137
|
+
rows.push({
|
|
138
|
+
id: def.id,
|
|
139
|
+
label: def.label,
|
|
140
|
+
tab: def.tab,
|
|
141
|
+
depth: def.depth,
|
|
142
|
+
parent: parents.get(def.id) ?? null,
|
|
143
|
+
keys: def.keys,
|
|
144
|
+
asOf,
|
|
145
|
+
latest: level,
|
|
146
|
+
previous: change != null ? sum([level, -change]) : null,
|
|
147
|
+
change,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return rows;
|
|
88
151
|
}
|
|
89
152
|
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
153
|
+
export function boardAsOf(rows: readonly DealerRow[]): string | null {
|
|
154
|
+
let best: string | null = null;
|
|
155
|
+
for (const row of rows) {
|
|
156
|
+
if (row.asOf && (best == null || row.asOf > best)) best = row.asOf;
|
|
157
|
+
}
|
|
158
|
+
return best;
|
|
93
159
|
}
|
|
94
160
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
161
|
+
/**
|
|
162
|
+
* The newest series break: the one the release is published in now. Breaks
|
|
163
|
+
* restate the series, so keys are only meaningful inside one.
|
|
164
|
+
*/
|
|
165
|
+
export function newestSeriesBreak(breaks: ReadonlyArray<{ seriesBreak: string; startDate: string }>): string | null {
|
|
166
|
+
let best: { seriesBreak: string; startDate: string } | null = null;
|
|
167
|
+
for (const entry of breaks) {
|
|
168
|
+
if (best == null || entry.startDate > best.startDate) best = entry;
|
|
169
|
+
}
|
|
170
|
+
return best?.seriesBreak ?? null;
|
|
103
171
|
}
|
|
104
172
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
173
|
+
/** The dates every series has, with the sum of their values: a week a series is missing is not a week of the sum. */
|
|
174
|
+
export function sumSeries(series: ReadonlyArray<readonly SeriesPoint[]>): SeriesPoint[] {
|
|
175
|
+
if (series.length === 0) return [];
|
|
176
|
+
const totals = new Map<string, { value: number; count: number }>();
|
|
177
|
+
for (const points of series) {
|
|
178
|
+
for (const point of points) {
|
|
179
|
+
const entry = totals.get(point.asOf);
|
|
180
|
+
if (entry) {
|
|
181
|
+
entry.value += point.value;
|
|
182
|
+
entry.count += 1;
|
|
183
|
+
} else {
|
|
184
|
+
totals.set(point.asOf, { value: point.value, count: 1 });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return [...totals.entries()]
|
|
189
|
+
.filter(([, entry]) => entry.count === series.length)
|
|
190
|
+
.map(([asOf, entry]) => ({ asOf, value: sum([entry.value]) }))
|
|
191
|
+
.sort((a, b) => (a.asOf < b.asOf ? -1 : a.asOf > b.asOf ? 1 : 0));
|
|
112
192
|
}
|
|
113
193
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
194
|
+
/** The chart's ranges; the release's current series start in July 2024, so a long one is all of it. */
|
|
195
|
+
export const DEALER_RANGES = [
|
|
196
|
+
{ value: "6M", label: "6M", hint: "1", days: 183 },
|
|
197
|
+
{ value: "1Y", label: "1Y", hint: "2", days: 366 },
|
|
198
|
+
{ value: "ALL", label: "All", hint: "3", days: null },
|
|
199
|
+
] as const;
|
|
118
200
|
|
|
119
|
-
export
|
|
120
|
-
|
|
121
|
-
const current = seriesBreak ? entries.filter((entry) => entry.seriesBreak === seriesBreak) : [];
|
|
122
|
-
const positions = current.filter(isPositionAggregate).sort(byReadOrder).slice(0, SERIES_CAP);
|
|
123
|
-
const fails = current.filter(isFailAggregate).sort(byReadOrder).slice(0, Math.max(0, SERIES_CAP - positions.length));
|
|
124
|
-
return { seriesBreak: seriesBreak ?? "", positions, fails };
|
|
125
|
-
}
|
|
201
|
+
export type DealerRange = typeof DEALER_RANGES[number]["value"];
|
|
202
|
+
export const DEFAULT_DEALER_RANGE: DealerRange = "1Y";
|
|
126
203
|
|
|
127
|
-
function
|
|
128
|
-
|
|
129
|
-
const cut = text.search(/\s+-\s+|\s*:\s*/);
|
|
130
|
-
if (cut > 0) text = text.slice(0, cut).trim();
|
|
131
|
-
text = text.replace(/\s+/g, " ");
|
|
132
|
-
if (!text) return keyid;
|
|
133
|
-
return text.length <= 48 ? text : `${text.slice(0, 47).trimEnd()}…`;
|
|
204
|
+
export function dealerRange(value: unknown): DealerRange {
|
|
205
|
+
return DEALER_RANGES.find((range) => range.value === value)?.value ?? DEFAULT_DEALER_RANGE;
|
|
134
206
|
}
|
|
135
207
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (!asset) return fallbackLabel(entry.description, entry.keyid);
|
|
144
|
-
if (id.startsWith("PDFTD")) return `${asset}, fails to deliver`;
|
|
145
|
-
if (id.startsWith("PDFTR")) return `${asset}, fails to receive`;
|
|
146
|
-
if (id.startsWith("PDFAIL") || id.startsWith("PDFAS")) return `${asset} fails`;
|
|
147
|
-
return asset;
|
|
208
|
+
/** The points of the last `range`, counted back from the newest observation. */
|
|
209
|
+
export function pointsInRange(points: readonly SeriesPoint[], range: DealerRange): SeriesPoint[] {
|
|
210
|
+
const days = DEALER_RANGES.find((entry) => entry.value === range)?.days ?? null;
|
|
211
|
+
const last = points.at(-1);
|
|
212
|
+
if (days == null || !last) return [...points];
|
|
213
|
+
const cutoff = Date.parse(`${last.asOf}T00:00:00Z`) - days * 86_400_000;
|
|
214
|
+
return points.filter((point) => Date.parse(`${point.asOf}T00:00:00Z`) >= cutoff);
|
|
148
215
|
}
|
|
149
216
|
|
|
150
217
|
export function formatLevel(value: number | null): string {
|
|
@@ -161,30 +228,13 @@ export function formatChange(value: number | null): string {
|
|
|
161
228
|
return value > 0 ? `+${text}` : `-${text}`;
|
|
162
229
|
}
|
|
163
230
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
keyid: parsed.keyid || entry.keyid,
|
|
173
|
-
label: positionLabel(entry),
|
|
174
|
-
tab,
|
|
175
|
-
asOf: parsed.latest?.asOf ?? null,
|
|
176
|
-
latest,
|
|
177
|
-
previous,
|
|
178
|
-
change: latest != null && previous != null ? latest - previous : null,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function boardAsOf(rows: readonly DealerRow[]): string | null {
|
|
183
|
-
let best: string | null = null;
|
|
184
|
-
for (const row of rows) {
|
|
185
|
-
if (row.asOf && (best == null || row.asOf > best)) best = row.asOf;
|
|
186
|
-
}
|
|
187
|
-
return best;
|
|
231
|
+
/**
|
|
232
|
+
* The row's name, a breakdown set in under its total. The indent is no-break
|
|
233
|
+
* spaces: table cells do not wrap, and on the desktop that also folds ordinary
|
|
234
|
+
* leading spaces away.
|
|
235
|
+
*/
|
|
236
|
+
export function dealerRowLabel(row: Pick<DealerRow, "label" | "depth">): string {
|
|
237
|
+
return `${"\u00a0\u00a0".repeat(row.depth)}${row.label}`;
|
|
188
238
|
}
|
|
189
239
|
|
|
190
240
|
type DealerColumnId = "position" | "asOf" | "latest" | "previous" | "change";
|
|
@@ -1,24 +1,40 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
2
|
import {
|
|
3
|
+
ChartTableHeader,
|
|
3
4
|
DataTableView,
|
|
4
5
|
PaneStatusBody,
|
|
6
|
+
QueryBar,
|
|
7
|
+
usePaneNoticeFooter,
|
|
5
8
|
usePaneStatusFooter,
|
|
6
9
|
usePaneTabs,
|
|
10
|
+
type ChartTableChart,
|
|
7
11
|
type DataTableCell,
|
|
12
|
+
type DataTableKeyEvent,
|
|
8
13
|
} from "../../../components";
|
|
14
|
+
import { spanAxisFormatter } from "../../../components/chart-table";
|
|
15
|
+
import { useChartViewReset } from "../../../components/chart/core/view-reset";
|
|
16
|
+
import { scalarPoint, staticSeries } from "../../../components/chart/static/series";
|
|
9
17
|
import { handleRefreshKey, usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
10
18
|
import { useAsyncResource } from "../../../react/async-resource";
|
|
11
19
|
import { colors } from "../../../theme/colors";
|
|
12
20
|
import type { PaneProps } from "../../../types/plugin";
|
|
13
21
|
import { Box, TextAttributes } from "../../../ui";
|
|
22
|
+
import { formatNumber } from "../../../utils/format";
|
|
23
|
+
import { isPlainKeyboardEvent } from "../../../utils/keyboard";
|
|
14
24
|
import { usePluginPaneState } from "../../runtime";
|
|
15
|
-
import { fetchPrimaryDealers } from "./client";
|
|
25
|
+
import { cachedDealerHistory, fetchPrimaryDealers, hasDealerHistory, loadDealerHistory } from "./client";
|
|
16
26
|
import {
|
|
27
|
+
DEALER_RANGES,
|
|
28
|
+
DEFAULT_DEALER_RANGE,
|
|
17
29
|
PRIMARY_DEALERS_PANE_ID,
|
|
18
30
|
buildDealerColumns,
|
|
31
|
+
dealerRange,
|
|
32
|
+
dealerRowLabel,
|
|
19
33
|
formatChange,
|
|
20
34
|
formatLevel,
|
|
35
|
+
pointsInRange,
|
|
21
36
|
type DealerColumn,
|
|
37
|
+
type DealerRange,
|
|
22
38
|
type DealerRow,
|
|
23
39
|
type DealerTab,
|
|
24
40
|
} from "./model";
|
|
@@ -28,19 +44,49 @@ const TABS = [
|
|
|
28
44
|
{ label: "Fails", value: "fails" },
|
|
29
45
|
];
|
|
30
46
|
const EMPTY_ROWS: DealerRow[] = [];
|
|
47
|
+
const RANGE_OPTIONS = DEALER_RANGES.map(({ value, label, hint }) => ({ value, label, hint }));
|
|
48
|
+
/** How long the selection rests before its history is read, so holding a key down the board reads one row, not all it passes. */
|
|
49
|
+
const CHART_SETTLE_MS = 150;
|
|
50
|
+
/** Ticks in the table's own figures (whole $mn), with decimals only where the plotted range is narrow. */
|
|
51
|
+
const formatLevelAxis = spanAxisFormatter((value, digits) => formatNumber(value, digits));
|
|
31
52
|
|
|
32
53
|
function changeColor(value: number | null): string {
|
|
33
54
|
if (value == null || value === 0) return colors.textMuted;
|
|
34
55
|
return value > 0 ? colors.positive : colors.negative;
|
|
35
56
|
}
|
|
36
57
|
|
|
58
|
+
/** The row the chart follows: the selection once it rests, or at once when its history is already in hand. */
|
|
59
|
+
function useChartRow(rows: readonly DealerRow[], selected: DealerRow | null, known: boolean): DealerRow | null {
|
|
60
|
+
const [restedId, setRestedId] = useState<string | null>(null);
|
|
61
|
+
const selectedId = selected?.id ?? null;
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (selectedId === restedId) return;
|
|
64
|
+
if (selectedId == null || restedId == null || known || !rows.some((row) => row.id === restedId)) {
|
|
65
|
+
setRestedId(selectedId);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const timer = setTimeout(() => setRestedId(selectedId), CHART_SETTLE_MS);
|
|
69
|
+
return () => clearTimeout(timer);
|
|
70
|
+
}, [known, restedId, rows, selectedId]);
|
|
71
|
+
if (known) return selected;
|
|
72
|
+
return rows.find((row) => row.id === restedId) ?? selected;
|
|
73
|
+
}
|
|
74
|
+
|
|
37
75
|
export function PrimaryDealersPane({ focused, width, height }: PaneProps) {
|
|
38
76
|
const request = useCallback(() => fetchPrimaryDealers(), []);
|
|
39
77
|
const { data, loading, error, load } = useAsyncResource(request);
|
|
40
78
|
const [storedTab, setTab] = usePluginPaneState<string>("tab", "positions");
|
|
41
79
|
const tab: DealerTab = storedTab === "fails" ? "fails" : "positions";
|
|
42
|
-
const [
|
|
80
|
+
const [positionId, setPositionId] = usePluginPaneState<string | null>("selected:positions", null);
|
|
81
|
+
const [failId, setFailId] = usePluginPaneState<string | null>("selected:fails", null);
|
|
82
|
+
const [storedRange, setStoredRange] = usePluginPaneState<string>("range", DEFAULT_DEALER_RANGE);
|
|
83
|
+
const range = dealerRange(storedRange);
|
|
84
|
+
const resetChartView = useChartViewReset();
|
|
43
85
|
const rows = tab === "fails" ? data?.fails ?? EMPTY_ROWS : data?.positions ?? EMPTY_ROWS;
|
|
86
|
+
const selectedId = tab === "fails" ? failId : positionId;
|
|
87
|
+
const setSelectedId = tab === "fails" ? setFailId : setPositionId;
|
|
88
|
+
const selected = rows.find((row) => row.id === selectedId) ?? rows[0] ?? null;
|
|
89
|
+
const seriesBreak = data?.seriesBreak ?? null;
|
|
44
90
|
const asOf = useMemo(() => {
|
|
45
91
|
let best: string | null = null;
|
|
46
92
|
for (const row of rows) {
|
|
@@ -54,15 +100,57 @@ export function PrimaryDealersPane({ focused, width, height }: PaneProps) {
|
|
|
54
100
|
[asOf],
|
|
55
101
|
);
|
|
56
102
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
103
|
+
// The chart follows the selected row. Its history is read for that row alone.
|
|
104
|
+
const known = !!selected && !!seriesBreak && hasDealerHistory(seriesBreak, selected);
|
|
105
|
+
const chartRow = useChartRow(rows, selected, known);
|
|
106
|
+
const loadHistory = useMemo(
|
|
107
|
+
() => chartRow && seriesBreak ? (force: boolean) => loadDealerHistory(seriesBreak, chartRow, force) : null,
|
|
108
|
+
[chartRow, seriesBreak],
|
|
109
|
+
);
|
|
110
|
+
const historyResource = useAsyncResource(loadHistory, { keepPreviousData: true });
|
|
111
|
+
const history = useMemo(
|
|
112
|
+
() => (chartRow && seriesBreak ? cachedDealerHistory(seriesBreak, chartRow) : null) ?? historyResource.data,
|
|
113
|
+
[chartRow, historyResource.data, seriesBreak],
|
|
114
|
+
);
|
|
115
|
+
// While another row's history loads the chart keeps the last one drawn, unless that row is not on this tab.
|
|
116
|
+
const historyRow = history ? rows.find((row) => row.id === history.rowId) ?? null : null;
|
|
117
|
+
const series = useMemo(() => history && historyRow
|
|
118
|
+
? [staticSeries(
|
|
119
|
+
pointsInRange(history.points, range).map((point) => scalarPoint(new Date(`${point.asOf}T00:00:00Z`), point.value)),
|
|
120
|
+
{ id: historyRow.id, label: `${historyRow.label} $mn`, color: colors.positive, calendarSpaced: true },
|
|
121
|
+
)]
|
|
122
|
+
: [], [history, historyRow, range]);
|
|
123
|
+
const historyFailed = history?.rowId !== chartRow?.id && !!historyResource.error;
|
|
124
|
+
const chart: ChartTableChart | null = selected && !historyFailed ? {
|
|
125
|
+
series,
|
|
126
|
+
formatValue: formatLevel,
|
|
127
|
+
formatAxisValue: formatLevelAxis,
|
|
128
|
+
remoteKind: "primary-dealer-history",
|
|
129
|
+
loading: !historyRow,
|
|
130
|
+
empty: `No history for ${selected.label}`,
|
|
131
|
+
} : null;
|
|
61
132
|
|
|
62
|
-
const
|
|
133
|
+
const { reload: reloadHistory } = historyResource;
|
|
134
|
+
const refresh = useCallback(() => {
|
|
135
|
+
void load();
|
|
136
|
+
void reloadHistory();
|
|
137
|
+
}, [load, reloadHistory]);
|
|
63
138
|
const selectTab = useCallback((value: string) => setTab(value === "fails" ? "fails" : "positions"), [setTab]);
|
|
139
|
+
const selectRange = useCallback((value: DealerRange) => setStoredRange(value), [setStoredRange]);
|
|
64
140
|
usePaneRefreshKey(refresh, { focused });
|
|
65
141
|
|
|
142
|
+
// The number keys pick the range, as the bar's hints say; picking the one showing sends the chart back to it.
|
|
143
|
+
const handleKeyDown = useCallback((event: DataTableKeyEvent) => {
|
|
144
|
+
if (handleRefreshKey(event, refresh, { stopPropagation: true })) return true;
|
|
145
|
+
if (!isPlainKeyboardEvent(event)) return false;
|
|
146
|
+
const picked = DEALER_RANGES.find((option) => option.hint === event.name);
|
|
147
|
+
if (!picked) return false;
|
|
148
|
+
event.preventDefault?.();
|
|
149
|
+
if (picked.value === range) resetChartView();
|
|
150
|
+
else selectRange(picked.value);
|
|
151
|
+
return true;
|
|
152
|
+
}, [range, refresh, resetChartView, selectRange]);
|
|
153
|
+
|
|
66
154
|
const { strip: tabs, rows: tabRows } = usePaneTabs({
|
|
67
155
|
tabs: TABS,
|
|
68
156
|
activeValue: tab,
|
|
@@ -78,6 +166,11 @@ export function PrimaryDealersPane({ focused, width, height }: PaneProps) {
|
|
|
78
166
|
error,
|
|
79
167
|
info: footerInfo,
|
|
80
168
|
});
|
|
169
|
+
usePaneNoticeFooter({
|
|
170
|
+
registrationId: `${PRIMARY_DEALERS_PANE_ID}:history`,
|
|
171
|
+
focused,
|
|
172
|
+
notices: historyFailed && selected ? [`History for ${selected.label} is unavailable: ${historyResource.error}`] : [],
|
|
173
|
+
});
|
|
81
174
|
|
|
82
175
|
const renderCell = useCallback((
|
|
83
176
|
row: DealerRow,
|
|
@@ -88,10 +181,12 @@ export function PrimaryDealersPane({ focused, width, height }: PaneProps) {
|
|
|
88
181
|
const selectedColor = rowState.selected ? colors.selectedText : undefined;
|
|
89
182
|
switch (column.id) {
|
|
90
183
|
case "position":
|
|
184
|
+
// A breakdown reads under its total, in plain weight.
|
|
91
185
|
return {
|
|
92
|
-
text: row
|
|
93
|
-
|
|
94
|
-
|
|
186
|
+
text: dealerRowLabel(row),
|
|
187
|
+
value: row.label,
|
|
188
|
+
color: selectedColor ?? (row.depth === 0 ? colors.textBright : colors.text),
|
|
189
|
+
attributes: row.depth === 0 ? TextAttributes.BOLD : undefined,
|
|
95
190
|
};
|
|
96
191
|
case "asOf":
|
|
97
192
|
return { text: row.asOf ?? "—", color: selectedColor ?? colors.textMuted };
|
|
@@ -122,26 +217,51 @@ export function PrimaryDealersPane({ focused, width, height }: PaneProps) {
|
|
|
122
217
|
);
|
|
123
218
|
}
|
|
124
219
|
|
|
220
|
+
const rootHeight = Math.max(1, height - tabRows);
|
|
125
221
|
return (
|
|
126
222
|
<Box flexDirection="column" width={width} height={height}>
|
|
127
223
|
{tabs}
|
|
128
224
|
<DataTableView<DealerRow, DealerColumn>
|
|
129
225
|
focused={focused}
|
|
130
226
|
rootWidth={width}
|
|
131
|
-
rootHeight={
|
|
227
|
+
rootHeight={rootHeight}
|
|
228
|
+
rootBefore={selected ? (
|
|
229
|
+
<ChartTableHeader
|
|
230
|
+
width={width}
|
|
231
|
+
height={rootHeight}
|
|
232
|
+
tableRows={rows.length}
|
|
233
|
+
tableColumns={columns}
|
|
234
|
+
query={(
|
|
235
|
+
<QueryBar
|
|
236
|
+
width={width}
|
|
237
|
+
filters={[{
|
|
238
|
+
id: "range",
|
|
239
|
+
label: "Range",
|
|
240
|
+
inline: true,
|
|
241
|
+
chartRange: true,
|
|
242
|
+
value: range,
|
|
243
|
+
options: RANGE_OPTIONS,
|
|
244
|
+
onChange: (value: string) => selectRange(dealerRange(value)),
|
|
245
|
+
}]}
|
|
246
|
+
/>
|
|
247
|
+
)}
|
|
248
|
+
chart={chart}
|
|
249
|
+
/>
|
|
250
|
+
) : undefined}
|
|
132
251
|
selection={{
|
|
133
252
|
kind: "id",
|
|
134
|
-
selectedId,
|
|
135
|
-
getId: (row) => row.
|
|
253
|
+
selectedId: selected?.id ?? null,
|
|
254
|
+
getId: (row) => row.id,
|
|
136
255
|
onChange: (id) => setSelectedId(id),
|
|
137
256
|
}}
|
|
138
|
-
onRootKeyDown={
|
|
257
|
+
onRootKeyDown={handleKeyDown}
|
|
139
258
|
columns={columns}
|
|
140
259
|
items={rows}
|
|
141
260
|
sortColumnId={null}
|
|
142
261
|
sortDirection="asc"
|
|
143
|
-
getItemKey={(row) => row.
|
|
262
|
+
getItemKey={(row) => row.id}
|
|
144
263
|
renderCell={renderCell}
|
|
264
|
+
resetScrollKey={tab}
|
|
145
265
|
emptyStateTitle={tab === "fails" ? "No fails in this release." : "No positions in this release."}
|
|
146
266
|
/>
|
|
147
267
|
</Box>
|
|
@@ -5,6 +5,7 @@ import { formatRelativeTime } from "../../../utils/datetime-format";
|
|
|
5
5
|
import { displayWidth, formatCurrency, formatNumber } from "../../../utils/format";
|
|
6
6
|
import { compareSortValues, type SortDirection } from "../../../utils/sort-values";
|
|
7
7
|
import { loadingErrorFooterInfo } from "../../../components/data-table/table-pane";
|
|
8
|
+
import { delayedFooterSegment } from "../../../components/layout/pane/footer/model";
|
|
8
9
|
import { regularSessionValuationPrice } from "../../../market-data/quotes/regular-session-valuation";
|
|
9
10
|
|
|
10
11
|
function compactPeriod(period: string): string {
|
|
@@ -369,14 +370,17 @@ export function buildAnalystStatusSegments(
|
|
|
369
370
|
|
|
370
371
|
// The upside is only as good as the price it was measured against.
|
|
371
372
|
if (reference.price != null) {
|
|
373
|
+
// A delayed price is the footer's clock beside it; a real-time one says so in words.
|
|
374
|
+
const delay = reference.freshness?.endsWith(" delayed") ? reference.freshness.slice(0, -" delayed".length) : null;
|
|
372
375
|
segments.push({
|
|
373
376
|
id: "analyst-reference-price",
|
|
374
377
|
parts: [
|
|
375
378
|
{ text: "upside vs", tone: "label" },
|
|
376
379
|
{ text: price(reference.price) },
|
|
377
|
-
...(reference.freshness ? [{ text: reference.freshness, tone: "muted" as const }] : []),
|
|
380
|
+
...(reference.freshness && !delay ? [{ text: reference.freshness, tone: "muted" as const }] : []),
|
|
378
381
|
],
|
|
379
382
|
});
|
|
383
|
+
if (delay) segments.push(delayedFooterSegment({ id: "analyst-reference-delay", detail: `Price ${reference.freshness}`, delay }));
|
|
380
384
|
}
|
|
381
385
|
|
|
382
386
|
if (!data.stale && data.fetchedAt) {
|