gloomberb 0.18.0 → 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/market-movers.ts +7 -0
- package/src/api-client/types.ts +14 -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/fred-series.ts +15 -5
- package/src/cli/commands/market.ts +290 -159
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/overview.ts +31 -6
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +105 -52
- 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 +31 -7
- 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/symbol-list.ts +51 -16
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +22 -7
- 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 +64 -16
- package/src/components/chart/composite/price-axis-labels.tsx +2 -1
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/command-bar/workflow/collection-actions.ts +4 -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/onboarding/wizard-positions.ts +8 -0
- 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 +57 -22
- 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/analytics/index.tsx +1 -1
- package/src/plugins/builtin/analytics/risk-client.ts +44 -11
- package/src/plugins/builtin/analytics/risk-fx.ts +61 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +10 -1
- package/src/plugins/builtin/analytics/risk-model.ts +79 -16
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/analytics/risk-test-data.ts +3 -0
- 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/cloud/team/pane-sections.tsx +10 -3
- package/src/plugins/builtin/cloud/team/pane.tsx +30 -9
- 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/custom-view/loader.ts +3 -2
- package/src/plugins/builtin/custom-view/pane.tsx +5 -4
- package/src/plugins/builtin/custom-view/view-spec.ts +1 -0
- 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 +38 -2
- package/src/plugins/builtin/econ/fred-catalog.ts +6 -2
- package/src/plugins/builtin/econ/fred-series-map.ts +12 -1
- 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/executives/data.ts +24 -8
- package/src/plugins/builtin/executives/index.tsx +2 -18
- package/src/plugins/builtin/executives/pane.tsx +34 -10
- 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-movers/client.ts +2 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -3
- package/src/plugins/builtin/market-movers/session-body.tsx +27 -11
- package/src/plugins/builtin/market-movers/session.ts +25 -10
- 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 +33 -13
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/letters/client.ts +1 -0
- package/src/plugins/builtin/news/wire/news/command-bar-search.ts +6 -3
- package/src/plugins/builtin/news/wire/news/detail-view.tsx +2 -1
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -1
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/feed-xml.ts +6 -1
- package/src/plugins/builtin/news/wire/rss/managed-feed.ts +1 -0
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +33 -8
- package/src/plugins/builtin/notes/export.ts +49 -3
- package/src/plugins/builtin/notes/files.ts +14 -3
- package/src/plugins/builtin/notes/index.tsx +3 -3
- package/src/plugins/builtin/notes/quick-notes-pane.tsx +45 -10
- package/src/plugins/builtin/notes/store.ts +13 -3
- 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/bulk-symbols.ts +8 -7
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +166 -15
- package/src/plugins/builtin/portfolio-list/cli/render.ts +69 -15
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +2 -2
- package/src/plugins/builtin/portfolio-list/headless.ts +3 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +44 -17
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -1
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +42 -7
- 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/broker-reconnect.ts +105 -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-feature-wall.tsx +9 -4
- 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/thirteenf/api.ts +4 -1
- package/src/plugins/builtin/thirteenf/data.ts +5 -1
- package/src/plugins/builtin/thirteenf/headless.ts +3 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +8 -3
- 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 +61 -0
- package/src/portfolio/mutations.ts +16 -2
- package/src/portfolio/position-metrics.ts +45 -4
- package/src/portfolio/position-quantity.ts +43 -0
- 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/fred-series.ts +27 -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 +63 -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/team/collections.ts +2 -0
- package/src/tickers/listing-fate.ts +27 -8
- package/src/tickers/metadata.ts +10 -0
- 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/ticker.ts +9 -0
- 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 +41 -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 { isFiniteNumber } from "../../utils/guards";
|
|
|
4
4
|
import { canonicalExchange, isKnownExchangeCode } from "../../utils/exchanges";
|
|
5
5
|
import { quoteTradingDay } from "../quotes/day-range";
|
|
6
6
|
import { isQuoteBeforeLatestSession } from "../quotes/freshness";
|
|
7
|
+
import { delistingLabel, delistingStateLabel } from "../quotes/delisting";
|
|
7
8
|
import { preMarketOpenOn, regularSessionCloseOn } from "./freshness";
|
|
8
9
|
|
|
9
10
|
const CLOSED_CHANGE_MUTING_RATIO = 0.55;
|
|
@@ -66,6 +67,30 @@ export function marketStateLabel(state: MarketState): string {
|
|
|
66
67
|
const STALE_SESSION_LABEL = "STALE";
|
|
67
68
|
|
|
68
69
|
const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const;
|
|
70
|
+
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] as const;
|
|
71
|
+
|
|
72
|
+
/** The books an extended session's bid and ask belong to, as `ticker` names them. */
|
|
73
|
+
const EXTENDED_BOOK_LABELS: Partial<Record<MarketState, string>> = { PRE: "pre-market book", POST: "after-hours book" };
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* What a quote's bid and ask are when the regular session is not on: the
|
|
77
|
+
* pre-market or after-hours book, or a closed market's last quote with the
|
|
78
|
+
* day it is from (ITA's $202.00 against $208.74 around a $206.65 close is no
|
|
79
|
+
* open book). Null in the regular session or without a known state, when the
|
|
80
|
+
* book is the live one. `ticker` prints it after Bid / Ask; the overview's
|
|
81
|
+
* header already names the session, so its quote book does not repeat it.
|
|
82
|
+
*/
|
|
83
|
+
export function quoteBookSessionLabel(quote: Quote): string | null {
|
|
84
|
+
const state = quote.marketState;
|
|
85
|
+
if (!state || state === "REGULAR") return null;
|
|
86
|
+
const extended = EXTENDED_BOOK_LABELS[state];
|
|
87
|
+
if (extended) return extended;
|
|
88
|
+
const day = quoteTradingDay(quote);
|
|
89
|
+
const date = day ? new Date(`${day}T00:00:00Z`) : null;
|
|
90
|
+
return date && Number.isFinite(date.getTime())
|
|
91
|
+
? `last quote, ${WEEKDAYS[date.getUTCDay()]} ${date.getUTCDate()} ${MONTHS[date.getUTCMonth()]}`
|
|
92
|
+
: "last quote";
|
|
93
|
+
}
|
|
69
94
|
|
|
70
95
|
/** `Thu close`: the weekday of the session whose close a quote still stands at. */
|
|
71
96
|
function closeLabel(date: string): string {
|
|
@@ -75,9 +100,12 @@ function closeLabel(date: string): string {
|
|
|
75
100
|
/**
|
|
76
101
|
* A quote's market state as a status cell names it (marketStateLabel), STALE
|
|
77
102
|
* for a print older than its venue's latest session, and the close it stands
|
|
78
|
-
* at (`Thu close`) in a pre-market nothing has traded in yet.
|
|
103
|
+
* at (`Thu close`) in a pre-market nothing has traded in yet. A listing that
|
|
104
|
+
* no longer trades says so with its last session ("Delisted Oct 27, 2022"),
|
|
105
|
+
* whatever state its last print carries.
|
|
79
106
|
*/
|
|
80
107
|
export function quoteMarketStateLabel(quote: Quote | null | undefined, now = Date.now()): string | null {
|
|
108
|
+
if (quote?.delisting) return delistingLabel(quote.delisting);
|
|
81
109
|
if (!quote?.marketState) return null;
|
|
82
110
|
if (isQuoteBeforeLatestSession(quote, now)) return STALE_SESSION_LABEL;
|
|
83
111
|
const awaiting = preMarketCloseAwaitingPrint(quote);
|
|
@@ -160,8 +188,9 @@ function overnightSessionDisplay(quote: Quote): ExtendedSessionDisplay | null {
|
|
|
160
188
|
}
|
|
161
189
|
|
|
162
190
|
/**
|
|
163
|
-
* A quote's session as a status cell names it:
|
|
164
|
-
*
|
|
191
|
+
* A quote's session as a status cell names it: "Delisted Oct 27, 2022" (or
|
|
192
|
+
* "Last traded ...") for a listing that no longer trades, STALE when its last
|
|
193
|
+
* print is older than its venue's latest completed session (isQuoteBeforeLatestSession),
|
|
165
194
|
* the overnight session while the quote is priced by it, the close it stands
|
|
166
195
|
* at (`Thu close`) in a pre-market nothing has traded in yet, else its market
|
|
167
196
|
* state (marketStateLabel). The exchange stays closed overnight, so the state
|
|
@@ -174,13 +203,15 @@ export function quoteSessionLabel(quote: Quote | null | undefined, now = Date.no
|
|
|
174
203
|
/**
|
|
175
204
|
* quoteSessionLabel as an export keeps it, one of a fixed set: the market
|
|
176
205
|
* state (`PRE-MKT`) in a pre-market nothing has traded in yet, where the
|
|
177
|
-
* status cell names the close (`Thu close`)
|
|
206
|
+
* status cell names the close (`Thu close`), and DELISTED or INACTIVE for a
|
|
207
|
+
* listing that no longer trades, its date being the export's own field.
|
|
178
208
|
*/
|
|
179
209
|
export function quoteSessionStateLabel(quote: Quote | null | undefined, now = Date.now()): string | null {
|
|
180
210
|
return sessionLabel(quote, now, false);
|
|
181
211
|
}
|
|
182
212
|
|
|
183
213
|
function sessionLabel(quote: Quote | null | undefined, now: number, standingClose: boolean): string | null {
|
|
214
|
+
if (quote?.delisting) return standingClose ? delistingLabel(quote.delisting) : delistingStateLabel(quote.delisting);
|
|
184
215
|
if (!quote?.marketState) return null;
|
|
185
216
|
if (isQuoteBeforeLatestSession(quote, now)) return STALE_SESSION_LABEL;
|
|
186
217
|
if (overnightSessionDisplay(quote)) return "OVERNIGHT";
|
|
@@ -189,11 +220,13 @@ function sessionLabel(quote: Quote | null | undefined, now: number, standingClos
|
|
|
189
220
|
}
|
|
190
221
|
|
|
191
222
|
/**
|
|
192
|
-
* The color of quoteSessionLabel: STALE
|
|
193
|
-
* mark uses, an extended session's while the
|
|
194
|
-
* quote, a closed session's for the close a
|
|
223
|
+
* The color of quoteSessionLabel: STALE, and a listing that no longer trades,
|
|
224
|
+
* in the warning color every stale mark uses, an extended session's while the
|
|
225
|
+
* overnight session prices the quote, a closed session's for the close a
|
|
226
|
+
* pre-market still stands at.
|
|
195
227
|
*/
|
|
196
228
|
export function quoteSessionColor(quote: Quote | null | undefined, palette: ThemeColors = colors, now = Date.now()): string {
|
|
229
|
+
if (quote?.delisting) return palette.warning;
|
|
197
230
|
if (!quote?.marketState) return palette.textDim;
|
|
198
231
|
if (isQuoteBeforeLatestSession(quote, now)) return palette.warning;
|
|
199
232
|
if (overnightSessionDisplay(quote)) return marketStateColor("POST", palette);
|
|
@@ -206,7 +239,7 @@ export function quoteSessionColor(quote: Quote | null | undefined, palette: Them
|
|
|
206
239
|
* with no known session rather than an open market.
|
|
207
240
|
*/
|
|
208
241
|
export function quoteDotMarketState(quote: Quote | null | undefined, now = Date.now()): MarketState | undefined {
|
|
209
|
-
return quote && !isQuoteBeforeLatestSession(quote, now) ? quote.marketState : undefined;
|
|
242
|
+
return quote && !quote.delisting && !isQuoteBeforeLatestSession(quote, now) ? quote.marketState : undefined;
|
|
210
243
|
}
|
|
211
244
|
|
|
212
245
|
/** Closed prices are final snapshots, so they should not look live or directional. */
|
|
@@ -378,6 +411,8 @@ export function quoteHeadlineTime(quote: Quote): number {
|
|
|
378
411
|
*/
|
|
379
412
|
export function getRegularSessionDisplay(quote: Quote | null | undefined): ActiveQuoteDisplay | null {
|
|
380
413
|
if (!quote) return null;
|
|
414
|
+
// A listing that no longer trades: its last session's close, which moves no more.
|
|
415
|
+
if (quote.delisting) return { price: quote.price };
|
|
381
416
|
const session = completedRegularSession(quote);
|
|
382
417
|
if (session == null) return { price: quote.price, change: quote.change, changePercent: quote.changePercent };
|
|
383
418
|
const { close, reported } = session;
|
|
@@ -390,6 +425,17 @@ export function getRegularSessionDisplay(quote: Quote | null | undefined): Activ
|
|
|
390
425
|
return { price: close, change, changePercent: (change / reference) * 100 };
|
|
391
426
|
}
|
|
392
427
|
|
|
428
|
+
/**
|
|
429
|
+
* The exchange-local session getRegularSessionDisplay's move belongs to: the
|
|
430
|
+
* completed session's while the headline still shows its close (a pre-market
|
|
431
|
+
* quote before anything trades, whose own session is the day ahead), the
|
|
432
|
+
* quote's declared session otherwise. Null when the quote names neither.
|
|
433
|
+
*/
|
|
434
|
+
export function regularSessionDisplayDate(quote: Quote | null | undefined): string | null {
|
|
435
|
+
if (!quote) return null;
|
|
436
|
+
return completedRegularSession(quote)?.date ?? quote.changeSessionDate ?? null;
|
|
437
|
+
}
|
|
438
|
+
|
|
393
439
|
/**
|
|
394
440
|
* getRegularSessionDisplay once the regular session is over (and in the
|
|
395
441
|
* pre-market, while the quote carries the last session's close and move), null
|
|
@@ -429,7 +475,7 @@ export function getRegularSessionBarClose(quote: Quote | null | undefined): { pr
|
|
|
429
475
|
* when there is no extended print to show.
|
|
430
476
|
*/
|
|
431
477
|
export function getExtendedSessionDisplay(quote: Quote | null | undefined): ExtendedSessionDisplay | null {
|
|
432
|
-
if (!quote) return null;
|
|
478
|
+
if (!quote || quote.delisting) return null;
|
|
433
479
|
const overnight = overnightSessionDisplay(quote);
|
|
434
480
|
if (overnight) return overnight;
|
|
435
481
|
const state = quote.marketState;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { barLastSession } from "./bar-period";
|
|
2
|
+
|
|
3
|
+
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] as const;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The session a year-to-date figure dated `day` is measured from at `venue`:
|
|
7
|
+
* the venue's last close of the year before, `YYYY-MM-DD`. 2026 runs from
|
|
8
|
+
* Wed 31 Dec 2025, 2023 from Fri 30 Dec 2022 (31 Dec was a Saturday). A venue
|
|
9
|
+
* whose sessions are unknown reads December's last weekday.
|
|
10
|
+
*/
|
|
11
|
+
export function yearToDateBaseDay(day: string, venue: string | undefined, now = Date.now()): string | null {
|
|
12
|
+
if (!/^\d{4}-\d{2}-\d{2}/.test(day)) return null;
|
|
13
|
+
return barLastSession(`${Number(day.slice(0, 4)) - 1}-12-01`, "month", venue, now);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** That close as a column header names it, `31 Dec`. */
|
|
17
|
+
export function yearToDateBaseLabel(day: string, venue: string | undefined, now = Date.now()): string {
|
|
18
|
+
const base = yearToDateBaseDay(day, venue, now) ?? `${Number(day.slice(0, 4)) - 1}-12-31`;
|
|
19
|
+
const date = new Date(`${base}T00:00:00Z`);
|
|
20
|
+
return Number.isFinite(date.getTime()) ? `${date.getUTCDate()} ${MONTHS[date.getUTCMonth()]}` : "31 Dec";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Where a year-to-date run of bars starts: the last bar whose close is from on
|
|
25
|
+
* or before 31 Dec of the year before the newest bar, the close the year is
|
|
26
|
+
* measured from, never the year's first session. A weekly bar (dated by its
|
|
27
|
+
* week's start) closes as its week ends and a monthly one as its month does,
|
|
28
|
+
* so the week of Mon 29 Dec 2025, which closes on 2 Jan, is after it. 0 when
|
|
29
|
+
* every bar is from the year itself (a listing that started trading in it);
|
|
30
|
+
* -1 without bars.
|
|
31
|
+
*/
|
|
32
|
+
export function yearToDateStartIndex(dates: readonly string[], interval?: string | null): number {
|
|
33
|
+
const last = dates.at(-1);
|
|
34
|
+
if (!last) return -1;
|
|
35
|
+
const yearEnd = `${Number(last.slice(0, 4)) - 1}-12-31`;
|
|
36
|
+
const closes = (date: string) => {
|
|
37
|
+
const day = Date.parse(`${date.slice(0, 10)}T00:00:00Z`);
|
|
38
|
+
if (interval === "1wk") return new Date(day + 6 * 86_400_000).toISOString().slice(0, 10);
|
|
39
|
+
if (interval === "1mo") return new Date(Date.UTC(new Date(day).getUTCFullYear(), new Date(day).getUTCMonth() + 1, 0)).toISOString().slice(0, 10);
|
|
40
|
+
return date.slice(0, 10);
|
|
41
|
+
};
|
|
42
|
+
for (let index = dates.length - 1; index >= 0; index -= 1) {
|
|
43
|
+
if (closes(dates[index]!) <= yearEnd) return index;
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
@@ -212,12 +212,24 @@ export function mergeQuoteContribution(
|
|
|
212
212
|
overnightPriceKind: next.overnightPriceKind,
|
|
213
213
|
// The board this observation was read on; one read on the listing's own line names none.
|
|
214
214
|
tradingExchangeName: next.tradingExchangeName,
|
|
215
|
+
// Whether the listing no longer trades is said with each observation: a symbol that trades again drops it.
|
|
216
|
+
delisting: next.delisting,
|
|
215
217
|
regularClose: next.regularClose,
|
|
216
218
|
regularCloseSessionDate: next.regularClose != null ? next.regularCloseSessionDate : undefined,
|
|
217
219
|
regularChange: next.regularClose != null ? next.regularChange : undefined,
|
|
218
220
|
regularChangePercent: next.regularClose != null ? next.regularChangePercent : undefined,
|
|
219
221
|
};
|
|
220
222
|
|
|
223
|
+
// What this observation's move is from, when not its previous close (a coin's
|
|
224
|
+
// 24 hours, Linear: GLO-1089), is said with each one: a quote back on its
|
|
225
|
+
// previous close drops an earlier "24h", and one on 24 hours drops the
|
|
226
|
+
// earlier close its move no longer runs from.
|
|
227
|
+
if (next.changeBasis == null) {
|
|
228
|
+
delete merged.changeBasis;
|
|
229
|
+
} else if (next.previousClose === undefined) {
|
|
230
|
+
delete merged.previousClose;
|
|
231
|
+
}
|
|
232
|
+
|
|
221
233
|
if (isOtherTradingDay(current, next)) {
|
|
222
234
|
const record = merged as unknown as Record<string, unknown>;
|
|
223
235
|
for (const field of TRADING_DAY_FIELDS) {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ListingDelisting } from "../../types/financials";
|
|
2
|
+
import { tf } from "../../i18n";
|
|
3
|
+
import { formatShortDate } from "../../utils/datetime-format";
|
|
4
|
+
|
|
5
|
+
const DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A listing's delisting as Gloom Cloud sends it, or undefined for anything
|
|
9
|
+
* else: the service's answer is untrusted, and a malformed one must not mark
|
|
10
|
+
* a listing that trades.
|
|
11
|
+
*/
|
|
12
|
+
export function parseListingDelisting(value: unknown): ListingDelisting | undefined {
|
|
13
|
+
if (!value || typeof value !== "object") return undefined;
|
|
14
|
+
const { status, lastTradeDate, historyStart } = value as Record<string, unknown>;
|
|
15
|
+
if (status !== "delisted" && status !== "inactive") return undefined;
|
|
16
|
+
if (typeof lastTradeDate !== "string" || !DATE.test(lastTradeDate)) return undefined;
|
|
17
|
+
if (typeof historyStart !== "string" || !DATE.test(historyStart) || historyStart > lastTradeDate) return undefined;
|
|
18
|
+
return { status, lastTradeDate, historyStart };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* What a listing that no longer trades reads as beside its symbol, always with
|
|
23
|
+
* its last session: "Delisted Oct 27, 2022" when the exchange's removal is on
|
|
24
|
+
* record, "Last traded Apr 28, 2023" when only the last trade is known.
|
|
25
|
+
*/
|
|
26
|
+
export function delistingLabel(delisting: ListingDelisting): string {
|
|
27
|
+
const date = formatShortDate(delisting.lastTradeDate, { utc: true });
|
|
28
|
+
return delisting.status === "delisted" ? tf("Delisted {date}", { date }) : tf("Last traded {date}", { date });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The state alone, for a cell that names a session in a word (OPEN, CLOSED): DELISTED or INACTIVE. */
|
|
32
|
+
export function delistingStateLabel(delisting: ListingDelisting): "DELISTED" | "INACTIVE" {
|
|
33
|
+
return delisting.status === "delisted" ? "DELISTED" : "INACTIVE";
|
|
34
|
+
}
|
|
@@ -189,6 +189,29 @@ export function liveFiftyTwoWeekRange(
|
|
|
189
189
|
return high > low ? { low, high } : null;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
/**
|
|
193
|
+
* The currency an analyst's targets are written in: the one the target or its
|
|
194
|
+
* research names, whatever the quote's (a target in pounds under a quote in
|
|
195
|
+
* pence stays in pounds). When neither names one, the quote's, but its major
|
|
196
|
+
* unit when the quote is in a minor one (GBp, USX) and the target's reference
|
|
197
|
+
* price, or the target itself, is a hundred times under the quote's price.
|
|
198
|
+
*/
|
|
199
|
+
export function analystTargetDisplayCurrency(
|
|
200
|
+
analyst: AnalystResearchData | null | undefined,
|
|
201
|
+
quoteCurrency: string,
|
|
202
|
+
currentPrice: number | null | undefined,
|
|
203
|
+
): string {
|
|
204
|
+
const named = analyst?.priceTarget?.currency?.trim() || analyst?.currency?.trim();
|
|
205
|
+
if (named) return named;
|
|
206
|
+
const unit = resolveCurrencyUnit(quoteCurrency);
|
|
207
|
+
if (unit.divisor === 1 || !positive(currentPrice)) return quoteCurrency;
|
|
208
|
+
const target = analyst?.priceTarget;
|
|
209
|
+
const sample = [target?.current, target?.average, target?.median].find(positive);
|
|
210
|
+
return sample != null && sample < currentPrice
|
|
211
|
+
&& hasLikelyQuoteUnitMismatch({ currency: quoteCurrency, price: sample }, { currency: quoteCurrency, price: currentPrice })
|
|
212
|
+
? unit.currency : quoteCurrency;
|
|
213
|
+
}
|
|
214
|
+
|
|
192
215
|
/**
|
|
193
216
|
* The price an analyst target is compared with. The current price when the
|
|
194
217
|
* target is quoted in the listing's currency; otherwise the research snapshot's
|
|
@@ -213,7 +213,9 @@ function assignDailyChangeFields(
|
|
|
213
213
|
const referenceProvider = buildDailyReferenceCandidates(candidates, priceProvider)[0];
|
|
214
214
|
if (!referenceProvider) {
|
|
215
215
|
pickField(target, provenance, "previousClose", candidates);
|
|
216
|
-
|
|
216
|
+
// A move not from a previous close says what it is from (a coin's 24 hours,
|
|
217
|
+
// Linear: GLO-1089), and that is said by the source whose move it is.
|
|
218
|
+
assignField(target, provenance, "changeBasis", pickField(target, provenance, "change", candidates));
|
|
217
219
|
pickField(target, provenance, "changePercent", candidates);
|
|
218
220
|
return;
|
|
219
221
|
}
|
|
@@ -222,7 +224,7 @@ function assignDailyChangeFields(
|
|
|
222
224
|
const price = target.price;
|
|
223
225
|
const previousClose = target.previousClose;
|
|
224
226
|
if (!finitePositiveNumber(price) || !finitePositiveNumber(previousClose)) {
|
|
225
|
-
pickField(target, provenance, "change", candidates);
|
|
227
|
+
assignField(target, provenance, "changeBasis", pickField(target, provenance, "change", candidates));
|
|
226
228
|
pickField(target, provenance, "changePercent", candidates);
|
|
227
229
|
return;
|
|
228
230
|
}
|
|
@@ -495,6 +497,8 @@ export function resolveCanonicalQuote(
|
|
|
495
497
|
currency: String(resolved.currency ?? priceProvider?.currency ?? contributions[0]!.currency ?? ""),
|
|
496
498
|
change: resolved.change ?? Number.NaN,
|
|
497
499
|
changePercent: resolved.changePercent ?? Number.NaN,
|
|
500
|
+
// What that move is from when it is not previousClose; absent when it is.
|
|
501
|
+
...(resolved.changeBasis ? { changeBasis: resolved.changeBasis } : {}),
|
|
498
502
|
previousClose: resolved.previousClose as Quote["previousClose"],
|
|
499
503
|
regularClose: priceProvider?.regularClose,
|
|
500
504
|
regularCloseSessionDate: priceProvider?.regularClose != null ? priceProvider.regularCloseSessionDate : undefined,
|
|
@@ -534,6 +538,8 @@ export function resolveCanonicalQuote(
|
|
|
534
538
|
overnightPriceKind: priceProvider?.overnightPriceKind,
|
|
535
539
|
// An index's regular sessions come with the print they date.
|
|
536
540
|
indexSession: priceProvider?.indexSession,
|
|
541
|
+
// A listing that no longer trades: the price is its last trade.
|
|
542
|
+
delisting: priceProvider?.delisting,
|
|
537
543
|
bid: resolved.bid as Quote["bid"],
|
|
538
544
|
ask: resolved.ask as Quote["ask"],
|
|
539
545
|
bidSize: resolved.bidSize as Quote["bidSize"],
|
|
@@ -161,6 +161,8 @@ export function taxEquivalentYield(yieldPercent: number, combinedRatePercent: nu
|
|
|
161
161
|
export interface TaxEquivalentAnalysis {
|
|
162
162
|
symbol: string;
|
|
163
163
|
rates: TaxRates;
|
|
164
|
+
/** A failed prerequisite check, distinct from a confirmed inapplicable fund. */
|
|
165
|
+
checkError?: string;
|
|
164
166
|
/** Why there is no tax-equivalent yield: the fund is not a muni fund, or nothing classes it. */
|
|
165
167
|
notApplicable?: string;
|
|
166
168
|
/** The rate the yield is grossed up by: federal, plus the state's for one of its own funds. */
|
|
@@ -187,16 +189,18 @@ function formatRate(value: number): string {
|
|
|
187
189
|
* fund its sponsor classes as municipal has one; any other says why not.
|
|
188
190
|
*/
|
|
189
191
|
export function analyzeTaxEquivalent({
|
|
190
|
-
symbol, bond, rates, distribution, treasury,
|
|
192
|
+
symbol, bond, rates, distribution, treasury, checkError,
|
|
191
193
|
}: {
|
|
192
194
|
symbol: string;
|
|
193
195
|
bond: FundBondFacts | null | undefined;
|
|
194
196
|
rates: TaxRates;
|
|
197
|
+
checkError?: string;
|
|
195
198
|
/** The distribution yield in percent and its label, for a fund without a SEC yield. */
|
|
196
199
|
distribution?: YieldBasis | null;
|
|
197
200
|
/** The Treasury curve's points, or null when it could not be read. */
|
|
198
201
|
treasury?: readonly TreasuryTenor[] | null;
|
|
199
202
|
}): TaxEquivalentAnalysis {
|
|
203
|
+
if (checkError) return { symbol, rates, checkError };
|
|
200
204
|
if (!bond) {
|
|
201
205
|
return { symbol, rates, notApplicable: `no tax-equivalent yield: ${symbol} is not a bond fund with sponsor figures (iShares, Vanguard and SPDR bond ETFs have them)` };
|
|
202
206
|
}
|
|
@@ -257,6 +261,9 @@ export interface TaxEquivalentLine {
|
|
|
257
261
|
* Muni/Treasury ratio 81.0% (SEC yield 4.04% / 7Y Treasury 4.99% on 2026-10-16, the tenor nearest its 7.13-year duration)
|
|
258
262
|
*/
|
|
259
263
|
export function taxEquivalentLines(analysis: TaxEquivalentAnalysis): TaxEquivalentLine[] {
|
|
264
|
+
if (analysis.checkError) {
|
|
265
|
+
return [{ key: "taxEquivalentYield", label: "Tax-equivalent yield", text: analysis.checkError, value: null }];
|
|
266
|
+
}
|
|
260
267
|
const { rates } = analysis;
|
|
261
268
|
const stateText = !rates.state ? (analysis.notApplicable ? "federal" : "federal; no state rate given")
|
|
262
269
|
: analysis.stateApplied ? `federal ${formatRate(rates.federal)} + ${rates.state.code} ${formatRate(rates.state.rate)}`
|
|
@@ -302,7 +309,8 @@ export function taxEquivalentJson(tax: TaxEquivalentAnalysis) {
|
|
|
302
309
|
value: tax.ratio.value, tenor: tax.ratio.treasury.tenor, treasuryYield: tax.ratio.treasury.yield,
|
|
303
310
|
treasuryAsOf: tax.ratio.treasury.asOf, durationYears: tax.ratio.durationYears,
|
|
304
311
|
} : null,
|
|
305
|
-
|
|
312
|
+
...(tax.checkError ? { checkError: tax.checkError } : {}),
|
|
313
|
+
notes: [tax.checkError, tax.notApplicable, tax.stateNote, tax.ratioNote].filter((note): note is string => !!note),
|
|
306
314
|
formula: TAX_EQUIVALENT_FORMULA,
|
|
307
315
|
ratioFormula: MUNI_TREASURY_RATIO_FORMULA,
|
|
308
316
|
};
|
|
@@ -245,8 +245,8 @@ export function createAppNotifier({
|
|
|
245
245
|
}: CreateAppNotifierOptions): AppNotifier {
|
|
246
246
|
return {
|
|
247
247
|
notify(request) {
|
|
248
|
-
// A missed price, catalyst or team notice waits in the
|
|
249
|
-
// its toast action means it was seen.
|
|
248
|
+
// A missed price, catalyst, economic release or team notice waits in the
|
|
249
|
+
// Unread list; using its toast action means it was seen.
|
|
250
250
|
const logged = appendNotificationLog(request);
|
|
251
251
|
const notification: AppNotificationRequest = logged && request.action
|
|
252
252
|
? {
|
|
@@ -6,20 +6,22 @@ export const NOTIFICATION_LOG_LIMIT = 300;
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The notices worth finding again after their toast is gone: a price alert or
|
|
9
|
-
* a catalyst that fired, and a team card. Chat
|
|
10
|
-
* the account, and plain info, success and
|
|
9
|
+
* a catalyst that fired, a high-impact economic print, and a team card. Chat
|
|
10
|
+
* keeps its own unread state on the account, and plain info, success and
|
|
11
|
+
* error toasts are not logged.
|
|
11
12
|
*/
|
|
12
|
-
export type MissedNoticeKind = "price" | "catalyst" | "team";
|
|
13
|
+
export type MissedNoticeKind = "price" | "catalyst" | "econ" | "team";
|
|
13
14
|
|
|
14
15
|
const KIND_PREFIXES: ReadonlyArray<readonly [MissedNoticeKind, string]> = [
|
|
15
16
|
["price", "price:"],
|
|
16
17
|
["catalyst", "catalyst:"],
|
|
18
|
+
["econ", "econ:"],
|
|
17
19
|
["team", "team:"],
|
|
18
20
|
];
|
|
19
21
|
|
|
20
22
|
export interface NotificationLogEntry {
|
|
21
23
|
id: string;
|
|
22
|
-
/** `price:<alertId>`, `catalyst:<eventId>` or `team:<cardId>`. A later notice with the same ref updates this row. */
|
|
24
|
+
/** `price:<alertId>`, `catalyst:<eventId>`, `econ:<eventId>` or `team:<cardId>`. A later notice with the same ref updates this row. */
|
|
23
25
|
refId: string;
|
|
24
26
|
title?: string;
|
|
25
27
|
body: string;
|
|
@@ -134,8 +136,8 @@ export function unreadNotificationLogCount(log: readonly NotificationLogEntry[]
|
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
/**
|
|
137
|
-
* Records a notice whose `refId` names a price alert, a catalyst
|
|
138
|
-
* card; anything else returns null and is not kept. The same ref replaces its
|
|
139
|
+
* Records a notice whose `refId` names a price alert, a catalyst, an economic
|
|
140
|
+
* release or a team card; anything else returns null and is not kept. The same ref replaces its
|
|
139
141
|
* row: a price alert that fires again is new and unread again.
|
|
140
142
|
*/
|
|
141
143
|
export function appendNotificationLog(
|
|
@@ -2,11 +2,12 @@ import { withConfigData, withMarketData } from "../../../cli/scoped-context";
|
|
|
2
2
|
import { CLI_COMMAND_GROUPS } from "../../../cli/help";
|
|
3
3
|
import { dryRunNote } from "../../../cli/helpers";
|
|
4
4
|
import { requireArg, takeOption } from "../../../cli/commands/command-utils";
|
|
5
|
-
import {
|
|
5
|
+
import { requireQuotedListing } from "../../../cli/quote-answer";
|
|
6
|
+
import { EXCHANGE_OPTION, listingReadNotes, requireListingArg } from "../../../cli/listing-arg";
|
|
6
7
|
import { saveConfig } from "../../../data/config/store";
|
|
7
|
-
import type {
|
|
8
|
+
import type { CliCommandDef } from "../../../types/plugin";
|
|
9
|
+
import type { Quote } from "../../../types/financials";
|
|
8
10
|
import { getActiveQuoteDisplay } from "../../../market-data/market/status";
|
|
9
|
-
import { settleWithin } from "../../../utils/async-deadline";
|
|
10
11
|
import { cliStyles } from "../../../utils/cli-output";
|
|
11
12
|
import { ambiguousNumberMessage, meantThousandsDot, parsePastedNumber, thousandsDotReadings, type ThousandsDotReadings } from "../../../utils/decimal-comma";
|
|
12
13
|
import { newYorkToday } from "../earnings/board-model";
|
|
@@ -38,6 +39,15 @@ import { scheduledEventSources } from "./scheduled-sources";
|
|
|
38
39
|
import { readAlertStores } from "./storage";
|
|
39
40
|
import type { AlertCondition, AlertRule } from "./types";
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* What delivers a price alert, as docs/usage.md states it: the app checks it on
|
|
44
|
+
* every tick while it runs. Signed in with Cloud sync on, Gloom Cloud also
|
|
45
|
+
* pushes the trigger to a registered phone, and emails it once no app has
|
|
46
|
+
* synced the account for a while, so a rule the app has synced reaches you
|
|
47
|
+
* with the app closed. Signed out, only the running app does.
|
|
48
|
+
*/
|
|
49
|
+
const PRICE_DELIVERY = "the app alerts while it is running; sign in with Cloud sync for email or phone alerts when it is closed";
|
|
50
|
+
|
|
41
51
|
const ADD_USAGE = "Usage: gloomberb alerts add <symbol> <above|below|crosses> <price>, alerts add <symbol> earnings [--days <n>], or alerts add <CPI|FOMC> [--days <n>]";
|
|
42
52
|
const DAYS_RULE = `--days takes a whole number of days from 1 to ${MAX_LEAD_DAYS}; without it the alert goes off 1 day before.`;
|
|
43
53
|
|
|
@@ -112,19 +122,13 @@ function retypeCommand(symbol: string, condition: AlertCondition, price: number,
|
|
|
112
122
|
return `gloomberb alerts add ${symbol} ${condition} ${price}${exchange ? ` --exchange ${exchange}` : ""}`;
|
|
113
123
|
}
|
|
114
124
|
|
|
115
|
-
const LAST_PRICE_TIMEOUT_MS = 10_000;
|
|
116
|
-
|
|
117
125
|
/**
|
|
118
126
|
* The listing's last price, as the alert will check it, for telling 81.000
|
|
119
127
|
* typed for 81000 from 81; null when there is none, and the price is then
|
|
120
128
|
* read as written.
|
|
121
129
|
*/
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
withMarketData(ctx, (market) => market.dataProvider.getQuote(listing.key.toUpperCase(), listing.exchange)),
|
|
125
|
-
LAST_PRICE_TIMEOUT_MS,
|
|
126
|
-
);
|
|
127
|
-
const price = quote ? getActiveQuoteDisplay(quote)?.price ?? quote.price : null;
|
|
130
|
+
function lastPrice(quote: Quote): number | null {
|
|
131
|
+
const price = getActiveQuoteDisplay(quote)?.price ?? quote.price;
|
|
128
132
|
return price != null && Number.isFinite(price) && price !== 0 ? price : null;
|
|
129
133
|
}
|
|
130
134
|
|
|
@@ -271,13 +275,17 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
271
275
|
}
|
|
272
276
|
};
|
|
273
277
|
const request = action === "add" ? parseAdd() : null;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
278
|
+
// A price alert is kept for the listing `quote` prints for the symbol, and for no symbol that quotes nothing.
|
|
279
|
+
const priced = request?.kind === "price"
|
|
280
|
+
? await withMarketData(ctx, (market) => requireQuotedListing(request.symbol, exchangeOption, market, ctx, "alerts add"))
|
|
281
|
+
: null;
|
|
282
|
+
const priceListing = priced?.listing ?? null;
|
|
283
|
+
if (request?.kind === "price" && request.thousandsDot && priced) {
|
|
284
|
+
const question = thousandsDotMessage(request, priced.listing.key, lastPrice(priced.quote), exchangeOption);
|
|
277
285
|
if (question) ctx.fail(question);
|
|
278
286
|
}
|
|
279
|
-
// What a typed listing was read as (7203:TSE as 7203:JPX), said beside what was added.
|
|
280
|
-
let read =
|
|
287
|
+
// What a typed listing was read as (7203:TSE as 7203:JPX, MXN as MXN:FWB2), said beside what was added.
|
|
288
|
+
let read = priced?.notes ?? [];
|
|
281
289
|
let newEventRule: EventAlertRule | null = null;
|
|
282
290
|
if (request && request.kind !== "price") {
|
|
283
291
|
const target: ScheduledEventTarget = request.kind === "macro"
|
|
@@ -356,8 +364,8 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
356
364
|
const alert = createAlert(symbol, condition, price, priceListing?.exchange);
|
|
357
365
|
await save({ alerts: [...alerts, alert] });
|
|
358
366
|
ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, alert }, ...(read.length > 0 ? { warnings: read } : {}) }, {
|
|
359
|
-
//
|
|
360
|
-
text: (data) => `Added alert ${data.alert.id}: ${symbol} ${condition} ${price}${dryRunNote(data.dryRun)}`,
|
|
367
|
+
// The delivery, as docs/usage.md states it, before any full stop, which after the price would read as part of it (below 81.).
|
|
368
|
+
text: (data) => `Added alert ${data.alert.id}: ${symbol} ${condition} ${price} (${PRICE_DELIVERY}).${dryRunNote(data.dryRun)}`,
|
|
361
369
|
});
|
|
362
370
|
return;
|
|
363
371
|
}
|
|
@@ -139,7 +139,7 @@ async function historyResult(ctx: HeadlessPaneContext): Promise<HeadlessRowsResu
|
|
|
139
139
|
offset = history.hasMore ? history.nextOffset : null;
|
|
140
140
|
}
|
|
141
141
|
} catch (error) {
|
|
142
|
-
throw explainRefusal(error, alertHistoryLockNote, { symbol: null, token: "ALRT" });
|
|
142
|
+
throw await explainRefusal(error, alertHistoryLockNote, { symbol: null, token: "ALRT" });
|
|
143
143
|
}
|
|
144
144
|
const unavailable = first?.status === "unavailable";
|
|
145
145
|
return {
|
|
@@ -60,7 +60,7 @@ export const alertsPlugin: GloomPlugin = {
|
|
|
60
60
|
id: "alerts",
|
|
61
61
|
name: "Alerts",
|
|
62
62
|
version: "1.0.0",
|
|
63
|
-
description: "Price,
|
|
63
|
+
description: "Price, earnings, filing, news and flow alerts",
|
|
64
64
|
toggleable: true,
|
|
65
65
|
cliCommands: [alertsCliCommand],
|
|
66
66
|
|
|
@@ -189,7 +189,11 @@ export const alertsPlugin: GloomPlugin = {
|
|
|
189
189
|
id: "set-event-alert",
|
|
190
190
|
label: "Add Event Alert",
|
|
191
191
|
description: "Create an alert before earnings, CPI or FOMC, or for filings, news and market moves",
|
|
192
|
-
keywords: [
|
|
192
|
+
keywords: [
|
|
193
|
+
"alert", "event", "congress", "13f", "fund", "member", "follow", "earnings", "cpi", "fomc", "days before",
|
|
194
|
+
"filing", "filings", "sec", "8-k", "news", "keyword", "analyst", "52-week", "unusual volume", "volume",
|
|
195
|
+
"short interest", "insider", "catalyst", "implied volatility", "iv spike", "options flow", "perps", "funding",
|
|
196
|
+
],
|
|
193
197
|
category: "data",
|
|
194
198
|
wizardLayout: "form",
|
|
195
199
|
wizard: [
|
|
@@ -348,8 +352,13 @@ export const alertsPlugin: GloomPlugin = {
|
|
|
348
352
|
id: "alerts-pane",
|
|
349
353
|
paneId: "alerts",
|
|
350
354
|
label: "Alerts",
|
|
351
|
-
description: "Price,
|
|
352
|
-
keywords: [
|
|
355
|
+
description: "Price, earnings, filing, news and flow alerts",
|
|
356
|
+
keywords: [
|
|
357
|
+
"alerts", "alert", "price", "trigger", "alarm", "watch", "notify", "notification",
|
|
358
|
+
"earnings", "cpi", "fomc", "filing", "filings", "sec", "8-k", "13f", "news", "keyword",
|
|
359
|
+
"analyst", "upgrade", "downgrade", "52-week", "unusual volume", "volume", "short interest",
|
|
360
|
+
"insider", "congress", "catalyst", "implied volatility", "iv spike", "options flow", "perps", "funding",
|
|
361
|
+
],
|
|
353
362
|
shortcut: { prefix: "ALRT" },
|
|
354
363
|
headless: alertsHeadless,
|
|
355
364
|
});
|
|
@@ -482,7 +482,7 @@ export const portfolioAnalyticsModule: PluginModule = {
|
|
|
482
482
|
paneId: "analytics",
|
|
483
483
|
label: prefix === "PORT" ? "Portfolio Analytics" : "Portfolio Market Risk",
|
|
484
484
|
description: "Benchmark-relative risk, factor betas, stress shifts and local account evidence.",
|
|
485
|
-
keywords: ["risk", "analytics", "sharpe", "beta", "sector", "allocation", "portfolio"],
|
|
485
|
+
keywords: ["risk", "analytics", "sharpe", "beta", "sector", "allocation", "portfolio", "style", "quality", "factor tilt"],
|
|
486
486
|
shortcut: { prefix, argKind: "text", argOptional: true, argPlaceholder: "portfolio-id" },
|
|
487
487
|
headless: portfolioRiskHeadless,
|
|
488
488
|
canCreate: (context) => context.config.portfolios.length > 0,
|