gloomberb 0.17.5 → 0.17.6
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 +3 -1
- package/src/api-client/chat.ts +9 -0
- package/src/api-client/commodity-balances.ts +77 -0
- package/src/api-client/data.ts +31 -2
- package/src/api-client/earnings.ts +10 -0
- package/src/api-client/index.ts +5 -0
- package/src/api-client/insider-signals.ts +75 -0
- package/src/api-client/market-discovery.ts +1 -1
- package/src/api-client/normalizers.ts +1 -0
- package/src/api-client/paths.ts +9 -0
- package/src/api-client/socket.ts +10 -0
- package/src/api-client/types.ts +25 -1
- package/src/app/runtime/broker-import.ts +23 -21
- package/src/app/runtime/plugin-bindings.ts +13 -7
- package/src/app/runtime/update.ts +14 -1
- package/src/architecture/core-builtin-allowlist.json +2 -10
- package/src/brokers/broker-portfolio-sync.ts +66 -0
- package/src/brokers/sync-broker-instance.ts +75 -41
- package/src/cli/commands/market.ts +101 -34
- package/src/cli/commands/options-chain.ts +20 -6
- package/src/cli/commands/overview.ts +52 -11
- package/src/cli/commands/search.ts +1 -1
- package/src/cli/commands/ticker.ts +24 -4
- package/src/cli/context.ts +13 -0
- package/src/cli/errors.ts +15 -2
- package/src/cli/fx-pair.ts +77 -0
- package/src/cli/history-rows.ts +25 -11
- package/src/cli/index.ts +24 -12
- package/src/cli/listing-arg.ts +61 -138
- package/src/cli/listing-suggestion.ts +3 -21
- package/src/cli/pane-functions/catalog.ts +50 -0
- package/src/cli/pane-functions/freshness.ts +44 -9
- package/src/cli/pane-functions/function-help.ts +8 -8
- package/src/cli/pane-functions/headless.ts +19 -7
- package/src/cli/pane-functions/index.ts +63 -5
- package/src/cli/pane-functions/resolver.ts +29 -8
- package/src/cli/pane-functions/screenshot.ts +2 -0
- package/src/cli/report-tables.ts +11 -4
- package/src/cli/result.ts +2 -0
- package/src/cli/series-search.ts +6 -1
- package/src/cli/ticker-resolution.ts +1 -2
- package/src/cli/unknown-command.ts +18 -0
- package/src/components/chart/composite/composite-chart.tsx +80 -19
- package/src/components/chart/composite/format.ts +10 -0
- package/src/components/chart/composite/legend.tsx +18 -4
- package/src/components/chart/composite/panel-labels.ts +38 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +25 -7
- package/src/components/chart/composite/price-axis-labels.tsx +16 -3
- package/src/components/chart/composite/scene.ts +60 -2
- package/src/components/chart/composite/series-axis-tags.tsx +67 -0
- package/src/components/chart/composite/tools.ts +2 -2
- package/src/components/chart/composite/types.ts +28 -0
- package/src/components/chart/core/view-reset.ts +46 -0
- package/src/components/command-bar/commands/collection.ts +1 -1
- package/src/components/command-bar/commands/direct/index.ts +1 -1
- package/src/components/command-bar/list/model.ts +7 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +1 -1
- package/src/components/command-bar/routes/root/pick-prefetch.ts +38 -0
- package/src/components/command-bar/routes/root/provider-search.ts +6 -0
- package/src/components/command-bar/routes/root/runtime.ts +3 -0
- package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -1
- package/src/components/command-bar/routes/root/shortcut-items.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/actions.ts +34 -0
- package/src/components/command-bar/surface/index.tsx +5 -0
- package/src/components/command-bar/workflow/collection-actions.ts +6 -5
- package/src/components/command-bar/workflow/ops.ts +4 -2
- package/src/components/command-bar/workflow/pane-reuse.ts +3 -2
- package/src/components/command-bar/workflow/tickers.ts +1 -1
- package/src/components/layout/pane/content.tsx +11 -8
- package/src/components/layout/pane/ticker-fate-gate.tsx +56 -0
- package/src/components/onboarding/wizard-model.ts +1 -1
- package/src/components/onboarding/wizard-positions.ts +18 -10
- package/src/components/ticker/badge/format.ts +64 -15
- package/src/components/ticker/badge/index.tsx +32 -11
- package/src/components/ticker/badge/list.tsx +4 -3
- package/src/components/ticker/badge/text.tsx +2 -2
- package/src/components/ticker/list-table-view.tsx +8 -1
- package/src/components/ui/data-table/opentui/index.tsx +61 -20
- package/src/components/ui/data-table/opentui/model.ts +104 -0
- package/src/components/ui/inline-quick-add.tsx +4 -1
- package/src/components/ui/query-bar-keys.tsx +29 -5
- package/src/components/ui/query-bar.tsx +29 -4
- package/src/components/ui/windowed-list.tsx +27 -0
- package/src/docs/snapshot.json +248 -5
- package/src/i18n/es.ts +1 -1
- package/src/i18n/ja.ts +1 -1
- package/src/i18n/ko.ts +1 -1
- package/src/i18n/zh-cn.ts +1 -1
- package/src/i18n/zh-tw.ts +1 -1
- package/src/layout-marketplace/cloud.ts +16 -5
- package/src/layout-marketplace/gallery.tsx +4 -4
- package/src/layout-marketplace/linked-sync.ts +9 -3
- package/src/layout-marketplace/linked.ts +24 -3
- package/src/layout-marketplace/model.ts +39 -4
- package/src/market-data/coordinator/index.ts +34 -1
- package/src/market-data/futures-contract.ts +53 -0
- package/src/market-data/hooks.tsx +57 -1
- package/src/market-data/market/bar-period.ts +71 -0
- package/src/market-data/market/format.ts +14 -2
- package/src/market-data/market/freshness.ts +28 -1
- package/src/market-data/market/status.ts +152 -13
- package/src/market-data/model-dividend-yield.ts +76 -0
- package/src/market-data/quotes/freshness.ts +30 -1
- package/src/{plugins/builtin/portfolio-list → market-data/quotes}/live-valuation.ts +6 -6
- package/src/market-data/quotes/regular-session-valuation.ts +32 -1
- package/src/plugins/builtin/analytics/metrics.ts +8 -4
- package/src/plugins/builtin/analytics/risk-headless.ts +12 -5
- package/src/plugins/builtin/analytics/risk-model.ts +74 -15
- package/src/plugins/builtin/analytics/risk-pane.tsx +6 -6
- package/src/plugins/builtin/builtin-plugin-meta.ts +0 -1
- package/src/plugins/builtin/chart-composer/editor/controller.ts +14 -10
- package/src/plugins/builtin/chart-composer/formula.ts +233 -0
- package/src/plugins/builtin/chart-composer/headless.ts +25 -1
- package/src/plugins/builtin/chart-composer/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/pane.tsx +21 -8
- package/src/plugins/builtin/chart-composer/presets.ts +91 -2
- package/src/plugins/builtin/chart-composer/quick-add.tsx +9 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +44 -11
- package/src/plugins/builtin/chart-composer/series-expression.ts +1 -1
- package/src/plugins/builtin/chart-composer/settings.ts +16 -4
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/content/channel-navigation.ts +17 -6
- package/src/plugins/builtin/chat/content/index.tsx +12 -5
- package/src/plugins/builtin/chat/content/mentions.ts +50 -21
- package/src/plugins/builtin/chat/content/scroll.ts +12 -5
- package/src/plugins/builtin/chat/content/snapshot.ts +11 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +48 -37
- package/src/plugins/builtin/chat/content/whole-message-copy.ts +44 -2
- package/src/plugins/builtin/chat/controller/index.ts +68 -17
- package/src/plugins/builtin/chat/controller/mention-candidates.ts +104 -0
- package/src/plugins/builtin/chat/controller/realtime.ts +9 -0
- package/src/plugins/builtin/chat/controller/state.ts +10 -0
- package/src/plugins/builtin/chat/controller/view.ts +2 -0
- package/src/plugins/builtin/chat/conversation-recency.ts +73 -0
- package/src/plugins/builtin/chat/conversations.ts +12 -5
- package/src/plugins/builtin/chat/layout.ts +23 -5
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
- package/src/plugins/builtin/chat/sidebar-rows.ts +18 -5
- package/src/plugins/builtin/chat/sidebar.tsx +6 -1
- package/src/plugins/builtin/cloud/askg/manifest.ts +4 -3
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +16 -5
- package/src/plugins/builtin/commodities-energy/index.tsx +23 -4
- package/src/plugins/builtin/commodity-balances/client.ts +51 -193
- package/src/plugins/builtin/commodity-balances/headless.ts +10 -3
- package/src/plugins/builtin/commodity-balances/model.ts +5 -180
- package/src/plugins/builtin/commodity-balances/pane.tsx +3 -0
- package/src/plugins/builtin/custom-view/index.tsx +1 -1
- package/src/plugins/builtin/custom-view/loader.ts +57 -7
- package/src/plugins/builtin/custom-view/sources.ts +146 -0
- package/src/plugins/builtin/custom-view/view-spec.ts +37 -1
- package/src/plugins/builtin/earnings/board-model.ts +33 -3
- package/src/plugins/builtin/earnings/board-table.ts +59 -14
- package/src/plugins/builtin/earnings/board.tsx +36 -3
- package/src/plugins/builtin/earnings/headless.ts +24 -5
- package/src/plugins/builtin/earnings/history-model.ts +9 -1
- package/src/plugins/builtin/earnings/history-table.ts +16 -5
- package/src/plugins/builtin/earnings/history.tsx +40 -10
- package/src/plugins/builtin/earnings/index.tsx +3 -1
- package/src/plugins/builtin/earnings/no-earnings.ts +52 -0
- package/src/plugins/builtin/econ-statistics/detail.tsx +4 -1
- package/src/plugins/builtin/econ-statistics/headless.ts +5 -0
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +4 -0
- package/src/plugins/builtin/futures/model.ts +5 -4
- package/src/plugins/builtin/futures/series.ts +38 -1
- package/src/plugins/builtin/futures-curve/client.ts +4 -4
- package/src/plugins/builtin/futures-curve/headless.ts +20 -2
- package/src/plugins/builtin/futures-curve/index.tsx +2 -2
- package/src/plugins/builtin/futures-curve/model.ts +45 -2
- package/src/plugins/builtin/futures-curve/pane.tsx +14 -10
- package/src/plugins/builtin/insider/headless.ts +74 -4
- package/src/plugins/builtin/insider/index.tsx +99 -5
- package/src/plugins/builtin/insider/signals-client.ts +69 -0
- package/src/plugins/builtin/insider/signals-model.ts +146 -0
- package/src/plugins/builtin/insider/signals.tsx +176 -0
- package/src/plugins/builtin/iv-history/client.ts +41 -2
- package/src/plugins/builtin/iv-history/headless.ts +24 -16
- package/src/plugins/builtin/iv-history/screen-pane.tsx +25 -7
- package/src/plugins/builtin/jodi/client.ts +42 -205
- package/src/plugins/builtin/jodi/headless.ts +13 -4
- package/src/plugins/builtin/jodi/model.ts +18 -43
- package/src/plugins/builtin/jodi/pane.tsx +5 -0
- package/src/plugins/builtin/market-movers/client.ts +32 -6
- package/src/plugins/builtin/market-movers/headless.ts +7 -2
- package/src/plugins/builtin/market-movers/model.ts +64 -3
- package/src/plugins/builtin/market-movers/screener.ts +20 -9
- package/src/plugins/builtin/market-valuation/detail.tsx +7 -2
- package/src/plugins/builtin/market-valuation/headless.ts +17 -4
- package/src/plugins/builtin/market-valuation/pane.tsx +7 -3
- package/src/plugins/builtin/market-valuation/view.ts +10 -0
- package/src/plugins/builtin/options-positioning/client.ts +10 -0
- package/src/plugins/builtin/options-positioning/headless.ts +88 -13
- package/src/plugins/builtin/options-positioning/index.tsx +2 -2
- package/src/plugins/builtin/options-positioning/model.ts +33 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +38 -16
- package/src/plugins/builtin/options-scenario/charts.tsx +13 -4
- package/src/plugins/builtin/options-scenario/client.ts +111 -17
- package/src/plugins/builtin/options-scenario/collar.ts +141 -0
- package/src/plugins/builtin/options-scenario/csp.ts +1 -1
- package/src/plugins/builtin/options-scenario/freshness.ts +27 -0
- package/src/plugins/builtin/options-scenario/headless.ts +28 -12
- package/src/plugins/builtin/options-scenario/index.tsx +1 -1
- package/src/plugins/builtin/options-scenario/model.ts +55 -5
- package/src/plugins/builtin/options-scenario/pane.tsx +24 -5
- package/src/plugins/builtin/portfolio-list/allocation.ts +9 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +39 -20
- package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +15 -7
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +1 -1
- package/src/plugins/builtin/portfolio-list/column-values.ts +4 -3
- package/src/plugins/builtin/portfolio-list/command-bar.ts +1 -1
- package/src/plugins/builtin/portfolio-list/headless.ts +10 -3
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +36 -4
- package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +1 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +35 -6
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
- package/src/plugins/builtin/portfolio-list/settings.ts +22 -0
- package/src/plugins/builtin/portfolio-list/since-last-seen.ts +197 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +14 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +14 -5
- package/src/plugins/builtin/research/event-model.ts +41 -49
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/reverse-dcf/headless.ts +25 -9
- package/src/plugins/builtin/reverse-dcf/index.tsx +4 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +39 -5
- package/src/plugins/builtin/reverse-dcf/pane.tsx +16 -6
- package/src/plugins/builtin/shared/earnings-report.ts +48 -1
- package/src/plugins/builtin/shared/history-span.ts +55 -0
- package/src/plugins/builtin/shared/report-freshness.ts +20 -11
- package/src/plugins/builtin/shared/series-detail-chart.tsx +5 -2
- package/src/plugins/builtin/shared/series-list-detail.tsx +7 -3
- package/src/plugins/builtin/shared/ticker-surface.ts +16 -2
- package/src/plugins/builtin/shared/use-quote-board.ts +12 -5
- package/src/plugins/builtin/themes/headless.ts +15 -2
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +85 -17
- package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +92 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +26 -8
- package/src/plugins/builtin/ticker-detail/headless-schema.ts +4 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +95 -22
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +17 -3
- package/src/{plugins/builtin/portfolio-list → portfolio}/mutations.ts +6 -6
- package/src/portfolio/valuation-basis.ts +4 -21
- package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
- package/src/remote/controller.ts +27 -7
- package/src/remote/schema.ts +25 -9
- package/src/renderers/cloudflare/http-proxy.ts +21 -13
- package/src/renderers/dom/desktop/query-bar.tsx +1 -1
- package/src/renderers/dom/dom-ui-host.tsx +2 -0
- package/src/renderers/dom/windowed-list.tsx +201 -0
- package/src/renderers/dom/windowed-rows.ts +65 -0
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +2 -9
- package/src/renderers/electrobun/bun/desktop/http-stream.ts +21 -6
- package/src/renderers/electrobun/bun/desktop/update.ts +21 -6
- package/src/renderers/electrobun/shared/protocol.ts +4 -2
- package/src/renderers/electrobun/view/http-fetch.ts +12 -1
- package/src/sources/listing-symbols.ts +8 -4
- package/src/state/app/context/index.tsx +13 -0
- package/src/state/app/tab-cursor.ts +85 -0
- package/src/sync/core-contributors.ts +206 -59
- package/src/test-support/config-store.ts +31 -0
- package/src/test-support/moved-symbols.ts +64 -0
- package/src/theme/colors.ts +30 -0
- package/src/tickers/listing-fate.ts +242 -0
- package/src/tickers/search/index.ts +45 -3
- package/src/tickers/search/ranking.ts +19 -2
- package/src/tickers/search/types.ts +3 -0
- package/src/tickers/search/upsert.ts +22 -7
- package/src/tickers/ticker-changes.ts +26 -9
- package/src/time-series/bar-spacing.ts +19 -0
- package/src/time-series/chart-data.ts +37 -13
- package/src/time-series/field-catalog.ts +5 -2
- package/src/time-series/forward-valuation.ts +10 -7
- package/src/time-series/fundamentals.ts +49 -11
- package/src/time-series/hooks.ts +2 -0
- package/src/time-series/live-bars.ts +3 -1
- package/src/time-series/parsed-history-cache.ts +2 -0
- package/src/time-series/price-units.ts +69 -0
- package/src/time-series/resolve.ts +50 -5
- package/src/time-series/session-bar.ts +138 -0
- package/src/time-series/spec.ts +12 -3
- package/src/time-series/studies.ts +211 -12
- package/src/time-series/transforms.ts +2 -0
- package/src/time-series/types.ts +11 -1
- package/src/time-series/valuation-price.ts +13 -0
- package/src/types/config.ts +10 -1
- package/src/types/financials.ts +2 -0
- package/src/types/headless.ts +27 -0
- package/src/types/plugin.ts +7 -0
- package/src/ui/host.tsx +5 -0
- package/src/updater/index.ts +3 -1
- package/src/utils/asset-category.ts +20 -0
- package/src/utils/decimal-comma.ts +83 -3
- package/src/utils/fundamentals.ts +7 -2
- package/src/utils/http-proxy-response.ts +66 -9
- package/src/utils/plugin-proxy-hosts.json +1 -5
- package/src/utils/typo-distance.ts +22 -0
- package/src/plugins/builtin/shared/zip-text.ts +0 -46
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
"src/app.tsx": ["cloud"],
|
|
4
4
|
"src/app/global-shortcuts.ts": ["help"],
|
|
5
5
|
"src/app/pane-runtime/plugin-bindings.ts": ["broker-manager","layout-manager"],
|
|
6
|
-
"src/app/runtime/broker-import.ts": ["portfolio-list"],
|
|
7
6
|
"src/app/runtime/desktop-deeplink.ts": ["account-management","chat"],
|
|
8
7
|
"src/app/runtime/startup.ts": ["chat"],
|
|
9
8
|
"src/cli/commands/market.ts": ["holders","insider","research","sec"],
|
|
@@ -19,21 +18,14 @@
|
|
|
19
18
|
"src/cli/pane-functions/resolver.ts": ["chart-composer"],
|
|
20
19
|
"src/cli/pane-functions/screenshot-evidence.ts": ["attention","awards","catalysts","company-attention","company-kpis","credit-documents","exposure","market-heatmap","options-calculator","options-scenario","power","realized-vol","supply-chain","vol-surface","volatility","world-venue-map"],
|
|
21
20
|
"src/cli/pane-functions/screenshot.ts": ["chart-composer","econ-statistics","market-valuation","portfolio-list","ticker-detail","time-sales"],
|
|
22
|
-
"src/components/command-bar/commands/
|
|
23
|
-
"src/components/command-bar/commands/direct/index.ts": ["broker-manager","portfolio-list"],
|
|
24
|
-
"src/components/command-bar/routes/root/command-items.ts": ["portfolio-list"],
|
|
21
|
+
"src/components/command-bar/commands/direct/index.ts": ["broker-manager"],
|
|
25
22
|
"src/components/command-bar/routes/root/help-items.ts": ["help"],
|
|
26
|
-
"src/components/command-bar/routes/root/shortcut-items.ts": ["portfolio-list"],
|
|
27
23
|
"src/components/command-bar/workflow/builtin.ts": ["portfolio-list"],
|
|
28
|
-
"src/components/command-bar/workflow/collection-actions.ts": ["portfolio-list"],
|
|
29
24
|
"src/components/command-bar/workflow/ops.ts": ["correlation","portfolio-list","ticker-detail"],
|
|
30
|
-
"src/components/command-bar/workflow/tickers.ts": ["portfolio-list"],
|
|
31
25
|
"src/components/form-modal/routes.ts": ["portfolio-list"],
|
|
32
26
|
"src/components/index.ts": ["portfolio-list"],
|
|
33
27
|
"src/components/layout/shell/index.tsx": ["cloud"],
|
|
34
28
|
"src/components/onboarding/use-onboarding-upgrade.ts": ["chat"],
|
|
35
|
-
"src/components/onboarding/wizard-model.ts": ["portfolio-list"],
|
|
36
|
-
"src/components/onboarding/wizard-positions.ts": ["portfolio-list"],
|
|
37
29
|
"src/layout/desks.ts": ["chart-composer"],
|
|
38
30
|
"src/plugins/catalog-browser.ts": ["alerts","analytics","attention","awards","backtest","bond-calculator","catalysts","cds","central-bank-rates","changelog","chart-composer","clinical-trials","cloud","comment-letters","commodities-energy","commodity-balances","company-attention","company-kpis","connections","correlation","cot","cpi","credit-boards","credit-conditions","credit-documents","crypto-board","debt-maturities","dividend-yield","docs","doe","earnings-calls","earnings-ripple","econ","econ-statistics","equity-screener","estimate-revisions","executives","exposure","fear-greed","filing-events","fixed-income","futures","futures-curve","fx-matrix","gpu","help","insider","ipo-calendar","iv-history","jobs","jodi","kelly-sizer","layout-manager","macro-day","market-halts","market-heatmap","market-valuation","members","mna","money-markets","news","openfda","options","options-calculator","options-positioning","options-scenario","pe-band","perps","portfolio-list","power","primary-dealers","rate-path","realized-vol","relative-rotation","research","research-search","revenue-breakdown","reverse-dcf","risk-factors","scanner","seasonality","sec","sectors","short-interest","short-volume","social-mentions","supply-chain","ticker-detail","time-sales","trace-bonds","treasury-auctions","treasury-daily","vol-surface","volatility","world-indices","world-venue-map","yield-curve"],
|
|
39
31
|
"src/plugins/catalog-ui.ts": ["alerts","clinical-trials","cloud","comment-letters","custom-view","fear-greed","ipo-calendar","market-halts","market-heatmap","members","news","notes","openfda","research-search"],
|
|
@@ -43,5 +35,5 @@
|
|
|
43
35
|
"src/renderers/electrobun/view/cli-pane-shot-entry.tsx": ["econ-statistics","market-valuation","time-sales"],
|
|
44
36
|
"src/renderers/electrobun/view/main.tsx": ["notes"],
|
|
45
37
|
"src/renderers/electrobun/view/remote/connection-health.ts": ["connections"],
|
|
46
|
-
"src/sync/core-contributors.ts": ["analytics"
|
|
38
|
+
"src/sync/core-contributors.ts": ["analytics"]
|
|
47
39
|
}
|
|
@@ -181,10 +181,17 @@ export function clearBrokerInstanceTickerData(
|
|
|
181
181
|
brokerPortfolioIds: Set<string>,
|
|
182
182
|
/** Portfolios this profile just took over: whatever another profile imported there goes too. */
|
|
183
183
|
takenOverPortfolioIds: ReadonlySet<string> = new Set(),
|
|
184
|
+
/**
|
|
185
|
+
* Portfolios this sync writes. A position there without a profile came from
|
|
186
|
+
* another device's sync of the same account, so this one replaces it too;
|
|
187
|
+
* one entered by hand stays.
|
|
188
|
+
*/
|
|
189
|
+
syncedPortfolioIds: ReadonlySet<string> = new Set(),
|
|
184
190
|
): TickerRecord | null {
|
|
185
191
|
const positions = ticker.metadata.positions.filter((position) =>
|
|
186
192
|
position.brokerInstanceId !== instanceId
|
|
187
193
|
&& !(takenOverPortfolioIds.has(position.portfolio) && position.brokerInstanceId)
|
|
194
|
+
&& !(syncedPortfolioIds.has(position.portfolio) && !position.brokerInstanceId && position.broker !== "manual")
|
|
188
195
|
);
|
|
189
196
|
const remainingPositionPortfolios = new Set(positions.map((position) => position.portfolio));
|
|
190
197
|
const portfolios = ticker.metadata.portfolios.filter((portfolioId) =>
|
|
@@ -211,6 +218,65 @@ export function clearBrokerInstanceTickerData(
|
|
|
211
218
|
};
|
|
212
219
|
}
|
|
213
220
|
|
|
221
|
+
/** The profile a broker portfolio id was made for: `broker:<profile>:<account>`. */
|
|
222
|
+
function brokerInstanceIdOfPortfolio(portfolioId: string): string | undefined {
|
|
223
|
+
return isBrokerPortfolioId(portfolioId) ? portfolioId.split(":")[1] || undefined : undefined;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** The broker portfolios and profiles that still exist somewhere this device knows of. */
|
|
227
|
+
export interface KnownBrokerHoldings {
|
|
228
|
+
portfolioIds: ReadonlySet<string>;
|
|
229
|
+
instanceIds: ReadonlySet<string>;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function knownBrokerHoldings(
|
|
233
|
+
...sources: Array<{ portfolios?: ReadonlyArray<{ id: string }>; brokerInstances?: ReadonlyArray<{ id: string }> }>
|
|
234
|
+
): KnownBrokerHoldings {
|
|
235
|
+
return {
|
|
236
|
+
portfolioIds: new Set(sources.flatMap((source) => source.portfolios?.map((portfolio) => portfolio.id) ?? [])),
|
|
237
|
+
instanceIds: new Set(sources.flatMap((source) => source.brokerInstances?.map((instance) => instance.id) ?? [])),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* A broker portfolio that is gone, along with the profile it was made for. One
|
|
243
|
+
* that still has its profile stays: that profile restores it on its next sync.
|
|
244
|
+
*/
|
|
245
|
+
function isGoneBrokerPortfolio(portfolioId: string, known: KnownBrokerHoldings): boolean {
|
|
246
|
+
if (!isBrokerPortfolioId(portfolioId) || known.portfolioIds.has(portfolioId)) return false;
|
|
247
|
+
const instanceId = brokerInstanceIdOfPortfolio(portfolioId);
|
|
248
|
+
return !instanceId || !known.instanceIds.has(instanceId);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function isLeftoverBrokerPosition(position: TickerPosition, known: KnownBrokerHoldings): boolean {
|
|
252
|
+
return isGoneBrokerPortfolio(position.portfolio, known)
|
|
253
|
+
&& !(position.brokerInstanceId && known.instanceIds.has(position.brokerInstanceId));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* The ticker without what removed broker profiles left on it: positions and
|
|
258
|
+
* links in portfolios that are gone with their profile, and contracts of
|
|
259
|
+
* profiles that are gone. A pull from another device brings such positions
|
|
260
|
+
* without the profile that imported them, as a profile never leaves its
|
|
261
|
+
* device, so nothing else ever clears them. Null when there is none.
|
|
262
|
+
*/
|
|
263
|
+
export function clearLeftoverBrokerHoldings(ticker: TickerRecord, known: KnownBrokerHoldings): TickerRecord | null {
|
|
264
|
+
const { metadata } = ticker;
|
|
265
|
+
const positions = metadata.positions.filter((position) => !isLeftoverBrokerPosition(position, known));
|
|
266
|
+
const held = new Set(positions.map((position) => position.portfolio));
|
|
267
|
+
const portfolios = metadata.portfolios.filter((portfolioId) => held.has(portfolioId) || !isGoneBrokerPortfolio(portfolioId, known));
|
|
268
|
+
const contracts = metadata.broker_contracts ?? [];
|
|
269
|
+
const brokerContracts = contracts.filter((contract) => !contract.brokerInstanceId || known.instanceIds.has(contract.brokerInstanceId));
|
|
270
|
+
if (
|
|
271
|
+
positions.length === metadata.positions.length
|
|
272
|
+
&& portfolios.length === metadata.portfolios.length
|
|
273
|
+
&& brokerContracts.length === contracts.length
|
|
274
|
+
) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
return { ...ticker, metadata: { ...metadata, positions, portfolios, broker_contracts: brokerContracts } };
|
|
278
|
+
}
|
|
279
|
+
|
|
214
280
|
function inferBrokerAccountId(position: TickerPosition, portfolioId: string, instanceId: string): string | undefined {
|
|
215
281
|
if (position.brokerAccountId) return position.brokerAccountId;
|
|
216
282
|
const prefix = `broker:${instanceId}:`;
|
|
@@ -7,9 +7,11 @@ import type { TickerRecord } from "../types/ticker";
|
|
|
7
7
|
import { getBrokerInstance } from "../utils/broker-instances";
|
|
8
8
|
import {
|
|
9
9
|
clearBrokerInstanceTickerData,
|
|
10
|
+
clearLeftoverBrokerHoldings,
|
|
10
11
|
ensureBrokerPortfolio,
|
|
11
12
|
findReusableBrokerPortfolioId,
|
|
12
13
|
isHeldByOtherSignedInProfile,
|
|
14
|
+
knownBrokerHoldings,
|
|
13
15
|
removeStaleBrokerPortfolios,
|
|
14
16
|
resolvePortfolioBrokerId,
|
|
15
17
|
} from "./broker-portfolio-sync";
|
|
@@ -33,6 +35,12 @@ export interface SyncBrokerInstanceArgs {
|
|
|
33
35
|
deferPersistence?: boolean;
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
/** The app as it is when a sync commits, which may be seconds after the sync read it. */
|
|
39
|
+
interface BrokerSyncCommitTarget {
|
|
40
|
+
tickers: ReadonlyMap<string, TickerRecord>;
|
|
41
|
+
config: Pick<AppConfig, "portfolios" | "brokerInstances">;
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
export interface SyncBrokerInstanceResult {
|
|
37
45
|
config: AppConfig;
|
|
38
46
|
tickers: Map<string, TickerRecord>;
|
|
@@ -41,6 +49,15 @@ export interface SyncBrokerInstanceResult {
|
|
|
41
49
|
portfolioIds: string[];
|
|
42
50
|
addedTickers: TickerRecord[];
|
|
43
51
|
updatedTickers: TickerRecord[];
|
|
52
|
+
/**
|
|
53
|
+
* Carries the sync onto the app as it is now, which may be seconds after the
|
|
54
|
+
* sync read it, so what changed while the broker answered (a pull from
|
|
55
|
+
* another device, an edit) stays; `tickers`, `addedTickers` and
|
|
56
|
+
* `updatedTickers` then hold the sync over it. Hand them to the app before
|
|
57
|
+
* anything waits: a pull landing in a wait would be overwritten in turn.
|
|
58
|
+
*/
|
|
59
|
+
rebase(onto: BrokerSyncCommitTarget): void;
|
|
60
|
+
/** Writes the sync's tickers and broker accounts. */
|
|
44
61
|
commit(): Promise<void>;
|
|
45
62
|
}
|
|
46
63
|
|
|
@@ -310,62 +327,78 @@ export async function syncBrokerInstance({
|
|
|
310
327
|
: []);
|
|
311
328
|
|
|
312
329
|
nextConfig = removeStaleBrokerPortfolios(nextConfig, instance.id, currentPortfolioIds);
|
|
313
|
-
const cleanedTickers = new Map<string, TickerRecord>();
|
|
314
|
-
for (const ticker of tickers.values()) {
|
|
315
|
-
const cleanedTicker = clearBrokerInstanceTickerData(ticker, instance.id, brokerPortfolioIds, takenOverPortfolioIds);
|
|
316
|
-
if (!cleanedTicker) continue;
|
|
317
|
-
tickers.set(cleanedTicker.metadata.ticker, cleanedTicker);
|
|
318
|
-
cleanedTickers.set(cleanedTicker.metadata.ticker, cleanedTicker);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
330
|
nextConfig = await maybePersistResolvedBrokerConfig(nextConfig, instance, broker, persistResolvedBrokerConfig);
|
|
322
331
|
throwIfBrokerImportCancelled(signal);
|
|
323
332
|
nextConfig = markBrokerInstanceSynced(nextConfig, instance.id, syncedAt);
|
|
333
|
+
const syncedConfig = nextConfig;
|
|
334
|
+
|
|
335
|
+
// What the sync does to the tickers, given them: it runs on the tickers it
|
|
336
|
+
// read, and again on the live ones when it commits after the broker answered.
|
|
337
|
+
const writeTickers = (source: ReadonlyMap<string, TickerRecord>, liveConfig?: BrokerSyncCommitTarget["config"]) => {
|
|
338
|
+
const tickers = new Map(source);
|
|
339
|
+
// Only a portfolio and a profile gone everywhere this device knows of make a leftover.
|
|
340
|
+
const known = knownBrokerHoldings(config, syncedConfig, ...(liveConfig ? [liveConfig] : []));
|
|
341
|
+
const cleanedTickers = new Map<string, TickerRecord>();
|
|
342
|
+
for (const ticker of tickers.values()) {
|
|
343
|
+
const cleared = clearBrokerInstanceTickerData(
|
|
344
|
+
ticker, instance.id, brokerPortfolioIds, takenOverPortfolioIds, currentPortfolioIds,
|
|
345
|
+
);
|
|
346
|
+
const cleanedTicker = clearLeftoverBrokerHoldings(cleared ?? ticker, known) ?? cleared;
|
|
347
|
+
if (!cleanedTicker) continue;
|
|
348
|
+
tickers.set(cleanedTicker.metadata.ticker, cleanedTicker);
|
|
349
|
+
cleanedTickers.set(cleanedTicker.metadata.ticker, cleanedTicker);
|
|
350
|
+
}
|
|
324
351
|
|
|
325
|
-
|
|
326
|
-
|
|
352
|
+
const addedTickers = new Map<string, TickerRecord>();
|
|
353
|
+
const updatedTickers = new Map<string, TickerRecord>();
|
|
354
|
+
|
|
355
|
+
for (const position of positions) {
|
|
356
|
+
throwIfBrokerImportCancelled(signal);
|
|
357
|
+
const portfolioId = findReusableBrokerPortfolioId(syncedConfig, instance, position.accountId, portfolioBrokerId);
|
|
358
|
+
if (heldElsewhere.has(portfolioId)) continue;
|
|
359
|
+
const { ticker, created } = upsertBrokerPositionTicker({
|
|
360
|
+
tickers,
|
|
361
|
+
instance,
|
|
362
|
+
portfolioId,
|
|
363
|
+
position,
|
|
364
|
+
});
|
|
365
|
+
if (created) {
|
|
366
|
+
addedTickers.set(position.ticker, ticker);
|
|
367
|
+
} else {
|
|
368
|
+
cleanedTickers.delete(ticker.metadata.ticker);
|
|
369
|
+
if (!addedTickers.has(position.ticker)) {
|
|
370
|
+
updatedTickers.set(position.ticker, ticker);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
327
373
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const { ticker, created } = upsertBrokerPositionTicker({
|
|
333
|
-
tickers,
|
|
334
|
-
instance,
|
|
335
|
-
portfolioId,
|
|
336
|
-
position,
|
|
337
|
-
});
|
|
338
|
-
if (created) {
|
|
339
|
-
addedTickers.set(position.ticker, ticker);
|
|
340
|
-
} else {
|
|
341
|
-
cleanedTickers.delete(ticker.metadata.ticker);
|
|
342
|
-
if (!addedTickers.has(position.ticker)) {
|
|
374
|
+
tickers.set(position.ticker, ticker);
|
|
375
|
+
if (addedTickers.has(position.ticker)) {
|
|
376
|
+
addedTickers.set(position.ticker, ticker);
|
|
377
|
+
} else {
|
|
343
378
|
updatedTickers.set(position.ticker, ticker);
|
|
344
379
|
}
|
|
345
380
|
}
|
|
346
381
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
updatedTickers.set(position.ticker, ticker);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
for (const ticker of cleanedTickers.values()) {
|
|
356
|
-
if (!addedTickers.has(ticker.metadata.ticker) && !updatedTickers.has(ticker.metadata.ticker)) {
|
|
357
|
-
updatedTickers.set(ticker.metadata.ticker, ticker);
|
|
382
|
+
for (const ticker of cleanedTickers.values()) {
|
|
383
|
+
if (!addedTickers.has(ticker.metadata.ticker) && !updatedTickers.has(ticker.metadata.ticker)) {
|
|
384
|
+
updatedTickers.set(ticker.metadata.ticker, ticker);
|
|
385
|
+
}
|
|
358
386
|
}
|
|
359
|
-
|
|
387
|
+
return { tickers, addedTickers: [...addedTickers.values()], updatedTickers: [...updatedTickers.values()] };
|
|
388
|
+
};
|
|
360
389
|
|
|
390
|
+
let written = writeTickers(tickers);
|
|
361
391
|
let committed = false;
|
|
392
|
+
const rebase = (onto: BrokerSyncCommitTarget) => {
|
|
393
|
+
if (!committed) written = writeTickers(onto.tickers, onto.config);
|
|
394
|
+
};
|
|
362
395
|
const commit = async () => {
|
|
363
396
|
if (committed) return;
|
|
364
397
|
throwIfBrokerImportCancelled(signal);
|
|
365
398
|
if (resources) {
|
|
366
399
|
persistBrokerAccounts(resources, instance, broker, brokerAccounts);
|
|
367
400
|
}
|
|
368
|
-
await Promise.all([...addedTickers
|
|
401
|
+
await Promise.all([...written.addedTickers, ...written.updatedTickers].map((ticker) => (
|
|
369
402
|
tickerRepository.saveTicker(ticker)
|
|
370
403
|
)));
|
|
371
404
|
committed = true;
|
|
@@ -377,12 +410,13 @@ export async function syncBrokerInstance({
|
|
|
377
410
|
|
|
378
411
|
return {
|
|
379
412
|
config: nextConfig,
|
|
380
|
-
tickers,
|
|
413
|
+
get tickers() { return written.tickers; },
|
|
381
414
|
brokerAccounts,
|
|
382
415
|
positions,
|
|
383
416
|
portfolioIds,
|
|
384
|
-
|
|
385
|
-
|
|
417
|
+
get addedTickers() { return written.addedTickers; },
|
|
418
|
+
get updatedTickers() { return written.updatedTickers; },
|
|
419
|
+
rebase,
|
|
386
420
|
commit,
|
|
387
421
|
};
|
|
388
422
|
}
|
|
@@ -16,10 +16,12 @@ import {
|
|
|
16
16
|
getExtendedSessionDisplay,
|
|
17
17
|
getQuoteSessionFields,
|
|
18
18
|
getRegularSessionDisplay,
|
|
19
|
+
quoteHeadlineTime,
|
|
19
20
|
quoteSessionLabel,
|
|
21
|
+
quoteSessionStateLabel,
|
|
20
22
|
type ExtendedSession,
|
|
21
23
|
} from "../../market-data/market/status";
|
|
22
|
-
import { formatCompact, formatDistributionAmount, formatPercent, formatPercentRaw } from "../../utils/format";
|
|
24
|
+
import { formatCompact, formatCurrency, formatDistributionAmount, formatNumber, formatPercent, formatPercentRaw } from "../../utils/format";
|
|
23
25
|
import { withCliServices, withMarketData } from "../context";
|
|
24
26
|
import { isoDate, parsePositiveInt, requireOneArg, takeFlag, takeOption } from "./command-utils";
|
|
25
27
|
import type { BuiltinCliCommandDef } from "../command-options";
|
|
@@ -59,6 +61,7 @@ import {
|
|
|
59
61
|
formatOptionIvCell,
|
|
60
62
|
formatOptionQuoteCell,
|
|
61
63
|
OPTION_MODEL_RATE,
|
|
64
|
+
OPTION_VOLATILITY_FIELDS,
|
|
62
65
|
OPTIONS_USAGE,
|
|
63
66
|
optionRows,
|
|
64
67
|
optionsBasisFields,
|
|
@@ -81,6 +84,7 @@ import {
|
|
|
81
84
|
type CliReportTables,
|
|
82
85
|
} from "../report-tables";
|
|
83
86
|
import {
|
|
87
|
+
formingPeriodBar,
|
|
84
88
|
historyBarStatusNote,
|
|
85
89
|
historyCoverageWarning,
|
|
86
90
|
historyFacts,
|
|
@@ -102,6 +106,7 @@ import { bareFuturesCode } from "../../market-data/futures-board";
|
|
|
102
106
|
import { newsEmptyMessage } from "./news-hints";
|
|
103
107
|
import { newsFallbackText, newsSearchHeading, newsSearchText, newsSearchTextProblem, searchNewsStories } from "./news-search";
|
|
104
108
|
import { formatUtcTime } from "../../utils/utc-time";
|
|
109
|
+
import { DAY_SO_FAR_TEXT, withSessionBar } from "../../time-series/session-bar";
|
|
105
110
|
import { isFiniteNumber } from "../../utils/guards";
|
|
106
111
|
import { exportedFundamentals } from "../../utils/price-earnings";
|
|
107
112
|
import { fundamentalsFreshness, fxPairFreshness, quotesFreshness, rowsFreshness, valuationFreshness } from "../freshness";
|
|
@@ -116,7 +121,15 @@ import {
|
|
|
116
121
|
SEC_FILINGS,
|
|
117
122
|
} from "../../plugins/builtin/shared/report-freshness";
|
|
118
123
|
import {
|
|
119
|
-
|
|
124
|
+
earningsDateStatus,
|
|
125
|
+
earningsDetailParts,
|
|
126
|
+
earningsEpsBasis,
|
|
127
|
+
epsBasisName,
|
|
128
|
+
isProvisionalActual,
|
|
129
|
+
quarterLabel,
|
|
130
|
+
showsEpsBasis,
|
|
131
|
+
} from "../../plugins/builtin/shared/earnings-report";
|
|
132
|
+
import {
|
|
120
133
|
tradingBoardNote,
|
|
121
134
|
EXCHANGE_OPTION,
|
|
122
135
|
futuresTryLine,
|
|
@@ -132,16 +145,15 @@ import {
|
|
|
132
145
|
loadForListing,
|
|
133
146
|
loadListingQuote,
|
|
134
147
|
listingFateLoader,
|
|
135
|
-
|
|
136
|
-
noLongerTradingNote,
|
|
148
|
+
movedSymbolLine,
|
|
137
149
|
notTradedMessage,
|
|
138
150
|
quoteBareListing,
|
|
139
151
|
requireCliListing,
|
|
140
|
-
tradingListing,
|
|
141
152
|
type CliListing,
|
|
142
153
|
type ListingDataRequest,
|
|
143
154
|
type ListingIdentity,
|
|
144
155
|
} from "../listing-arg";
|
|
156
|
+
import { bareListingNote, listingFateNotes, noLongerTradingNote, tradingListing } from "../../tickers/listing-fate";
|
|
145
157
|
import { indexRootAlsoLine, indexRootFor, indexRootTryLine, isYieldIndexSymbol } from "../index-roots";
|
|
146
158
|
import { earningsCalendarFailure, earningsChoiceNote, earningsDateLabel, isListingAbroad, noListingCalendarNote } from "../earnings-calendar";
|
|
147
159
|
import { formatBasisPoints, toBasisPoints } from "../../utils/basis-points";
|
|
@@ -155,11 +167,11 @@ import { secRegistrantMismatchMessage, SecRegistrantMismatchError, areDifferentC
|
|
|
155
167
|
import { isCryptoPairSymbol } from "../../utils/crypto-pair";
|
|
156
168
|
import { looksLikeCurrencyPair } from "../../utils/currency-pair";
|
|
157
169
|
import { canonicalExchange, isUsListingExchange } from "../../utils/exchanges";
|
|
158
|
-
import { describeTickerChange, startTickerChangeLookup, tickerChangeLine, type TickerChange } from "../../tickers/ticker-changes";
|
|
170
|
+
import { describeTickerChange, movedAwayChange, startTickerChangeLookup, tickerChangeLine, type TickerChange } from "../../tickers/ticker-changes";
|
|
159
171
|
import { noSecReportsNotice, nonUsSecListingVenue } from "../../utils/sec";
|
|
160
172
|
import type { MarketContext } from "../types";
|
|
161
173
|
import { windowRows } from "../row-window";
|
|
162
|
-
import { crossRate, describeFxRate, isUsableRate,
|
|
174
|
+
import { crossRate, describeFxConversion, describeFxRate, FX_USAGE, isUsableRate, parseFxArgs } from "../fx-pair";
|
|
163
175
|
import {
|
|
164
176
|
holderListFacts,
|
|
165
177
|
holderListPeriod,
|
|
@@ -271,7 +283,8 @@ const QUOTE_LEAD_COLUMNS = [
|
|
|
271
283
|
extendedColumn("PRE"),
|
|
272
284
|
extendedColumn("POST"),
|
|
273
285
|
extendedColumn("OVERNIGHT"),
|
|
274
|
-
|
|
286
|
+
// An export keeps the market state (PRE-MKT); the table names the close a pre-market with no trade stands at (Thu close).
|
|
287
|
+
{ key: "session", header: "Session", shrink: false, format: (_value: unknown, row: QuoteRow) => row.sessionLabel },
|
|
275
288
|
];
|
|
276
289
|
|
|
277
290
|
type QuoteRow = ReturnType<typeof quoteRows>[number];
|
|
@@ -393,7 +406,8 @@ function quoteRows(results: QuoteCliRecord[], now = Date.now()) {
|
|
|
393
406
|
extendedChangePercent: roundedPercent(extended?.changePercent),
|
|
394
407
|
// The extended price is the session's bid and ask midpoint, nothing having traded in it.
|
|
395
408
|
extendedMark: extended?.mark === true,
|
|
396
|
-
session:
|
|
409
|
+
session: quoteSessionStateLabel(quote, now) ?? "",
|
|
410
|
+
sessionLabel: quoteSessionLabel(quote, now) ?? "",
|
|
397
411
|
// The close the shown move is measured from: the one before the session Last is.
|
|
398
412
|
previousClose: price(display?.change != null ? display.price - display.change : quote?.previousClose),
|
|
399
413
|
dayRange: quote?.low != null && quote.high != null
|
|
@@ -404,7 +418,8 @@ function quoteRows(results: QuoteCliRecord[], now = Date.now()) {
|
|
|
404
418
|
providerId: quote?.providerId ?? "",
|
|
405
419
|
source: quote?.dataSource ?? quote?.providerId ?? "",
|
|
406
420
|
feed: quoteFeedState(quote, now),
|
|
407
|
-
|
|
421
|
+
// When the price Last shows was observed: in a pre-market nothing has traded in, the close it stands at.
|
|
422
|
+
updatedAt: quote?.lastUpdated ? new Date(quoteHeadlineTime(quote)).toISOString() : "",
|
|
408
423
|
error: result.error ?? "",
|
|
409
424
|
};
|
|
410
425
|
});
|
|
@@ -490,19 +505,32 @@ function corporateActionRows(data: CorporateActionsData) {
|
|
|
490
505
|
// An upcoming estimate carries no unit of its own; it shares the one every reported quarter states.
|
|
491
506
|
const reportedCurrencies = new Set(data.earnings.map((event) => event.currency?.trim()).filter(Boolean));
|
|
492
507
|
const earningsCurrency = reportedCurrencies.size === 1 ? [...reportedCurrencies][0] : undefined;
|
|
508
|
+
// To the cent, as ERN and EE show it; a consensus average carries more digits.
|
|
493
509
|
const eps = (value: number | undefined, unit: string | undefined) => (
|
|
494
|
-
value == null || !
|
|
495
|
-
:
|
|
510
|
+
value == null || !Number.isFinite(value) ? "-"
|
|
511
|
+
: !unit ? formatNumber(value, 2)
|
|
512
|
+
: /^[A-Z]{3}$/.test(unit) ? formatCurrency(value, unit, 2) : `${formatNumber(value, 2)} ${unit}`
|
|
496
513
|
);
|
|
497
514
|
return [
|
|
498
515
|
...data.earnings.map((event) => {
|
|
499
516
|
const unit = event.currency?.trim() || earningsCurrency;
|
|
517
|
+
const quarter = quarterLabel(event.fiscalPeriod);
|
|
518
|
+
const basis = earningsEpsBasis(event.epsBasis);
|
|
519
|
+
// History rows are keyed by fiscal quarter end, upcoming ones by announcement date.
|
|
520
|
+
const note = [event.dateType === "fiscal-period-end" ? "period end" : null, quarter === "-" ? null : quarter].filter(Boolean).join(", ");
|
|
500
521
|
return {
|
|
501
522
|
type: "earnings",
|
|
502
523
|
date: event.date,
|
|
503
|
-
//
|
|
504
|
-
detail:
|
|
505
|
-
|
|
524
|
+
// The basis, beat, date status and analysts are the words EE uses for the same row.
|
|
525
|
+
detail: [event.epsActual == null ? `est ${eps(event.epsEstimate, unit)}` : `eps ${eps(event.epsActual, unit)}`,
|
|
526
|
+
...earningsDetailParts(event)].join(", ") + (note ? ` (${note})` : ""),
|
|
527
|
+
epsActual: event.epsActual,
|
|
528
|
+
epsEstimate: event.epsEstimate,
|
|
529
|
+
epsBasis: showsEpsBasis({ epsActual: event.epsActual, epsBasis: basis }) ? epsBasisName(basis) : undefined,
|
|
530
|
+
...(isProvisionalActual(event) ? { epsProvisional: true } : {}),
|
|
531
|
+
epsAnalysts: event.epsAnalysts,
|
|
532
|
+
dateStatus: earningsDateStatus(event.dateStatus) ?? undefined,
|
|
533
|
+
fiscalPeriod: event.fiscalPeriod,
|
|
506
534
|
};
|
|
507
535
|
}),
|
|
508
536
|
...data.dividends.map((event) => ({
|
|
@@ -562,6 +590,17 @@ function earningsRows(events: EarningsEvent[]) {
|
|
|
562
590
|
/** Symbols at most whose venues a command looks up to name the company each bare one went to. */
|
|
563
591
|
const MAX_CHOICE_LOOKUPS = 10;
|
|
564
592
|
|
|
593
|
+
/**
|
|
594
|
+
* The line `quote` prints beside a symbol that changed, as `history` and
|
|
595
|
+
* `ticker` word it: a company that moved, with nothing new listed there,
|
|
596
|
+
* names the quote of its new symbol ("HFC became DINO on 2022-03-15:
|
|
597
|
+
* `gloomberb quote DINO`"); its old line's last price stays in the row.
|
|
598
|
+
*/
|
|
599
|
+
function quoteChangeLine(change: TickerChange | null | undefined): string | null {
|
|
600
|
+
const moved = movedAwayChange(change);
|
|
601
|
+
return moved ? movedSymbolLine(moved, `gloomberb quote ${moved.tradesAs}`) : tickerChangeLine(change);
|
|
602
|
+
}
|
|
603
|
+
|
|
565
604
|
async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1], commandName: "quote" | "compare") {
|
|
566
605
|
const args = [...rawArgs];
|
|
567
606
|
const exchange = takeOption(args, "--exchange") ?? "";
|
|
@@ -600,7 +639,7 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
|
|
|
600
639
|
}));
|
|
601
640
|
const failed = answers[0]?.failure;
|
|
602
641
|
if (listings.length === 1 && failed) {
|
|
603
|
-
ctx.fail(failed.message, [
|
|
642
|
+
ctx.fail(failed.message, [quoteChangeLine(tickerChanges[0]), failed.details].filter(Boolean).join("\n") || undefined);
|
|
604
643
|
}
|
|
605
644
|
// A futures quote adds its dated settlement and contract terms, as `ticker` prints them; `compare` stays a table.
|
|
606
645
|
const contracts = await Promise.all(answers.map(({ result }) => commandName === "quote" && result.quote
|
|
@@ -632,7 +671,7 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
|
|
|
632
671
|
const notes = [
|
|
633
672
|
// FB now lists another security; the company that was FB trades as META. A symbol a
|
|
634
673
|
// company renamed into (META) is what was typed: its change is in the row, not here.
|
|
635
|
-
...tickerChanges.flatMap((change) =>
|
|
674
|
+
...tickerChanges.flatMap((change) => quoteChangeLine(change) ?? []),
|
|
636
675
|
// Which listing a bare symbol was read as, and the index a bare root is not, are in no row.
|
|
637
676
|
...answers.flatMap((answer) => answer.notes),
|
|
638
677
|
// A row that stopped trading long ago reads as a quote that is merely stale.
|
|
@@ -839,7 +878,10 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
839
878
|
.catch((error) => failHistory(error, listing.key, ctx)),
|
|
840
879
|
quoted,
|
|
841
880
|
]);
|
|
842
|
-
const {
|
|
881
|
+
const { resolution } = loaded;
|
|
882
|
+
// While the session trades the day's bar is the quote's, whatever range the history was cut for (withSessionBar).
|
|
883
|
+
const points = withSessionBar(loaded.points, quote, { now: Date.now(), venue: quote?.listingExchangeName || exchange || undefined });
|
|
884
|
+
const fromQuote = points !== loaded.points;
|
|
843
885
|
const identity = listingIdentity(listing, quote);
|
|
844
886
|
// Without a quote currency, the listing metadata research reads (as `ticker` does) may still state it.
|
|
845
887
|
const listed = quote?.currency ? quote : await market.dataProvider.getQuoteMetadata?.(symbol, exchange).catch(() => null);
|
|
@@ -849,20 +891,28 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
849
891
|
};
|
|
850
892
|
// A coin's volume is traded value in its quote currency: said in every row and on the status line.
|
|
851
893
|
const volume = historyVolume(unit, traded);
|
|
852
|
-
const
|
|
894
|
+
const served = historyRows(points, resolution, unit.currency, unit.unit, volume);
|
|
895
|
+
// Bars stop arriving on the listing's own trading days, not New York's.
|
|
896
|
+
const venue = quote?.listingExchangeName || exchange || undefined;
|
|
897
|
+
// October's monthly bar on 16 Oct runs through the 15th: marked partial, and the status line says how far.
|
|
898
|
+
const forming = formingPeriodBar(served, venue);
|
|
899
|
+
const data = forming?.rows ?? served;
|
|
853
900
|
const interval = data[0]?.interval ?? null;
|
|
854
901
|
const decimals = historyPriceDecimals(data, listing.saved?.metadata.assetCategory);
|
|
855
902
|
const price = (value: unknown) => typeof value === "number" ? value.toFixed(decimals) : "";
|
|
856
903
|
// Intraday bars print in UTC, as the charts and time and sales label them, not the host zone.
|
|
857
904
|
const intraday = data.some((row) => row.date.length > 10);
|
|
858
905
|
// A bar history: dated by its last bar, stale once bars of its size stop arriving.
|
|
906
|
+
// The day's own bar, while its session trades, is not a close: dated by the quote it was read from.
|
|
907
|
+
const dayToDate = fromQuote && interval === "1d" && data.at(-1)?.partial === true;
|
|
859
908
|
const freshness = rowsFreshness(data, {
|
|
860
909
|
...barHistoryFreshness(intraday ? null : barResolutionFromDates(data.map((row) => row.date))),
|
|
861
910
|
observedKey: "date",
|
|
862
911
|
oldest: null,
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
...(
|
|
912
|
+
cadenceExchange: venue,
|
|
913
|
+
...(forming ? { periodToDate: forming.periodToDate } : {}),
|
|
914
|
+
...(dayToDate ? { basis: "daily bars", ...(quote && Number.isFinite(quote.lastUpdated) ? { asOf: new Date(quote.lastUpdated).toISOString() } : {}) } : {}),
|
|
915
|
+
...(volume || dayToDate ? { note: [volume ? historyVolumeText(volume, interval) : null, dayToDate ? `the newest bar is ${DAY_SO_FAR_TEXT}` : null].filter(Boolean).join("; ") } : {}),
|
|
866
916
|
});
|
|
867
917
|
const gaps = historyGapNote(data, traded);
|
|
868
918
|
const notes = [
|
|
@@ -870,7 +920,8 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
870
920
|
...listingFateNotes(await fate(), { served: data.length > 0, firstDate: data[0]?.date }),
|
|
871
921
|
...await listingChoiceNotes(listing, market, quote, venues),
|
|
872
922
|
...historyNotes(range, interval, data[0]?.date), historyCoverageWarning(range, loaded) ?? [],
|
|
873
|
-
|
|
923
|
+
// The status line says how far a bar still forming runs; the note is for the bars it does not name.
|
|
924
|
+
gaps ?? [], historyJumpNote(data) ?? [], historyFlagNote(data) ?? [], historyBarStatusNote(forming ? served : data) ?? [],
|
|
874
925
|
].flat();
|
|
875
926
|
const priceColumns: CliResultColumn[] = [
|
|
876
927
|
intraday
|
|
@@ -1627,6 +1678,8 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1627
1678
|
interval: OPTIONS_EXPORT_INTERVAL,
|
|
1628
1679
|
// `iv` is annualised volatility and `delta` the option's change per 1.00 move in the underlying, both decimals.
|
|
1629
1680
|
units: { iv: "annualized decimal", delta: "decimal per 1.00 move in the underlying" },
|
|
1681
|
+
// What each contract's `iv` and `impliedVolatility` are, so neither is read as the other.
|
|
1682
|
+
fields: OPTION_VOLATILITY_FIELDS,
|
|
1630
1683
|
// What `iv` and `delta` on each contract are valued from; the provider's own impliedVolatility is left as sent.
|
|
1631
1684
|
model: {
|
|
1632
1685
|
spot: basisFields.spot, spotBasis: basisFields.spotBasis, spotAsOf: basisFields.spotAsOf,
|
|
@@ -1685,13 +1738,12 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1685
1738
|
/** What an options chain export's `interval` says: one quote snapshot, not bars. */
|
|
1686
1739
|
const OPTIONS_EXPORT_INTERVAL = "snapshot";
|
|
1687
1740
|
|
|
1688
|
-
const FX_USAGE = "fx <currency> | fx <base>/<quote>";
|
|
1689
|
-
|
|
1690
1741
|
async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
1691
|
-
|
|
1742
|
+
if (rawArgs.length === 0) ctx.fail(`Usage: gloomberb ${FX_USAGE}`);
|
|
1692
1743
|
await withMarketData(ctx, async (market) => {
|
|
1693
|
-
const
|
|
1694
|
-
if ("error" in
|
|
1744
|
+
const parsed = parseFxArgs(rawArgs, market.config.baseCurrency);
|
|
1745
|
+
if ("error" in parsed) return ctx.fail(parsed.error, `Usage: gloomberb ${FX_USAGE}`);
|
|
1746
|
+
const { request, amount, toUsdByDefault } = parsed;
|
|
1695
1747
|
const { currency, baseCurrency } = request;
|
|
1696
1748
|
const pair = `${currency}/${baseCurrency}`;
|
|
1697
1749
|
const reasons = new Map<string, string>();
|
|
@@ -1719,7 +1771,8 @@ async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]
|
|
|
1719
1771
|
const observed = legs.flatMap((leg) => leg?.asOf ?? []);
|
|
1720
1772
|
const asOf = observed.length > 0 ? new Date(Math.min(...observed)).toISOString() : null;
|
|
1721
1773
|
const stale = legs.some((leg) => leg != null && (leg.staleAt <= Date.now() || leg.refreshError != null));
|
|
1722
|
-
const
|
|
1774
|
+
const converted = amount == null ? null : amount * rate;
|
|
1775
|
+
const row = { currency, baseCurrency, rate, asOf, stale, pair, inverse: 1 / rate, ...(amount == null ? {} : { amount, converted }) };
|
|
1723
1776
|
// The closing line dates the rate, and says when it is stale, as `fn FXC` does.
|
|
1724
1777
|
const freshness = fxPairFreshness(new Map(codes.flatMap((code, index) => {
|
|
1725
1778
|
const leg = legs[index];
|
|
@@ -1728,8 +1781,13 @@ async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]
|
|
|
1728
1781
|
timing: { asOf: leg.asOf, staleAt: leg.staleAt, error: leg.refreshError, delayMinutes: leg.delayMinutes },
|
|
1729
1782
|
}] as const] : [];
|
|
1730
1783
|
})));
|
|
1731
|
-
|
|
1732
|
-
|
|
1784
|
+
// An amount with one currency names no target; the answer says it went to USD.
|
|
1785
|
+
const warnings = toUsdByDefault && amount != null
|
|
1786
|
+
? [`No target currency given, so ${currency} is converted to USD; for another, write fx ${rawArgs[0]!.trim()} ${currency}/<to>.`]
|
|
1787
|
+
: undefined;
|
|
1788
|
+
ctx.printResult({ data: [row], freshness, warnings }, {
|
|
1789
|
+
// The conversion, then the rate it used; the source line under it dates that rate.
|
|
1790
|
+
text: () => amount == null ? describeFxRate(request, rate) : `${describeFxConversion(request, amount, rate)}\n${describeFxRate(request, rate)}`,
|
|
1733
1791
|
// The rate is the base currency's price of one unit of the other, at the latest quote of each leg.
|
|
1734
1792
|
exportIdentity: { symbolOrSeries: pair, currencyOrUnit: baseCurrency, interval: "spot" },
|
|
1735
1793
|
columns: [
|
|
@@ -1740,6 +1798,10 @@ async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]
|
|
|
1740
1798
|
{ key: "stale", header: "Stale" },
|
|
1741
1799
|
{ key: "pair", header: "Pair" },
|
|
1742
1800
|
{ key: "inverse", header: "Inverse", align: "right" },
|
|
1801
|
+
...(amount == null ? [] : [
|
|
1802
|
+
{ key: "amount", header: "Amount", align: "right" as const },
|
|
1803
|
+
{ key: "converted", header: "Converted", align: "right" as const },
|
|
1804
|
+
]),
|
|
1743
1805
|
],
|
|
1744
1806
|
});
|
|
1745
1807
|
});
|
|
@@ -2097,18 +2159,23 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
2097
2159
|
},
|
|
2098
2160
|
{
|
|
2099
2161
|
name: "fx",
|
|
2100
|
-
description: "Convert a currency into your base currency,
|
|
2162
|
+
description: "Convert a currency into your base currency, quote a pair such as USD/NGN, or convert an amount such as fx 1234.5 EUR/USD",
|
|
2101
2163
|
help: {
|
|
2102
2164
|
group: CLI_COMMAND_GROUPS.markets,
|
|
2103
|
-
usage: ["fx <currency>", "fx <base>/<quote>"],
|
|
2165
|
+
usage: ["fx <currency>", "fx <base>/<quote>", "fx <amount> <from>/<to>", "fx <amount> <from> <to>"],
|
|
2104
2166
|
sections: [{
|
|
2105
2167
|
title: "Direction",
|
|
2106
2168
|
lines: [
|
|
2107
2169
|
"A bare code is one unit of it in your base currency. A pair reads as markets quote it: USD/NGN is how many NGN one USD buys. Two codes neither of which is USD, such as ZAR/NGN, cross through their USD rates.",
|
|
2108
2170
|
"The answer states both directions: 1 NGN = 0.000752791 USD (USD/NGN 1328.39). --json, --csv and --ndjson carry rate (base currency per currency), pair, inverse, asOf and stale.",
|
|
2109
2171
|
],
|
|
2172
|
+
}, {
|
|
2173
|
+
title: "Amounts",
|
|
2174
|
+
lines: [
|
|
2175
|
+
"An amount in front converts it from the first currency to the second: fx 1234.5 EUR/USD, or fx 1234,5 EUR USD. It takes either decimal mark (1.234,5 and 1,234.5 are 1234.5) and reads as an amount of the first currency: 1,234 or 1.234 EUR are thousands, a third decimal being below a cent, while in a currency with three decimals (KWD) fx asks which. An amount and one currency converts to USD. The answer gives the converted amount, then the rate it used, dated by the source line; --json adds amount and converted.",
|
|
2176
|
+
],
|
|
2110
2177
|
}],
|
|
2111
|
-
examples: ["fx EUR", "fx NGN", "fx USD/NGN", "fx ZAR/NGN", "fx JPY --json"],
|
|
2178
|
+
examples: ["fx EUR", "fx NGN", "fx USD/NGN", "fx ZAR/NGN", "fx 1234.5 EUR/USD", "fx 1234,5 EUR USD", "fx JPY --json"],
|
|
2112
2179
|
},
|
|
2113
2180
|
execute: runFx,
|
|
2114
2181
|
},
|