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,6 +4,7 @@ import { signalWithTimeout } from "../../../utils/async-deadline";
|
|
|
4
4
|
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
5
5
|
import { httpFetch } from "../../../utils/http-transport";
|
|
6
6
|
import { isoDateOrNull, numericOrNull, recordOrNull, textOrNull } from "../../../utils/guards";
|
|
7
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
7
8
|
import { zonedDateKey } from "../../../utils/zoned-date-time";
|
|
8
9
|
|
|
9
10
|
const ENDPOINT = "https://services-dynarep.ddwa.finra.org/public/reporting/v2/data/group/FixedIncomeMarket/name/corporateAndAgencySecurities";
|
|
@@ -226,6 +227,7 @@ function dataRows(data: unknown): unknown[] | null {
|
|
|
226
227
|
const parsed = JSON.parse(data) as unknown;
|
|
227
228
|
return Array.isArray(parsed) ? parsed : null;
|
|
228
229
|
} catch {
|
|
230
|
+
// Intentional ignore: malformed nested rows make postBondPage reject the response shape.
|
|
229
231
|
return null;
|
|
230
232
|
}
|
|
231
233
|
}
|
|
@@ -340,7 +342,9 @@ async function readPayload(response: Response): Promise<unknown> {
|
|
|
340
342
|
async function openSession(signal: AbortSignal): Promise<string> {
|
|
341
343
|
const session = await httpFetch(ENDPOINT, { headers: REQUEST_HEADERS, signal });
|
|
342
344
|
const token = xsrfToken(session);
|
|
343
|
-
await session.body?.cancel().catch(() => {
|
|
345
|
+
await session.body?.cancel().catch(() => {
|
|
346
|
+
// Intentional ignore: only session headers are needed; discarding its unused body is best effort.
|
|
347
|
+
});
|
|
344
348
|
if (!token) throw new Error(failureText(session.status, "session"));
|
|
345
349
|
return token;
|
|
346
350
|
}
|
|
@@ -477,7 +481,14 @@ export async function fetchBondTape(input: string, options: BondTapeOptions = {}
|
|
|
477
481
|
let answer = first;
|
|
478
482
|
let issuer: BondIssuer | null = null;
|
|
479
483
|
if (first.bonds.length === 0 && query?.kind === "issuer" && query.ticker && options.companyName) {
|
|
480
|
-
const company = await options.companyName(query.ticker).catch(() =>
|
|
484
|
+
const company = await options.companyName(query.ticker).catch((error: unknown) => {
|
|
485
|
+
options.signal?.throwIfAborted();
|
|
486
|
+
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
487
|
+
logFailure("trace-bonds.client", "Resolve bond issuer company failed")(error);
|
|
488
|
+
answer = { ...first, failures: [...first.failures, "Issuer-name lookup failed."] };
|
|
489
|
+
return null;
|
|
490
|
+
});
|
|
491
|
+
options.signal?.throwIfAborted();
|
|
481
492
|
const name = company ? tapeIssuerName(company) : null;
|
|
482
493
|
if (company && name && name !== query.name) {
|
|
483
494
|
issuer = { company, name };
|
|
@@ -492,7 +503,7 @@ export async function fetchBondTape(input: string, options: BondTapeOptions = {}
|
|
|
492
503
|
// No prints for the query: date the answer by the market's newest sale.
|
|
493
504
|
const market = await postBondPage(token, [TRADED], 1, deadline)
|
|
494
505
|
.then((payload) => latestTradeDate(mergeBondAnswers([{ payload, accepts: anyBond, limit: 1 }]).bonds))
|
|
495
|
-
.catch((
|
|
506
|
+
.catch(logFailure("trace-bonds.client", "Load bond market date failed", null));
|
|
496
507
|
return { ...result, tradeDate: market };
|
|
497
508
|
}
|
|
498
509
|
|
|
@@ -89,7 +89,7 @@ export const traceBondsHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
89
89
|
rows: bonds.map(reportRow),
|
|
90
90
|
...(query ? { empty: noPrintsText(query, tradeDate, issuer) } : {}),
|
|
91
91
|
}],
|
|
92
|
-
complete:
|
|
92
|
+
complete: failures.length === 0,
|
|
93
93
|
...(failures.length > 0 ? { errors: failures } : {}),
|
|
94
94
|
...(truncated ? { notes: [TRUNCATED_TEXT] } : {}),
|
|
95
95
|
...(shown ? { warnings: [issuerFallbackText(query!, issuer)] } : {}),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { DataTableView, EmptyState, PaneStatusBody, QueryBar, StatGrid, statGridRows, usePaneFooter, usePaneMenuItems,
|
|
2
|
+
import { splitDelayedStatus, DataTableView, EmptyState, PaneStatusBody, QueryBar, StatGrid, statGridRows, usePaneFooter, usePaneMenuItems,
|
|
3
3
|
usePaneNoticeFooter, usePaneTabs, usePaneTicker, type DataTableColumn, type DataTableKeyEvent, type StatItem } from "../../../components";
|
|
4
4
|
import { useTableLoadMore } from "../../../components/table-view-shared";
|
|
5
5
|
import { useStaticChartBitmapSize } from "../../../components/chart/composite/bitmap";
|
|
@@ -360,7 +360,7 @@ export function VolSurfacePane({ focused, width, height }: PaneProps) {
|
|
|
360
360
|
info: [
|
|
361
361
|
...(active.loading ? [{ id: "loading", parts: [{ text: snapshot && snapshot.loaded < snapshot.requested
|
|
362
362
|
? `loading ${snapshot.loaded}/${snapshot.requested} expiries` : "loading", tone: "muted" as const }] }] : []),
|
|
363
|
-
...(freshness ?
|
|
363
|
+
...(freshness ? splitDelayedStatus({ id: "source", parts: [{ text: freshness, tone: "muted" }] }) : []),
|
|
364
364
|
...(snapshot?.expiries.some((entry) => entry.stale) ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
|
|
365
365
|
...(selectedExpiry?.fit && activeTab === "smile" ? [{ id: "fit", parts: [{ text: `${selectedExpiry.fit.method} · RMSE ${(selectedExpiry.fit.residual * 100).toFixed(3)} vol pts`, tone: "muted" as const }] }] : []),
|
|
366
366
|
...(selectionStatus ? [{ id: "selection", parts: [{ text: selectionStatus, tone: "muted" as const }] }] : []),
|
|
@@ -31,7 +31,7 @@ export const BUILTIN_EDITORIAL: Readonly<Record<string, BuiltinEditorial>> = {
|
|
|
31
31
|
tagline: "Account, chat, Ask Gloom and Cloud data.",
|
|
32
32
|
icon: "plugin-icons/gloomberb-cloud.webp",
|
|
33
33
|
},
|
|
34
|
-
alerts: { categories: ["alerts"], tagline: "Price,
|
|
34
|
+
alerts: { categories: ["alerts"], tagline: "Price, earnings, filing, news and flow alerts." },
|
|
35
35
|
"alt-data": {
|
|
36
36
|
categories: ["research", "data"],
|
|
37
37
|
tagline: "Supply chains, hiring, apps, GPUs and power.",
|
package/src/plugins/compat.ts
CHANGED
|
@@ -68,6 +68,8 @@ export interface PluginManifest {
|
|
|
68
68
|
id: string | null;
|
|
69
69
|
/** The oldest Gloomberb the checkout says it runs on. */
|
|
70
70
|
minGloomberb: string | null;
|
|
71
|
+
/** gloom.json `icon`: the plugin's logo, a path inside the checkout such as `icon.webp`. */
|
|
72
|
+
icon: string | null;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
function readJson(path: string): Record<string, unknown> | null {
|
|
@@ -106,6 +108,7 @@ export function readPluginManifest(pluginDir: string): PluginManifest {
|
|
|
106
108
|
minGloomberb: versionField(gloom?.minGloom)
|
|
107
109
|
?? versionField(gloom?.minGloomberb)
|
|
108
110
|
?? peerRangeMinimum(peers?.gloomberb),
|
|
111
|
+
icon: typeof gloom?.icon === "string" && gloom.icon.trim() ? gloom.icon.trim() : null,
|
|
109
112
|
};
|
|
110
113
|
}
|
|
111
114
|
|
|
@@ -24,6 +24,11 @@ export function listExternalPlugins(): readonly LoadedExternalPlugin[] {
|
|
|
24
24
|
return entries;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** The logo of a plugin loaded here, as a `data:` URL; null for a built-in or a plugin without one. */
|
|
28
|
+
export function externalPluginIcon(pluginId: string): string | null {
|
|
29
|
+
return entries.find((entry) => entry.plugin.id === pluginId)?.icon ?? null;
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
/** Replaces the entry for the same plugin id or directory, or appends. */
|
|
28
33
|
export function upsertExternalPlugin(entry: LoadedExternalPlugin): void {
|
|
29
34
|
const index = entries.findIndex((existing) => (
|
package/src/plugins/loader.ts
CHANGED
|
@@ -33,6 +33,12 @@ export interface LoadedExternalPlugin {
|
|
|
33
33
|
repo?: string;
|
|
34
34
|
/** A symlink to a local checkout (`gloomberb plugin link`) rather than a clone. */
|
|
35
35
|
linked?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* The plugin's logo as a `data:` URL (plugin-icon.ts), for the desktop and
|
|
38
|
+
* the web to draw beside what it contributes, such as its broker. The
|
|
39
|
+
* terminal draws no images and leaves it unread.
|
|
40
|
+
*/
|
|
41
|
+
icon?: string;
|
|
36
42
|
error?: string;
|
|
37
43
|
/**
|
|
38
44
|
* The Gloomberb the checkout declares it needs, when this one is older. The
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "fs";
|
|
2
|
+
import { basename, extname, isAbsolute, relative, resolve, sep } from "path";
|
|
3
|
+
import { logFailure } from "../utils/log-failure";
|
|
4
|
+
import { readPluginManifest } from "./compat";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A plugin's logo as the desktop and the web draw it: the file its gloom.json
|
|
8
|
+
* names as `icon` (PLUGINS.md), or the `icon.svg`, `icon.webp` or `icon.png`
|
|
9
|
+
* a checkout without one ships beside its entry, read into a `data:` URL. The
|
|
10
|
+
* view has no filesystem, so the Bun process that reads the plugins folder
|
|
11
|
+
* hands the image over with the plugin's bundle.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const ICON_TYPES: Readonly<Record<string, string>> = {
|
|
15
|
+
".svg": "image/svg+xml",
|
|
16
|
+
".webp": "image/webp",
|
|
17
|
+
".png": "image/png",
|
|
18
|
+
".jpg": "image/jpeg",
|
|
19
|
+
".jpeg": "image/jpeg",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const CONVENTIONAL_ICONS = ["icon.svg", "icon.webp", "icon.png"];
|
|
23
|
+
|
|
24
|
+
/** A logo is drawn at most a few dozen pixels wide; anything larger is not one. */
|
|
25
|
+
const MAX_ICON_BYTES = 256 * 1024;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The icon `name` names inside `pluginDir`, typed by the name the plugin gave
|
|
29
|
+
* it, or null for a path that leaves the checkout or is not a small image.
|
|
30
|
+
*/
|
|
31
|
+
function iconFile(pluginDir: string, name: string): { path: string; type: string } | null {
|
|
32
|
+
const type = ICON_TYPES[extname(name).toLowerCase()];
|
|
33
|
+
if (!type) return null;
|
|
34
|
+
const root = realpathSync(pluginDir);
|
|
35
|
+
const path = resolve(root, name);
|
|
36
|
+
if (!existsSync(path)) return null;
|
|
37
|
+
// A link that points out of the checkout is not the plugin's to show.
|
|
38
|
+
const real = realpathSync(path);
|
|
39
|
+
const inside = relative(root, real);
|
|
40
|
+
if (!inside || inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside)) return null;
|
|
41
|
+
const info = statSync(real);
|
|
42
|
+
return info.isFile() && info.size > 0 && info.size <= MAX_ICON_BYTES ? { path: real, type } : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The plugin's logo as a `data:` URL. A declared icon that is missing,
|
|
47
|
+
* unusable or unreadable falls back to the conventional names, each tried on
|
|
48
|
+
* its own, so one file the process may not read does not hide the next.
|
|
49
|
+
*/
|
|
50
|
+
export function readPluginIcon(pluginDir: string): string | null {
|
|
51
|
+
const declared = readPluginManifest(pluginDir).icon;
|
|
52
|
+
for (const name of declared ? [declared, ...CONVENTIONAL_ICONS] : CONVENTIONAL_ICONS) {
|
|
53
|
+
try {
|
|
54
|
+
const icon = iconFile(pluginDir, name);
|
|
55
|
+
if (icon) return `data:${icon.type};base64,${readFileSync(icon.path).toString("base64")}`;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
logFailure("plugin-icon", `Reading ${name} for ${basename(pluginDir)} failed`)(error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
@@ -9,6 +9,7 @@ import type { NewsQuery, NewsQueryState } from "../../types/news-source";
|
|
|
9
9
|
import type {
|
|
10
10
|
AppNotificationDelivery,
|
|
11
11
|
AppNotificationRequest,
|
|
12
|
+
BrokerInstanceCreateOptions,
|
|
12
13
|
BrokerInstanceUpdateOptions,
|
|
13
14
|
PaneSettingField,
|
|
14
15
|
PaneTemplateCreateOptions,
|
|
@@ -27,7 +28,7 @@ export interface PluginHostActions {
|
|
|
27
28
|
getTicker(symbol: string): TickerRecord | null;
|
|
28
29
|
getData(symbol: string): TickerFinancials | null;
|
|
29
30
|
getConfig(): AppConfig;
|
|
30
|
-
createBrokerInstance(brokerType: string, label: string, values: Record<string, unknown
|
|
31
|
+
createBrokerInstance(brokerType: string, label: string, values: Record<string, unknown>, options?: BrokerInstanceCreateOptions): Promise<BrokerInstanceConfig>;
|
|
31
32
|
connectBrokerInstance(instanceId: string): Promise<void>;
|
|
32
33
|
updateBrokerInstance(instanceId: string, values: Record<string, unknown>, options?: BrokerInstanceUpdateOptions): Promise<void>;
|
|
33
34
|
syncBrokerInstance(instanceId: string): Promise<void>;
|
|
@@ -45,6 +45,7 @@ import type {
|
|
|
45
45
|
import { debugLog } from "../../utils/debug-log";
|
|
46
46
|
import { logFailure } from "../../utils/log-failure";
|
|
47
47
|
import { EventBus, type HostEvents } from "../event-bus";
|
|
48
|
+
import { externalPluginIcon } from "../external-runtime";
|
|
48
49
|
import { isReservedBuiltinPluginId } from "../ownership";
|
|
49
50
|
import { createPluginPersistence } from "../plugin-persistence";
|
|
50
51
|
import { createPluginTeamState } from "../team-state";
|
|
@@ -532,6 +533,12 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
532
533
|
return this.contributions.brokersMap.owners.get(brokerType);
|
|
533
534
|
}
|
|
534
535
|
|
|
536
|
+
/** The logo of the plugin that registered `brokerType` (its gloom.json `icon`), for lists of brokers. */
|
|
537
|
+
getBrokerLogo = (brokerType: string): string | null => {
|
|
538
|
+
const owner = this.getBrokerPluginId(brokerType);
|
|
539
|
+
return owner ? externalPluginIcon(owner) : null;
|
|
540
|
+
};
|
|
541
|
+
|
|
535
542
|
getShortcutPluginId(shortcutId: string): string | undefined {
|
|
536
543
|
return this.contributions.shortcutsMap.owners.get(shortcutId);
|
|
537
544
|
}
|
|
@@ -619,6 +626,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
619
626
|
getData: (ticker) => this.getData(ticker),
|
|
620
627
|
getTicker: (symbol) => this.getTicker(symbol),
|
|
621
628
|
getConfig: () => this.getConfig(),
|
|
629
|
+
getBrokerAdapter: this.getBrokerAdapter,
|
|
622
630
|
getPaneDef: (paneId) => contributions.panesMap.get(paneId),
|
|
623
631
|
marketData: this.marketData,
|
|
624
632
|
connectionHealth: this.connectionHealth,
|
|
@@ -630,7 +638,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
630
638
|
updateLayout: (layout) => this.updateLayout(layout),
|
|
631
639
|
resolvePaneTarget: (paneId) => this.resolvePaneTarget(paneId),
|
|
632
640
|
}),
|
|
633
|
-
createBrokerInstance: (brokerType, label, values) => this.createBrokerInstance(brokerType, label, values),
|
|
641
|
+
createBrokerInstance: (brokerType, label, values, options) => this.createBrokerInstance(brokerType, label, values, options),
|
|
634
642
|
updateBrokerInstance: this.updateBrokerInstance,
|
|
635
643
|
syncBrokerInstance: this.syncBrokerInstance,
|
|
636
644
|
removeBrokerInstance: this.removeBrokerInstance,
|
|
@@ -18,6 +18,11 @@ export interface PluginRuntimeAccess extends CapabilityInvoker, PluginRuntimeHos
|
|
|
18
18
|
getBrokerAdapter(brokerType: string): BrokerAdapter | null;
|
|
19
19
|
/** Every broker adapter installed now, read when called: plugins come and go. */
|
|
20
20
|
listBrokerAdapters(): BrokerAdapter[];
|
|
21
|
+
/**
|
|
22
|
+
* The logo of the plugin that registered a broker adapter, as an image URL
|
|
23
|
+
* for the desktop and the web; null when it has none.
|
|
24
|
+
*/
|
|
25
|
+
getBrokerLogo?(brokerType: string): string | null;
|
|
21
26
|
subscribeResumeState(pluginId: string, key: string, listener: () => void): () => void;
|
|
22
27
|
getResumeState<T = unknown>(pluginId: string, key: string, schemaVersion?: number): T | null;
|
|
23
28
|
setResumeState(pluginId: string, key: string, value: unknown, schemaVersion?: number): void;
|
|
@@ -78,11 +78,14 @@ export function useCapabilityInvoker(): CapabilityInvoker {
|
|
|
78
78
|
return usePluginRenderContext().runtime;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
const noBrokerLogo = (): string | null => null;
|
|
82
|
+
|
|
81
83
|
export function usePluginBrokerActions() {
|
|
82
84
|
const { runtime } = usePluginRenderContext();
|
|
83
85
|
return {
|
|
84
86
|
getBrokerAdapter: runtime.getBrokerAdapter,
|
|
85
87
|
listBrokerAdapters: runtime.listBrokerAdapters,
|
|
88
|
+
getBrokerLogo: runtime.getBrokerLogo ?? noBrokerLogo,
|
|
86
89
|
createBrokerInstance: runtime.createBrokerInstance,
|
|
87
90
|
connectBrokerInstance: runtime.connectBrokerInstance,
|
|
88
91
|
updateBrokerInstance: runtime.updateBrokerInstance,
|
|
@@ -8,6 +8,7 @@ import type { EarningsEvent } from "../types/data-provider";
|
|
|
8
8
|
import type { TickerRecord } from "../types/ticker";
|
|
9
9
|
import { colors, priceColor } from "../theme/colors";
|
|
10
10
|
import { formatQuoteAgeWithSource, resolveQuoteAgeTimestamp } from "../market-data/quotes/time";
|
|
11
|
+
import { delistingStateLabel } from "../market-data/quotes/delisting";
|
|
11
12
|
import { formatFeedTime, formatShortDate, parseDisplayDate, type ShortDateOptions } from "../utils/datetime-format";
|
|
12
13
|
import { convertCurrency, formatCompact, formatCompactAmount, formatNumber, formatPercentRaw } from "../utils/format";
|
|
13
14
|
import {
|
|
@@ -40,6 +41,7 @@ import { currentListingName } from "../tickers/listing-name";
|
|
|
40
41
|
import { PRICE_SPARKLINE_COLUMN_ID } from "../components/price-sparkline/view";
|
|
41
42
|
import { followLiveSparklinePrice, resolveSparklineHistory, sparklineValues } from "../components/price-sparkline/model";
|
|
42
43
|
import {
|
|
44
|
+
analystTargetDisplayCurrency,
|
|
43
45
|
listingDividendYield,
|
|
44
46
|
liveDividendYield,
|
|
45
47
|
liveFiftyTwoWeekRange,
|
|
@@ -67,6 +69,7 @@ import {
|
|
|
67
69
|
} from "./valuation-basis";
|
|
68
70
|
import { futuresQuantityText, futuresSettlementDisplay, type PortfolioFuturesTerms } from "./futures-positions";
|
|
69
71
|
import { isFiniteNumber } from "../utils/guards";
|
|
72
|
+
import { currencyCodeLabel } from "../utils/currency-units";
|
|
70
73
|
import {
|
|
71
74
|
allocationFigures,
|
|
72
75
|
formatAllocationDrift,
|
|
@@ -453,7 +456,7 @@ export function getColumnValue(
|
|
|
453
456
|
),
|
|
454
457
|
};
|
|
455
458
|
case "currency":
|
|
456
|
-
return { text: compactText(listedCurrency(positionCurrency, quoteCurrency, ticker, quote)) };
|
|
459
|
+
return { text: compactText(currencyCodeLabel(listedCurrency(positionCurrency, quoteCurrency, ticker, quote))) };
|
|
457
460
|
case "sector":
|
|
458
461
|
return { text: compactText(ticker.metadata.sector || financials?.profile?.sector) };
|
|
459
462
|
case "industry":
|
|
@@ -464,6 +467,8 @@ export function getColumnValue(
|
|
|
464
467
|
return resolvePortfolioPriceValue(headlineQuote, brokerMarkPrice, headlineQuote ? currentQuoteOptions : markOptions, col.width, quote?.marketState);
|
|
465
468
|
case "change":
|
|
466
469
|
if (!headlineQuote) return { text: "—" };
|
|
470
|
+
// A listing that no longer trades has no day's move: its last session's is years old.
|
|
471
|
+
if (quote?.delisting) return { text: delistingStateLabel(quote.delisting), color: colors.warning };
|
|
467
472
|
return {
|
|
468
473
|
text: formatSignedMarketPrice(headlineQuote.change, { ...currentQuoteOptions, maxWidth: col.width }),
|
|
469
474
|
color: marketChangeColor(headlineQuote.change, quote?.marketState),
|
|
@@ -489,6 +494,7 @@ export function getColumnValue(
|
|
|
489
494
|
return { text: `${formatCompact(quote?.bidSize)}/${formatCompact(quote?.askSize)}` };
|
|
490
495
|
}
|
|
491
496
|
case "change_pct": {
|
|
497
|
+
if (quote?.delisting) return { text: delistingStateLabel(quote.delisting), color: colors.warning };
|
|
492
498
|
const changePercent = headlineChangePercent(headlineQuote, quote);
|
|
493
499
|
return quote
|
|
494
500
|
? { text: formatPercentRaw(changePercent), color: marketChangeColor(changePercent, quote.marketState) }
|
|
@@ -573,6 +579,7 @@ export function getColumnValue(
|
|
|
573
579
|
return { text, color: /[1-9]/.test(text) ? priceColor(allocation.tradeValue!) : undefined };
|
|
574
580
|
}
|
|
575
581
|
case "day_pnl":
|
|
582
|
+
if (quote?.delisting) return { text: delistingStateLabel(quote.delisting), color: colors.warning };
|
|
576
583
|
if (activeQuote && isFiniteNumber(activeQuote.change) && Number.isFinite(positionMetrics.grossPriceUnits) && positionMetrics.grossPriceUnits !== 0) {
|
|
577
584
|
const dayPnl = toBaseQuote(totalPriceUnits * activeQuote.change);
|
|
578
585
|
return { text: formatCompactAmount(dayPnl, { signed: true }), color: priceColor(dayPnl) };
|
|
@@ -603,7 +610,8 @@ export function getColumnValue(
|
|
|
603
610
|
const analyst = mapData(ctx.analystResearch, ticker.metadata.ticker);
|
|
604
611
|
if (analyst.pending) return { text: "…" };
|
|
605
612
|
const value = targetValue(analyst.data);
|
|
606
|
-
|
|
613
|
+
// The target's own currency: one in pounds under a quote in pence reads £, not p.
|
|
614
|
+
const currency = analystTargetDisplayCurrency(analyst.data, quoteCurrency, displayQuote?.price);
|
|
607
615
|
return {
|
|
608
616
|
text: value == null
|
|
609
617
|
? "—"
|
|
@@ -730,9 +738,9 @@ export function getSortValue(
|
|
|
730
738
|
? (quote?.bidSize ?? 0) + (quote?.askSize ?? 0)
|
|
731
739
|
: null;
|
|
732
740
|
case "change":
|
|
733
|
-
return headlineQuote?.change ?? null;
|
|
741
|
+
return quote?.delisting ? null : headlineQuote?.change ?? null;
|
|
734
742
|
case "change_pct":
|
|
735
|
-
return headlineChangePercent(headlineQuote, quote) ?? null;
|
|
743
|
+
return quote?.delisting ? null : headlineChangePercent(headlineQuote, quote) ?? null;
|
|
736
744
|
case "volume":
|
|
737
745
|
return reportedQuoteVolume(quote);
|
|
738
746
|
case "dollar_volume": {
|
|
@@ -774,6 +782,7 @@ export function getSortValue(
|
|
|
774
782
|
case "trade_value":
|
|
775
783
|
return rowAllocation(ticker, activeQuote, baseMetrics, toBaseQuote, ctx).tradeValue;
|
|
776
784
|
case "day_pnl":
|
|
785
|
+
if (quote?.delisting) return null;
|
|
777
786
|
if (activeQuote && isFiniteNumber(activeQuote.change) && Number.isFinite(positionMetrics.grossPriceUnits) && positionMetrics.grossPriceUnits !== 0) {
|
|
778
787
|
return toBaseQuote(totalPriceUnits * activeQuote.change);
|
|
779
788
|
}
|
package/src/portfolio/cost-fx.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import type { CloudMarketResponse, CloudPricePointPayload } from "../api-client/types";
|
|
2
|
-
import type { CloudHistoryParams } from "../api-client/paths";
|
|
3
1
|
import { fxLegForCurrency } from "../market-data/coordinator/fx-legs";
|
|
2
|
+
import { fxCloseOnDate, type FxHistoryClient } from "../market-data/fx-closes";
|
|
4
3
|
import { heldCurrencyUnit } from "../utils/currency-units";
|
|
5
4
|
|
|
6
5
|
/** Reads a pair's daily bars; `apiClient` in the app, a fake in tests. */
|
|
7
|
-
export
|
|
8
|
-
getCloudHistory(symbol: string, exchange: string, params: CloudHistoryParams): Promise<CloudMarketResponse<CloudPricePointPayload[]>>;
|
|
9
|
-
}
|
|
6
|
+
export type CostFxHistoryClient = FxHistoryClient;
|
|
10
7
|
|
|
11
8
|
/** The rate a lot's cost was bought at: one unit of its currency in the portfolio's, at a dated daily close. */
|
|
12
9
|
export interface CostFxReading {
|
|
@@ -23,36 +20,16 @@ export class CostFxError extends Error {
|
|
|
23
20
|
}
|
|
24
21
|
}
|
|
25
22
|
|
|
26
|
-
const DAY_MS = 86_400_000;
|
|
27
|
-
/** How far before the purchase date a close is looked for: a weekend and a holiday fit inside it, a gap in the data does not. */
|
|
28
|
-
const LOOKBACK_DAYS = 7;
|
|
29
|
-
|
|
30
|
-
const isoDay = (time: number) => new Date(time).toISOString().slice(0, 10);
|
|
31
|
-
|
|
32
23
|
/**
|
|
33
24
|
* USD per unit of an ISO currency at its last completed daily close on or
|
|
34
|
-
* before `date`, looking back a week at most
|
|
35
|
-
*
|
|
25
|
+
* before `date`, looking back a week at most (`fxCloseOnDate`, as `fx --date`
|
|
26
|
+
* reads it). Null when the pair has no close in that week.
|
|
36
27
|
*/
|
|
37
28
|
async function usdPerUnitOn(code: string, date: string, client: CostFxHistoryClient, now: Date): Promise<CostFxReading | null> {
|
|
38
29
|
if (code === "USD") return { rate: 1, date };
|
|
39
30
|
const leg = fxLegForCurrency(code);
|
|
40
31
|
if (!leg) throw new CostFxError(`No daily FX history is served for ${code}`);
|
|
41
|
-
|
|
42
|
-
const response = await client.getCloudHistory(leg.instrument.symbol, leg.instrument.exchange ?? "", {
|
|
43
|
-
interval: "1day", startDate: isoDay(day - LOOKBACK_DAYS * DAY_MS), endDate: isoDay(day + DAY_MS), outputsize: LOOKBACK_DAYS + 4,
|
|
44
|
-
});
|
|
45
|
-
if (response.status !== "success" && response.status !== "partial") return null;
|
|
46
|
-
const today = isoDay(now.getTime());
|
|
47
|
-
let best: CostFxReading | null = null;
|
|
48
|
-
for (const row of Array.isArray(response.data) ? response.data : []) {
|
|
49
|
-
const at = typeof row?.date === "string" ? Date.parse(row.date) : Number.NaN;
|
|
50
|
-
if (!Number.isFinite(at) || !Number.isFinite(row.close) || row.close <= 0) continue;
|
|
51
|
-
const rowDay = isoDay(at);
|
|
52
|
-
if (rowDay > date || rowDay >= today || Date.parse(`${rowDay}T00:00:00Z`) < day - LOOKBACK_DAYS * DAY_MS) continue;
|
|
53
|
-
if (!best || rowDay > best.date) best = { rate: leg.invert ? 1 / row.close : row.close, date: rowDay };
|
|
54
|
-
}
|
|
55
|
-
return best;
|
|
32
|
+
return fxCloseOnDate(leg, date, client, now);
|
|
56
33
|
}
|
|
57
34
|
|
|
58
35
|
/**
|
|
@@ -4,6 +4,7 @@ exports[`the runtime exports of every public subpath are the reviewed ones 1`] =
|
|
|
4
4
|
{
|
|
5
5
|
".": [],
|
|
6
6
|
"./broker": [
|
|
7
|
+
"LOCAL_BROKER_MARKET_DATA",
|
|
7
8
|
"PRESERVED_PASSWORD_HINT",
|
|
8
9
|
"cloudBrokerLink",
|
|
9
10
|
"getBrokerRemoteClient",
|
|
@@ -53,6 +54,7 @@ exports[`the runtime exports of every public subpath are the reviewed ones 1`] =
|
|
|
53
54
|
"CompositeChart",
|
|
54
55
|
"ConfirmDialog",
|
|
55
56
|
"CurveSurface",
|
|
57
|
+
"DELAYED_SEGMENT",
|
|
56
58
|
"DataTable",
|
|
57
59
|
"DataTableStackView",
|
|
58
60
|
"DataTableView",
|
|
@@ -132,6 +134,7 @@ exports[`the runtime exports of every public subpath are the reviewed ones 1`] =
|
|
|
132
134
|
"curveSlope",
|
|
133
135
|
"curveStrip",
|
|
134
136
|
"curveSurfaceMinRows",
|
|
137
|
+
"delayedFooterSegment",
|
|
135
138
|
"fieldFrameStyle",
|
|
136
139
|
"fieldGridColumns",
|
|
137
140
|
"fieldGridRows",
|
|
@@ -159,6 +162,7 @@ exports[`the runtime exports of every public subpath are the reviewed ones 1`] =
|
|
|
159
162
|
"sortStackItems",
|
|
160
163
|
"spanAxisFormatter",
|
|
161
164
|
"spanDigits",
|
|
165
|
+
"splitDelayedStatus",
|
|
162
166
|
"stackedSeries",
|
|
163
167
|
"statGridColumns",
|
|
164
168
|
"statGridRows",
|
|
@@ -201,6 +205,7 @@ exports[`the runtime exports of every public subpath are the reviewed ones 1`] =
|
|
|
201
205
|
],
|
|
202
206
|
"./market-data": [
|
|
203
207
|
"computeTickerPriceReturns",
|
|
208
|
+
"createProviderMiss",
|
|
204
209
|
"currencyMinorDigits",
|
|
205
210
|
"formatCompactMarketPriceWithCurrency",
|
|
206
211
|
"formatMarketChangeWithCurrency",
|
package/src/public/broker.ts
CHANGED
|
@@ -20,6 +20,14 @@ export type { BrokerRemoteClient } from "../brokers/remote-broker-adapter";
|
|
|
20
20
|
export type { ResourceStore } from "../data/resource-store";
|
|
21
21
|
export { resolveTickerFinancialsForInstrument } from "../market-data/coordinator";
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* This host's Cloud sync honors `BrokerAdapter.localMarketData`. A broker whose market data must
|
|
25
|
+
* not reach Gloom Cloud reads it to tell an older host, which would sync its quotes and where it
|
|
26
|
+
* still has to ask for Cloud to be off: `import * as broker from "gloomberb/broker"`, then
|
|
27
|
+
* `broker.LOCAL_BROKER_MARKET_DATA === true`.
|
|
28
|
+
*/
|
|
29
|
+
export const LOCAL_BROKER_MARKET_DATA = true;
|
|
30
|
+
|
|
23
31
|
/**
|
|
24
32
|
* The app's cached-resource store. Set by the host at startup and cleared on
|
|
25
33
|
* teardown; null before services are constructed.
|
|
@@ -25,6 +25,11 @@ export type {
|
|
|
25
25
|
AssetDisplayContext, AssetDisplayKind, MarketFormatOptions, StablePriceContext,
|
|
26
26
|
} from "../market-data/market/format";
|
|
27
27
|
|
|
28
|
+
// How a provider says it has nothing for a symbol, as built-in sources do: the
|
|
29
|
+
// router tries the next provider, does not log it as a failure, and shows the
|
|
30
|
+
// reason where the data would be.
|
|
31
|
+
export { createProviderMiss } from "../sources/provider-errors";
|
|
32
|
+
|
|
28
33
|
// The coordinator the app loads snapshots through, so a plugin pane that shows
|
|
29
34
|
// its own list of tickers warms the same cache the rest of the app reads.
|
|
30
35
|
export { getSharedMarketDataCoordinator } from "../market-data/coordinator";
|
|
@@ -35,9 +35,14 @@ export function controlBorderColor(focused = false, active = false, palette: The
|
|
|
35
35
|
* casts a shadow. The icon button is `.gloom-icon-button` in styles.css.
|
|
36
36
|
*/
|
|
37
37
|
export function buttonPalette(
|
|
38
|
-
props: Pick<ButtonProps, "variant" | "active" | "disabled">,
|
|
38
|
+
props: Pick<ButtonProps, "variant" | "active" | "disabled" | "tone">,
|
|
39
39
|
palette: ThemeColors = colors,
|
|
40
40
|
) {
|
|
41
|
+
if (props.variant === "quiet") return {
|
|
42
|
+
fg: props.disabled ? palette.textMuted : props.active ? palette.textBright : palette.textDim,
|
|
43
|
+
bg: props.active ? selectedPanelFill(palette) : "transparent",
|
|
44
|
+
border: "transparent",
|
|
45
|
+
};
|
|
41
46
|
if (props.variant === "plain" || props.variant === "ghost") {
|
|
42
47
|
return {
|
|
43
48
|
fg: props.disabled ? palette.textMuted : props.active ? palette.textBright : palette.textDim,
|
|
@@ -52,6 +57,7 @@ export function buttonPalette(
|
|
|
52
57
|
border: controlBorderColour(palette),
|
|
53
58
|
};
|
|
54
59
|
}
|
|
60
|
+
if (props.tone && props.variant === "primary") return { fg: palette.bg, bg: palette[props.tone], border: props.active ? palette.textBright : palette[props.tone] };
|
|
55
61
|
if (props.active) {
|
|
56
62
|
return {
|
|
57
63
|
fg: palette.selectedText,
|