gloomberb 0.16.2 → 0.16.4
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 +4 -1
- package/src/api-client/askg.ts +43 -0
- package/src/api-client/auth.ts +15 -0
- package/src/api-client/beneficial-owners.ts +4 -0
- package/src/api-client/chat.ts +19 -0
- package/src/api-client/company-kpis.ts +14 -2
- package/src/api-client/data.ts +34 -16
- package/src/api-client/index.ts +12 -0
- package/src/api-client/normalizers.ts +27 -0
- package/src/api-client/paths.ts +88 -23
- package/src/api-client/request.ts +2 -1
- package/src/api-client/research-activity.ts +31 -0
- package/src/api-client/socket.ts +39 -8
- package/src/api-client/types.ts +57 -1
- package/src/api-client/upgrade-experiment.ts +90 -0
- package/src/app/global-shortcuts.ts +11 -1
- package/src/app/keybindings/actions.ts +10 -0
- package/src/app/pane-runtime/index.tsx +8 -0
- package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +65 -25
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
- package/src/app/runtime/desktop-deeplink.ts +5 -0
- package/src/app/star-prompt/model.ts +98 -0
- package/src/app/star-prompt/runtime.ts +41 -0
- package/src/app/star-prompt/status-line.tsx +190 -0
- package/src/app.tsx +13 -7
- package/src/architecture/core-builtin-allowlist.json +70 -0
- package/src/brokers/account-cache.ts +45 -0
- package/src/brokers/signed-in/adapter.ts +33 -2
- package/src/brokers/signed-in/client.ts +10 -0
- package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
- package/src/brokers/signed-in/sign-in.ts +62 -5
- package/src/capabilities/registry.ts +8 -1
- package/src/capabilities/types.ts +2 -0
- package/src/cli/command-options.ts +74 -0
- package/src/cli/commands/command-utils.ts +29 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +524 -148
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +223 -42
- package/src/cli/commands/search.ts +29 -8
- package/src/cli/commands/system.ts +99 -19
- package/src/cli/commands/themes.ts +64 -0
- package/src/cli/commands/ticker.ts +200 -73
- package/src/cli/desktop-pane-shot.ts +100 -16
- package/src/cli/errors.ts +25 -2
- package/src/cli/freshness.ts +7 -4
- package/src/cli/fx-pair.ts +57 -0
- package/src/cli/help.ts +40 -7
- package/src/cli/history-rows.ts +187 -15
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +21 -13
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +55 -7
- package/src/cli/pane-functions/capabilities.ts +13 -2
- package/src/cli/pane-functions/catalog.ts +13 -2
- package/src/cli/pane-functions/data.ts +36 -5
- package/src/cli/pane-functions/dom.ts +89 -28
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +71 -24
- package/src/cli/pane-functions/headless.ts +168 -35
- package/src/cli/pane-functions/index.ts +12 -2
- package/src/cli/pane-functions/market-session.ts +133 -0
- package/src/cli/pane-functions/options.ts +15 -4
- package/src/cli/pane-functions/report-notices.ts +63 -0
- package/src/cli/pane-functions/report.ts +12 -1
- package/src/cli/pane-functions/resolver.ts +32 -8
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +109 -23
- package/src/cli/registry.ts +8 -4
- package/src/cli/report-tables.ts +460 -0
- package/src/cli/result.ts +63 -16
- package/src/cli/row-window.ts +76 -0
- package/src/cli/scroll-truncation.ts +34 -0
- package/src/components/chart/composite/composite-chart.tsx +43 -4
- package/src/components/chart/composite/legend.tsx +39 -6
- package/src/components/chart/composite/rasterizer.ts +64 -4
- package/src/components/chart/composite/series-cues.ts +71 -0
- package/src/components/chart/composite/text-renderer.ts +16 -8
- package/src/components/chart/composite/types.ts +3 -0
- package/src/components/command-bar/commands/direct/index.ts +9 -1
- package/src/components/command-bar/commands/plugin/items.ts +3 -1
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +2 -0
- package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
- package/src/components/command-bar/routes/root/results.ts +40 -10
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/root/search-providers.ts +97 -14
- package/src/components/command-bar/surface/index.tsx +18 -4
- package/src/components/command-bar/theme-picker.tsx +48 -13
- package/src/components/command-bar/workflow/ops.ts +5 -3
- package/src/components/data-table/export.ts +6 -51
- package/src/components/data-table/view.tsx +31 -10
- package/src/components/layout/floating-pane.tsx +6 -2
- package/src/components/layout/market-summary.ts +23 -9
- package/src/components/layout/pane/content.tsx +46 -29
- package/src/components/layout/pane/cycle-order.ts +2 -11
- package/src/components/layout/pane/header.tsx +2 -1
- package/src/components/layout/pane/title.ts +1 -1
- package/src/components/layout/presentation.ts +22 -0
- package/src/components/layout/shell/active-drag.ts +67 -35
- package/src/components/layout/shell/cursor-occlusion.ts +6 -5
- package/src/components/layout/shell/drag/index.ts +16 -7
- package/src/components/layout/shell/drag/live.ts +193 -0
- package/src/components/layout/shell/drag/overlays.tsx +179 -35
- package/src/components/layout/shell/drag/runtime.ts +62 -50
- package/src/components/layout/shell/drag/slide.ts +10 -0
- package/src/components/layout/shell/index.tsx +84 -57
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
- package/src/components/layout/shell/native/surfaces.ts +30 -14
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +461 -180
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/pane/new-layout.ts +116 -0
- package/src/components/layout/shell/shortcuts.ts +5 -2
- package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-text.tsx +9 -5
- package/src/components/metric-treemap/heat-surface.tsx +27 -8
- package/src/components/metric-treemap/heat-tiles.ts +48 -17
- package/src/components/onboarding/onboarding-frame.tsx +33 -10
- package/src/components/onboarding/onboarding-wizard.tsx +49 -40
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
- package/src/components/onboarding/pro-step-layout.ts +86 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
- package/src/components/ui/button.tsx +10 -0
- package/src/components/ui/data-table/index.tsx +38 -0
- package/src/components/ui/data-table/types.ts +8 -1
- package/src/components/ui/data-table/visible-range.ts +2 -0
- package/src/components/ui/icon.tsx +11 -2
- package/src/components/ui/index.ts +1 -1
- package/src/core/connection-health.ts +1 -1
- package/src/core/state/app/layout-reducer.ts +89 -3
- package/src/core/state/app/state.ts +8 -5
- package/src/core/state/app/types.ts +13 -2
- package/src/data/config/layout.ts +55 -21
- package/src/data/config/store/fold-panes.ts +149 -0
- package/src/data/config/store/migrations.ts +7 -0
- package/src/data/config/store/node.ts +27 -6
- package/src/data/config/store/normalize.ts +37 -3
- package/src/data/json-persistence.ts +17 -3
- package/src/data/json-storage.ts +2 -0
- package/src/data/memory-resource-store.ts +46 -0
- package/src/i18n/es.ts +14 -1
- package/src/i18n/ja.ts +14 -1
- package/src/i18n/ko.ts +14 -1
- package/src/i18n/zh-cn.ts +14 -1
- package/src/i18n/zh-tw.ts +14 -1
- package/src/layout/desks.ts +1 -2
- package/src/layout/pane-layout-move.ts +264 -0
- package/src/layout/pane-manager/hidden-panes.ts +160 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/entries.ts +7 -1
- package/src/market-data/coordinator/quotes.ts +2 -2
- package/src/market-data/market/freshness.ts +156 -11
- package/src/market-data/market/status.ts +40 -0
- package/src/market-data/options-alternatives.ts +69 -0
- package/src/market-data/quotes/contributions.ts +19 -2
- package/src/market-data/quotes/freshness.ts +5 -0
- package/src/market-data/quotes/metadata.ts +16 -0
- package/src/market-data/quotes/screener-live-quotes.ts +56 -5
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +1 -0
- package/src/plugins/builtin/account-management/navigation.ts +1 -1
- package/src/plugins/builtin/account-management/pane.tsx +31 -4
- package/src/plugins/builtin/alerts/quotes.ts +3 -0
- package/src/plugins/builtin/analytics/pane-model.ts +10 -6
- package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
- package/src/plugins/builtin/analytics/risk-model.ts +37 -8
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
- package/src/plugins/builtin/attention/pane.tsx +1 -1
- package/src/plugins/builtin/backtest/pane.tsx +1 -1
- package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/broker-manager/model.ts +8 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
- package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
- package/src/plugins/builtin/broker-manager/table.ts +5 -0
- package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
- package/src/plugins/builtin/catalysts/detail.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +34 -36
- package/src/plugins/builtin/chat/command-bar.ts +138 -0
- package/src/plugins/builtin/chat/composer-commands.ts +21 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
- package/src/plugins/builtin/chat/content/footer.tsx +5 -3
- package/src/plugins/builtin/chat/content/index.tsx +21 -1
- package/src/plugins/builtin/chat/content/mentions.ts +2 -1
- package/src/plugins/builtin/chat/content/user-map.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +48 -0
- package/src/plugins/builtin/chat/conversations.ts +256 -0
- package/src/plugins/builtin/chat/discord-command.ts +91 -0
- package/src/plugins/builtin/chat/edit-window.ts +12 -6
- package/src/plugins/builtin/chat/ghost-user.ts +14 -0
- package/src/plugins/builtin/chat/index.tsx +40 -2
- package/src/plugins/builtin/chat/layout.ts +2 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
- package/src/plugins/builtin/chat/message/header.tsx +24 -15
- package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
- package/src/plugins/builtin/chat/message/render-state.ts +3 -0
- package/src/plugins/builtin/chat/pane-requests.ts +60 -0
- package/src/plugins/builtin/chat/pane-state.ts +32 -2
- package/src/plugins/builtin/chat/pane.tsx +8 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
- package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
- package/src/plugins/builtin/cloud/askg/model.ts +129 -70
- package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
- package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
- package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
- package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
- package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
- package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
- package/src/plugins/builtin/cloud/plugin.tsx +36 -1
- package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
- package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
- package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
- package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
- package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
- package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
- package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
- package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
- package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
- package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
- package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
- package/src/plugins/builtin/comment-letters/client.ts +1 -3
- package/src/plugins/builtin/company-attention/pane.tsx +1 -1
- package/src/plugins/builtin/company-kpis/client.ts +23 -8
- package/src/plugins/builtin/company-kpis/headless.ts +3 -1
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
- package/src/plugins/builtin/composite-plugins.ts +107 -64
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
- package/src/plugins/builtin/credit-boards/index.tsx +21 -21
- package/src/plugins/builtin/credit-boards/model.ts +0 -1
- package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
- package/src/plugins/builtin/debt-maturities/client.ts +37 -9
- package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/data.ts +28 -7
- package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
- package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
- package/src/plugins/builtin/econ/calendar-model.ts +42 -17
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/index.tsx +68 -22
- package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +17 -0
- package/src/plugins/builtin/executives/data.ts +17 -6
- package/src/plugins/builtin/executives/pane.tsx +16 -7
- package/src/plugins/builtin/filing-events/pane.tsx +22 -5
- package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
- package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
- package/src/plugins/builtin/gpu/headless.ts +9 -3
- package/src/plugins/builtin/gpu/model.ts +14 -5
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
- package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
- package/src/plugins/builtin/holders/client.ts +3 -6
- package/src/plugins/builtin/holders/format.ts +31 -1
- package/src/plugins/builtin/holders/headless.ts +32 -6
- package/src/plugins/builtin/holders/pane.tsx +25 -13
- package/src/plugins/builtin/holders/report-header.ts +46 -5
- package/src/plugins/builtin/insider/index.tsx +4 -4
- package/src/plugins/builtin/iv-history/charts.tsx +21 -7
- package/src/plugins/builtin/iv-history/client.ts +12 -2
- package/src/plugins/builtin/iv-history/format.ts +33 -2
- package/src/plugins/builtin/iv-history/headless.ts +22 -10
- package/src/plugins/builtin/iv-history/index.tsx +3 -3
- package/src/plugins/builtin/iv-history/model.ts +61 -13
- package/src/plugins/builtin/iv-history/pane.tsx +11 -3
- package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
- package/src/plugins/builtin/jobs/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
- package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
- package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
- package/src/plugins/builtin/layout-manager/index.tsx +9 -1
- package/src/plugins/builtin/macro-day/client.ts +24 -0
- package/src/plugins/builtin/macro-day/headless.ts +11 -8
- package/src/plugins/builtin/macro-day/index.tsx +3 -0
- package/src/plugins/builtin/macro-day/model.ts +11 -6
- package/src/plugins/builtin/macro-day/pane.tsx +12 -8
- package/src/plugins/builtin/macro-day/release-table.ts +40 -0
- package/src/plugins/builtin/macro-day/releases.ts +62 -44
- package/src/plugins/builtin/market-heatmap/data.ts +25 -5
- package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
- package/src/plugins/builtin/market-heatmap/model.ts +94 -3
- package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
- package/src/plugins/builtin/market-movers/headless.ts +31 -4
- package/src/plugins/builtin/market-movers/index.tsx +8 -1
- package/src/plugins/builtin/market-movers/model.ts +71 -17
- package/src/plugins/builtin/market-movers/table.ts +50 -23
- package/src/plugins/builtin/members/pane.tsx +10 -0
- package/src/plugins/builtin/news/headless.ts +9 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
- package/src/plugins/builtin/openfda/client.ts +1 -1
- package/src/plugins/builtin/options/carry.ts +95 -0
- package/src/plugins/builtin/options/index.tsx +58 -5
- package/src/plugins/builtin/options/strike-window.ts +183 -0
- package/src/plugins/builtin/options/table.ts +55 -3
- package/src/plugins/builtin/options/types.ts +8 -1
- package/src/plugins/builtin/options/view.tsx +161 -12
- package/src/plugins/builtin/options-positioning/headless.ts +4 -2
- package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +73 -17
- package/src/plugins/builtin/options-scenario/headless.ts +71 -24
- package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
- package/src/plugins/builtin/options-scenario/index.tsx +9 -2
- package/src/plugins/builtin/options-scenario/model.ts +33 -0
- package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
- package/src/plugins/builtin/pe-band/model.ts +2 -2
- package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
- package/src/plugins/builtin/perps/equity-row.tsx +8 -5
- package/src/plugins/builtin/perps/model.ts +16 -5
- package/src/plugins/builtin/perps/pane.tsx +1 -1
- package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
- package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
- package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
- package/src/plugins/builtin/plugin-module.ts +87 -4
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
- package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
- package/src/plugins/builtin/realized-vol/headless.ts +3 -0
- package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
- package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
- package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
- package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
- package/src/plugins/builtin/risk-factors/data.ts +23 -9
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/seasonality/pane.tsx +1 -1
- package/src/plugins/builtin/sec/index.tsx +5 -5
- package/src/plugins/builtin/sectors/client.ts +10 -2
- package/src/plugins/builtin/sectors/headless.ts +35 -6
- package/src/plugins/builtin/sectors/index.tsx +18 -1
- package/src/plugins/builtin/sectors/sector-model.ts +150 -41
- package/src/plugins/builtin/shared/command-tab.ts +51 -0
- package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
- package/src/plugins/builtin/shared/members-menu.tsx +260 -0
- package/src/plugins/builtin/shared/report-freshness.ts +50 -2
- package/src/plugins/builtin/shared/ticker-request.ts +25 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
- package/src/plugins/builtin/shared/volatility/options.ts +1 -46
- package/src/plugins/builtin/short-interest/index.tsx +19 -4
- package/src/plugins/builtin/short-interest/surface.tsx +4 -4
- package/src/plugins/builtin/short-volume/index.tsx +1 -10
- package/src/plugins/builtin/short-volume/pane.tsx +1 -1
- package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/themes/pane.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
- package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
- package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
- package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
- package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
- package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
- package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
- package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
- package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
- package/src/plugins/builtin/vol-surface/client.ts +6 -2
- package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
- package/src/plugins/builtin/vol-surface/headless.ts +124 -20
- package/src/plugins/builtin/vol-surface/model.ts +23 -11
- package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
- package/src/plugins/builtin/vol-surface/tables.ts +124 -0
- package/src/plugins/builtin/world-indices/client.ts +22 -4
- package/src/plugins/builtin/world-indices/headless.ts +54 -27
- package/src/plugins/builtin/world-indices/model.ts +4 -2
- package/src/plugins/builtin/world-indices/table.tsx +13 -2
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
- package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -6
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
- package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
- package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
- package/src/plugins/builtin/world-venue-map/model.ts +16 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/builtin-editorial.ts +126 -0
- package/src/plugins/catalog-backend.ts +16 -9
- package/src/plugins/catalog-browser.ts +127 -63
- package/src/plugins/catalog-manifest.ts +73 -40
- package/src/plugins/catalog-ui.ts +26 -6
- package/src/plugins/extracted-plugins.ts +7 -1
- package/src/plugins/ownership.ts +150 -7
- package/src/plugins/plugin-functions.ts +54 -0
- package/src/plugins/plugin-off.ts +36 -0
- package/src/plugins/registry/host-actions.ts +16 -0
- package/src/plugins/registry/index.ts +84 -24
- package/src/plugins/registry/plugin-state.ts +25 -1
- package/src/plugins/runtime/context.tsx +29 -0
- package/src/react/input.ts +48 -1
- package/src/remote/app-host.tsx +5 -0
- package/src/remote/controller.ts +3 -53
- package/src/remote/matching.ts +69 -0
- package/src/remote/schema.ts +1 -1
- package/src/remote/semantic-tree.tsx +14 -0
- package/src/remote/types.ts +2 -0
- package/src/renderers/browser/app-services.ts +3 -1
- package/src/renderers/dom/data-table/dom.ts +31 -0
- package/src/renderers/dom/data-table/index.tsx +5 -0
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/query-bar.tsx +59 -32
- package/src/renderers/dom/host/box.tsx +113 -17
- package/src/renderers/dom/host/drag-shield.ts +35 -0
- package/src/renderers/dom/host/live-frame.ts +62 -0
- package/src/renderers/dom/host/mouse.ts +8 -1
- package/src/renderers/dom/host/scroll-box.tsx +6 -5
- package/src/renderers/dom/host/tabs.tsx +3 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +26 -5
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
- package/src/renderers/electrobun/shared/protocol.ts +12 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/host/native.ts +0 -10
- package/src/renderers/electrobun/view/host/window-move.ts +28 -0
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
- package/src/renderers/electrobun/view/ui-host.tsx +2 -5
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/renderers/opentui/ui-host.tsx +7 -1
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/batches.ts +3 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +13 -7
- package/src/sources/provider-router/financials.ts +106 -16
- package/src/sources/provider-router/history.ts +27 -11
- package/src/sources/provider-router/index.ts +1 -1
- package/src/sources/provider-router/primary.ts +24 -6
- package/src/sources/sec-edgar.ts +68 -8
- package/src/state/pane-scroll-registry.tsx +67 -35
- package/src/sync/core-contributors.ts +4 -3
- package/src/test-support/cli-context.ts +2 -1
- package/src/test-support/data-provider.ts +34 -0
- package/src/test-support/margin-account.ts +108 -0
- package/src/test-support/shell-pane-keys.tsx +1 -0
- package/src/theme/series-colors.ts +46 -0
- package/src/theme/themes.ts +149 -0
- package/src/tickers/search/index.ts +22 -2
- package/src/tickers/search/ranking.ts +45 -1
- package/src/tickers/search/types.ts +3 -1
- package/src/time-series/resolution.ts +15 -0
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/financials.ts +32 -3
- package/src/types/headless.ts +11 -0
- package/src/types/instrument.ts +8 -0
- package/src/types/plugin.ts +49 -3
- package/src/ui/host.tsx +16 -0
- package/src/ui/index.tsx +10 -3
- package/src/ui/scroll-box-axis.ts +26 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/currency-units.ts +8 -3
- package/src/utils/depositary-receipt.ts +54 -10
- package/src/utils/display-number.ts +63 -0
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fundamentals.ts +17 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history-integrity.ts +19 -6
- package/src/utils/price-history.ts +39 -6
- package/src/utils/rpc-timeout-error.ts +6 -2
- package/src/utils/sec-eps-basis.ts +159 -30
- package/src/utils/sec.ts +38 -34
- package/src/utils/throttled-fetch.ts +51 -22
package/src/types/plugin.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { AppTickerRepositoryPort } from "../core/app-service-ports";
|
|
3
|
-
import type { HeadlessPaneDefinition, HeadlessPaneFreshness } from "./headless";
|
|
3
|
+
import type { HeadlessPaneDefinition, HeadlessPaneFreshness, HeadlessPaneOptionDef } from "./headless";
|
|
4
4
|
|
|
5
5
|
export type {
|
|
6
6
|
HeadlessBundleResult,
|
|
@@ -132,6 +132,18 @@ export interface PaneDef {
|
|
|
132
132
|
* shows would be wrong: local data, a calculator, filed records.
|
|
133
133
|
*/
|
|
134
134
|
reportFreshness?: Pick<HeadlessPaneFreshness, "source" | "status" | "basis">;
|
|
135
|
+
/**
|
|
136
|
+
* Options a rendered-view report takes (`fn FXC --currencies USD,ZAR`):
|
|
137
|
+
* each sets the pane setting of its key, is checked by its `normalize`, and
|
|
138
|
+
* is listed with its example by `gloomberb catalog`.
|
|
139
|
+
*/
|
|
140
|
+
reportOptions?: readonly PaneReportOptionDef[];
|
|
141
|
+
/**
|
|
142
|
+
* Lines a rendered-view report prints above its table, from the settings it
|
|
143
|
+
* ran with: what the default view leaves out and how to see more. `--json`
|
|
144
|
+
* carries them in `data.metadata.notices`.
|
|
145
|
+
*/
|
|
146
|
+
reportNotices?(settings: Readonly<Record<string, unknown>>): string[];
|
|
135
147
|
/** Add an Excel-compatible CSV action for the pane's single active DataTable. */
|
|
136
148
|
tableExport?: true;
|
|
137
149
|
settings?: PaneSettingsDef | ((context: PaneSettingsContext) => PaneSettingsDef | null);
|
|
@@ -144,6 +156,20 @@ export interface PaneDef {
|
|
|
144
156
|
quickSettings?: readonly PaneQuickSettingDef[];
|
|
145
157
|
}
|
|
146
158
|
|
|
159
|
+
/**
|
|
160
|
+
* An option of a rendered-view report, which sets the pane setting of its key,
|
|
161
|
+
* or of `settingKey` when the flag reads better than the setting's name
|
|
162
|
+
* (`--columns` for `optionColumnIds`).
|
|
163
|
+
*/
|
|
164
|
+
export interface PaneReportOptionDef extends Omit<HeadlessPaneOptionDef, "pluginState"> {
|
|
165
|
+
/** The value's name in the catalog's flag, `--currencies <codes>`; the type when absent. */
|
|
166
|
+
placeholder?: string;
|
|
167
|
+
/** The option in use as typed after the function, such as `--currencies USD,ZAR,NGN`; the catalog shows it as an example. */
|
|
168
|
+
example?: string;
|
|
169
|
+
/** Checks a typed value and returns it as the pane reads it; throws an Error that names what is wrong. */
|
|
170
|
+
normalize?(value: string): string;
|
|
171
|
+
}
|
|
172
|
+
|
|
147
173
|
export interface PaneQuickSettingDef {
|
|
148
174
|
type: "toggle";
|
|
149
175
|
key: string;
|
|
@@ -389,6 +415,10 @@ export interface CommandResultDef {
|
|
|
389
415
|
label: string;
|
|
390
416
|
detail?: string;
|
|
391
417
|
category?: string;
|
|
418
|
+
/** Short tag drawn left of the label, as on a search provider's row. Six characters at most. */
|
|
419
|
+
badge?: string;
|
|
420
|
+
/** Drawn muted after the label, e.g. a person's full name after their @username. */
|
|
421
|
+
name?: string;
|
|
392
422
|
right?: string;
|
|
393
423
|
keywords?: string[];
|
|
394
424
|
current?: boolean;
|
|
@@ -414,6 +444,8 @@ export interface CommandBarResultDef {
|
|
|
414
444
|
category?: string;
|
|
415
445
|
/** Short tag drawn left of the label, e.g. a document type. Six characters at most. */
|
|
416
446
|
badge?: string;
|
|
447
|
+
/** Drawn muted after the label, e.g. a person's full name after their @username. */
|
|
448
|
+
name?: string;
|
|
417
449
|
right?: string;
|
|
418
450
|
keywords?: string[];
|
|
419
451
|
disabled?: boolean;
|
|
@@ -431,15 +463,29 @@ export interface CommandBarSearchProvider {
|
|
|
431
463
|
category: string;
|
|
432
464
|
/** Sort position of the section. Higher sinks. Navigation sections are negative; use a positive value to sit below them. */
|
|
433
465
|
priority?: number;
|
|
434
|
-
/** Skip
|
|
466
|
+
/** Skip the provider below this length. Default 3. */
|
|
435
467
|
minQueryLength?: number;
|
|
436
|
-
/** Default 300. */
|
|
468
|
+
/** Default 300. Ignored by `match`. */
|
|
437
469
|
debounceMs?: number;
|
|
470
|
+
/**
|
|
471
|
+
* Codes (`CHAT`) after which the provider is still asked, typed alone or
|
|
472
|
+
* with text after them, with the whole query. Every other provider stays
|
|
473
|
+
* quiet once a code claims the text.
|
|
474
|
+
*/
|
|
475
|
+
shortcuts?: readonly string[];
|
|
438
476
|
provide(
|
|
439
477
|
query: string,
|
|
440
478
|
context: CommandBarSearchContext,
|
|
441
479
|
signal: AbortSignal,
|
|
442
480
|
): Promise<CommandBarResultDef[]>;
|
|
481
|
+
/**
|
|
482
|
+
* Rows from what the app already holds in memory. When set, the bar calls
|
|
483
|
+
* this instead of `provide`, as it renders and on every keystroke, so it
|
|
484
|
+
* must be cheap: no request, no waiting. Its rows are ranked with the bar's
|
|
485
|
+
* own matches as they are typed and sit under Suggested in the empty bar.
|
|
486
|
+
* Keep `provide` answering the same rows for older versions of the app.
|
|
487
|
+
*/
|
|
488
|
+
match?(query: string, context: CommandBarSearchContext): CommandBarResultDef[];
|
|
443
489
|
}
|
|
444
490
|
|
|
445
491
|
interface CliHelpColumn {
|
package/src/ui/host.tsx
CHANGED
|
@@ -108,6 +108,16 @@ export interface SyntaxStyleLike {
|
|
|
108
108
|
}): number;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/** Where a drag has a box this frame, in cells. */
|
|
112
|
+
export interface LiveBoxFrame {
|
|
113
|
+
/** Offset from where the box is laid out, carried on the box's own layer. */
|
|
114
|
+
dx?: number;
|
|
115
|
+
dy?: number;
|
|
116
|
+
/** Size in place of the laid-out one: the box and what stretches inside it lay out again, nothing renders. */
|
|
117
|
+
width?: number;
|
|
118
|
+
height?: number;
|
|
119
|
+
}
|
|
120
|
+
|
|
111
121
|
export interface BoxRenderable {
|
|
112
122
|
x?: number;
|
|
113
123
|
y?: number;
|
|
@@ -117,6 +127,12 @@ export interface BoxRenderable {
|
|
|
117
127
|
absoluteY?: number;
|
|
118
128
|
absoluteBounds?: { x: number; y: number; width: number; height: number };
|
|
119
129
|
getBoundingClientRect?: () => { x: number; y: number; width: number; height: number };
|
|
130
|
+
/**
|
|
131
|
+
* Desktop only: draws the box at `frame` by restyling its own element, with
|
|
132
|
+
* no render, until it is called with null. The offset rides the compositor,
|
|
133
|
+
* so it costs no layout or repaint; a size lays out the box's own subtree.
|
|
134
|
+
*/
|
|
135
|
+
setLiveFrame?: (frame: LiveBoxFrame | null) => void;
|
|
120
136
|
[key: string]: unknown;
|
|
121
137
|
}
|
|
122
138
|
|
package/src/ui/index.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createElement, forwardRef, useCallback, useRef, type ComponentProps } from "react";
|
|
2
2
|
import { useForwardedScrollBoxRef, useRegisterPaneScrollBox } from "../state/pane-scroll-registry";
|
|
3
3
|
import { useUiHost, type UiHost } from "./host";
|
|
4
|
+
import { scrollBoxScrollsVertically } from "./scroll-box-axis";
|
|
4
5
|
import { useRemoteUiNode } from "../remote/semantic-tree";
|
|
5
6
|
import { assignRef } from "../react/assign-ref";
|
|
6
7
|
import {
|
|
@@ -48,6 +49,7 @@ export type {
|
|
|
48
49
|
MediaSurfaceHandle,
|
|
49
50
|
MediaSurfaceProps,
|
|
50
51
|
InputRenderable,
|
|
52
|
+
LiveBoxFrame,
|
|
51
53
|
NativeCursorState,
|
|
52
54
|
NativeRendererHost,
|
|
53
55
|
NativePostProcessFn,
|
|
@@ -128,8 +130,11 @@ export const ScrollBox = forwardRef<any, ComponentProps<UiHost["ScrollBox"]>>((p
|
|
|
128
130
|
const { ScrollBox: HostScrollBox } = useUiHost();
|
|
129
131
|
const localRef = useForwardedScrollBoxRef<any>(ref);
|
|
130
132
|
const rawProps = props as Record<string, unknown>;
|
|
133
|
+
// The same rule the hosts scroll by: a ScrollBox that omits scrollY scrolls,
|
|
134
|
+
// so it is a scrollbox to the remote tree and to the pane scroll keys.
|
|
135
|
+
const scrollsVertically = scrollBoxScrollsVertically(props);
|
|
131
136
|
const remoteNodeId = useRemoteUiNode(
|
|
132
|
-
|
|
137
|
+
scrollsVertically
|
|
133
138
|
? {
|
|
134
139
|
role: remotePropRole(rawProps, "scrollbox"),
|
|
135
140
|
label: remotePropLabel(rawProps),
|
|
@@ -150,7 +155,7 @@ export const ScrollBox = forwardRef<any, ComponentProps<UiHost["ScrollBox"]>>((p
|
|
|
150
155
|
...remoteMetadataFromProps(rawProps),
|
|
151
156
|
width: rawProps.width,
|
|
152
157
|
height: rawProps.height,
|
|
153
|
-
scrollY:
|
|
158
|
+
scrollY: true,
|
|
154
159
|
scrollTop: localRef.current?.scrollTop,
|
|
155
160
|
viewportHeight: localRef.current?.viewport?.height,
|
|
156
161
|
},
|
|
@@ -158,7 +163,7 @@ export const ScrollBox = forwardRef<any, ComponentProps<UiHost["ScrollBox"]>>((p
|
|
|
158
163
|
: null,
|
|
159
164
|
);
|
|
160
165
|
useRegisterPaneScrollBox(localRef, {
|
|
161
|
-
enabled:
|
|
166
|
+
enabled: scrollsVertically,
|
|
162
167
|
onScrollActivity: typeof props.onMouseScroll === "function"
|
|
163
168
|
? props.onMouseScroll as (event: { scroll: { direction: "up" | "down"; delta: number } }) => void
|
|
164
169
|
: undefined,
|
|
@@ -191,6 +196,8 @@ export const Input = forwardRef<any, ComponentProps<UiHost["Input"]>>((props, re
|
|
|
191
196
|
},
|
|
192
197
|
metadata: {
|
|
193
198
|
...remoteMetadataFromProps(rawProps),
|
|
199
|
+
// Lets a reader that must not see typed passwords (the Cloud relay) recognize one.
|
|
200
|
+
...(rawProps.type === "password" ? { inputType: "password" } : {}),
|
|
194
201
|
value: rawProps.value,
|
|
195
202
|
placeholder: rawProps.placeholder,
|
|
196
203
|
focused: rawProps.focused,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which axes a ScrollBox scrolls on, decided from its props. The terminal and
|
|
3
|
+
* DOM hosts and the pane keyboard scroll registration all read it here, so a
|
|
4
|
+
* ScrollBox that omits `scrollY` behaves the same everywhere: it scrolls
|
|
5
|
+
* vertically, with the wheel and with the arrows, PageUp, PageDown, Home and
|
|
6
|
+
* End. Pass `scrollY={false}` to turn that off.
|
|
7
|
+
*/
|
|
8
|
+
interface ScrollBoxAxisProps {
|
|
9
|
+
scrollX?: unknown;
|
|
10
|
+
scrollY?: unknown;
|
|
11
|
+
height?: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A one-row strip that only scrolls sideways: tab bars, table headers and
|
|
16
|
+
* chart legends. It is not a vertical scroller even though `scrollY` is
|
|
17
|
+
* omitted.
|
|
18
|
+
*/
|
|
19
|
+
export function isHeaderStripScrollBox(props: ScrollBoxAxisProps): boolean {
|
|
20
|
+
return props.scrollX === true && props.scrollY !== true && props.height === 1;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Whether the ScrollBox scrolls vertically: unless `scrollY` is false, and never a header strip. */
|
|
24
|
+
export function scrollBoxScrollsVertically(props: ScrollBoxAxisProps): boolean {
|
|
25
|
+
return props.scrollY !== false && !isHeaderStripScrollBox(props);
|
|
26
|
+
}
|
package/src/utils/cli-output.ts
CHANGED
|
@@ -32,6 +32,7 @@ const MIN_SHRUNK_COLUMN_WIDTH = 8;
|
|
|
32
32
|
// Text columns first shrink right to left down to this width, then all shrink together.
|
|
33
33
|
const SOFT_MIN_COLUMN_WIDTH = 20;
|
|
34
34
|
let colorEnabledOverride: boolean | null = null;
|
|
35
|
+
let widthOverride: number | null = null;
|
|
35
36
|
|
|
36
37
|
function colorEnabled(): boolean {
|
|
37
38
|
if (colorEnabledOverride != null) return colorEnabledOverride;
|
|
@@ -56,8 +57,14 @@ export function visibleLength(text: string): number {
|
|
|
56
57
|
return typeof Bun !== "undefined" ? Bun.stringWidth(text) : stripAnsi(text).length;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
/**
|
|
60
|
+
/** `--width n`: tables, help and wrapped text fit n columns, whether or not stdout is a terminal. */
|
|
61
|
+
export function setCliWidthOverride(value: number | null): void {
|
|
62
|
+
widthOverride = value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Columns the output is fitted to: `--width`, else the terminal's, or null when output is piped or redirected. */
|
|
60
66
|
export function cliTerminalWidth(): number | null {
|
|
67
|
+
if (widthOverride != null) return widthOverride;
|
|
61
68
|
if (!process.stdout.isTTY) return null;
|
|
62
69
|
const columns = process.stdout.columns;
|
|
63
70
|
return typeof columns === "number" && columns > 0 ? columns : null;
|
|
@@ -139,6 +146,12 @@ export function wrapText(text: string, width: number): string[] {
|
|
|
139
146
|
return lines;
|
|
140
147
|
}
|
|
141
148
|
|
|
149
|
+
/** One line of text under a table, wrapped to the width the table was fitted to; whole when output is not fitted. */
|
|
150
|
+
export function wrapToTerminal(text: string): string {
|
|
151
|
+
const width = cliTerminalWidth();
|
|
152
|
+
return width == null ? text : wrapText(text, width).join("\n");
|
|
153
|
+
}
|
|
154
|
+
|
|
142
155
|
export const cliStyles = {
|
|
143
156
|
bold: (text: string) => applyAnsi(text, "1"),
|
|
144
157
|
dim: (text: string) => applyAnsi(text, "2"),
|
|
@@ -13,11 +13,16 @@ const SUB_UNIT_NAMES: Record<string, string> = {
|
|
|
13
13
|
ZAc: "cents",
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* A currency code as a reader should see it; a sub-unit says which one,
|
|
18
|
+
* "GBp (pence)", and with `ofMajor` what it divides: "ILA (agorot, 1/100 ILS)".
|
|
19
|
+
*/
|
|
20
|
+
export function currencyUnitLabel(code?: string | null, { ofMajor = false }: { ofMajor?: boolean } = {}): string {
|
|
18
21
|
const raw = (code ?? "").trim();
|
|
19
22
|
const unit = SUB_UNIT_NAMES[raw];
|
|
20
|
-
|
|
23
|
+
const major = SUB_UNIT_CURRENCIES[raw];
|
|
24
|
+
if (!unit) return raw;
|
|
25
|
+
return ofMajor && major ? `${raw} (${unit}, 1/${major.divisor} ${major.currency})` : `${raw} (${unit})`;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
const LIKELY_UNIT_MISMATCH_RATIOS: Record<string, number[]> = {
|
|
@@ -1,26 +1,70 @@
|
|
|
1
|
+
import type { Fundamentals, Quote, ShareBasis } from "../types/financials";
|
|
2
|
+
|
|
1
3
|
// A word of its own, so a company called "ADS-TEC" is not one.
|
|
2
4
|
const DEPOSITARY_NAME = /(?:^|\s)(?:ADRs?|ADSs?|American depositary (?:receipts?|shares?))(?=$|[\s,)])/i;
|
|
3
5
|
const DEPOSITARY_TYPES = new Set(["ADR", "ADS", "DEPOSITARYRECEIPT"]);
|
|
4
6
|
/** A profile that opens by naming the security: "Toyota Motor Corporation Sponsored ADR is ...". */
|
|
5
7
|
const DEPOSITARY_SUBJECT = /^.{0,160}?\s(?:ADRs?|ADSs?|American depositary (?:receipts?|shares?)) is\s/i;
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* name, and the security name a profile description opens with. Its share
|
|
11
|
-
* count and market cap are then counted in receipts, not ordinary shares. A
|
|
12
|
-
* listing in another currency is the ordinary line even when its profile
|
|
13
|
-
* mentions the receipts.
|
|
14
|
-
*/
|
|
15
|
-
export function isDepositaryReceipt(input: {
|
|
9
|
+
interface ListingEvidence {
|
|
10
|
+
/** Gloom Cloud's verdict; absent when it has no evidence either way. */
|
|
11
|
+
isDepositaryReceipt?: boolean | null;
|
|
16
12
|
instrumentType?: string | null;
|
|
17
13
|
name?: string | null;
|
|
18
14
|
currency?: string | null;
|
|
19
15
|
description?: string | null;
|
|
20
|
-
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Whether a listing is a depositary receipt. Gloom Cloud's flag decides when
|
|
20
|
+
* it is set. Without it (an older service, or a listing it has no evidence
|
|
21
|
+
* for) this reads the instrument type, the name, and the security name a
|
|
22
|
+
* profile description opens with. A listing in another currency is the
|
|
23
|
+
* ordinary line even when its profile mentions the receipts.
|
|
24
|
+
*/
|
|
25
|
+
export function isDepositaryReceipt(input: ListingEvidence): boolean {
|
|
26
|
+
if (typeof input.isDepositaryReceipt === "boolean") return input.isDepositaryReceipt;
|
|
21
27
|
const currency = input.currency?.trim().toUpperCase();
|
|
22
28
|
if (currency && currency !== "USD") return false;
|
|
23
29
|
if (DEPOSITARY_TYPES.has((input.instrumentType ?? "").replace(/[\s_-]/g, "").toUpperCase())) return true;
|
|
24
30
|
if (input.name && DEPOSITARY_NAME.test(input.name)) return true;
|
|
25
31
|
return DEPOSITARY_SUBJECT.test(input.description?.trim() ?? "");
|
|
26
32
|
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether a record's share counts are in receipts. Gloom Cloud says so with
|
|
36
|
+
* `shareBasis`; without it, a receipt's counts are read as receipts, as its
|
|
37
|
+
* market cap is.
|
|
38
|
+
*/
|
|
39
|
+
function sharesInReceipts(input: ListingEvidence & { shareBasis?: ShareBasis | null }): boolean {
|
|
40
|
+
if (input.shareBasis === "depositary_receipt") return true;
|
|
41
|
+
if (input.shareBasis === "ordinary") return false;
|
|
42
|
+
return isDepositaryReceipt(input);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Whether a listing's shares outstanding are counted in receipts: the
|
|
47
|
+
* fundamentals' basis, else the listing's flag (the fundamentals' or the
|
|
48
|
+
* quote's), else what the quote and profile say.
|
|
49
|
+
*/
|
|
50
|
+
export function sharesOutstandingInReceipts(
|
|
51
|
+
quote: Pick<Quote, "isDepositaryReceipt" | "instrumentType" | "name" | "currency"> | null | undefined,
|
|
52
|
+
fundamentals: Pick<Fundamentals, "isDepositaryReceipt" | "shareBasis"> | null | undefined,
|
|
53
|
+
description?: string | null,
|
|
54
|
+
): boolean {
|
|
55
|
+
return sharesInReceipts({
|
|
56
|
+
instrumentType: quote?.instrumentType,
|
|
57
|
+
name: quote?.name,
|
|
58
|
+
currency: quote?.currency,
|
|
59
|
+
description,
|
|
60
|
+
isDepositaryReceipt: fundamentals?.isDepositaryReceipt ?? quote?.isDepositaryReceipt,
|
|
61
|
+
shareBasis: fundamentals?.shareBasis,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** "1 ADR = 2 ordinary shares", or null without a usable ratio. */
|
|
66
|
+
export function adrRatioText(ratio: number | null | undefined): string | null {
|
|
67
|
+
if (typeof ratio !== "number" || !Number.isFinite(ratio) || ratio <= 0) return null;
|
|
68
|
+
const count = Number.isInteger(ratio) ? String(ratio) : String(Number(ratio.toPrecision(6)));
|
|
69
|
+
return `1 ADR = ${count} ordinary share${ratio === 1 ? "" : "s"}`;
|
|
70
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading back a number a table or report drew ("+1.25%", "1.20B", "$1,234.50"),
|
|
3
|
+
* so a CSV export can write the number a spreadsheet uses and name the unit
|
|
4
|
+
* in its header instead of in every cell.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** What a cell draws for a missing value: a dash, a run of dashes, or the loading ellipsis. */
|
|
8
|
+
export const DISPLAY_PLACEHOLDER = /^(?:[-\u2013\u2014]{1,3}|\u2026|\.\.\.)$/;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A number as a table draws it: sign, currency symbol, thousands commas,
|
|
12
|
+
* decimals, a compact scale (12.3k, 1.20B) and a unit (%, x, bp, USD, or c
|
|
13
|
+
* for the cents grain futures quote in).
|
|
14
|
+
*/
|
|
15
|
+
const DISPLAY_NUMBER = /^([+-]?)([A-Z]{0,3}[$\u20ac\u00a3\u00a5\u20b9\u20a9\u20bd\u20ba\u20aa\u20ab\u0e3f])?(?=\.?\d)(\d{1,3}(?:,\d{3})+|\d*)(?:\.(\d+))?([kKMBT])?(%|x|c|\u00a2| ?bps?| [A-Z]{3})?$/;
|
|
16
|
+
|
|
17
|
+
const SCALE_DIGITS: Record<string, number> = { k: 3, K: 3, M: 6, B: 9, T: 12 };
|
|
18
|
+
|
|
19
|
+
export interface DisplayNumber {
|
|
20
|
+
/** Plain decimal text: no sign for positives, no commas, no scale letter. */
|
|
21
|
+
number: string;
|
|
22
|
+
/** What the column header has to name once the cells are bare numbers. */
|
|
23
|
+
unit: string;
|
|
24
|
+
/** The place value of the last digit drawn: 0.01 for "1.25%", 10,000,000 for "1.20B". */
|
|
25
|
+
step: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const BLANK_DISPLAY = "blank";
|
|
29
|
+
|
|
30
|
+
/** Moves the decimal point right without going through a float, so 1.23B is exactly 1230000000. */
|
|
31
|
+
function scaleDecimal(integer: string, fraction: string, digits: number): string {
|
|
32
|
+
const whole = `${integer}${fraction.padEnd(digits, "0").slice(0, digits)}`.replace(/^0+(?=\d)/, "") || "0";
|
|
33
|
+
const rest = fraction.slice(digits);
|
|
34
|
+
return rest ? `${whole}.${rest}` : whole;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function parseDisplayNumber(text: string): DisplayNumber | typeof BLANK_DISPLAY | null {
|
|
38
|
+
// A direction arrow beside a signed move repeats the sign.
|
|
39
|
+
const trimmed = text.trim().replace(/\u2212/g, "-").replace(/\s*[\u2191\u2193]$/, "");
|
|
40
|
+
if (!trimmed || DISPLAY_PLACEHOLDER.test(trimmed)) return BLANK_DISPLAY;
|
|
41
|
+
const match = DISPLAY_NUMBER.exec(trimmed);
|
|
42
|
+
if (!match) return null;
|
|
43
|
+
const [, sign, currency = "", grouped, fraction = "", scale, unitText = ""] = match;
|
|
44
|
+
const integer = grouped!.replace(/,/g, "") || "0";
|
|
45
|
+
const digits = scale ? SCALE_DIGITS[scale]! : 0;
|
|
46
|
+
const magnitude = digits ? scaleDecimal(integer, fraction, digits) : fraction ? `${integer}.${fraction}` : integer;
|
|
47
|
+
const unit = unitText.trim().replace(/^bps$/, "bp").replace(/^\u00a2$/, "c");
|
|
48
|
+
return {
|
|
49
|
+
number: `${sign === "-" ? "-" : ""}${magnitude}`,
|
|
50
|
+
unit: [currency, unit].filter(Boolean).join(" "),
|
|
51
|
+
step: 10 ** (digits - fraction.length),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Adds the unit the cells dropped, unless the label already names it. */
|
|
56
|
+
export function labelWithUnit(label: string, unit: string): string {
|
|
57
|
+
if (!unit) return label;
|
|
58
|
+
// A word unit has to stand alone: "Max" does not name x.
|
|
59
|
+
const named = unit === "bp" ? /\bbps?\b/i.test(label)
|
|
60
|
+
: /^[A-Za-z]+$/.test(unit) ? new RegExp(`\\b${unit}\\b`, "i").test(label)
|
|
61
|
+
: label.includes(unit);
|
|
62
|
+
return named ? label : `${label} (${unit})`;
|
|
63
|
+
}
|
package/src/utils/exchanges.ts
CHANGED
|
@@ -133,6 +133,13 @@ export const CANONICAL_EXCHANGE_ALIASES: Record<string, string> = {
|
|
|
133
133
|
XJSE: "JSE",
|
|
134
134
|
TLV: "TASE",
|
|
135
135
|
XTAE: "TASE",
|
|
136
|
+
TADAWUL: "TADAWUL",
|
|
137
|
+
XSAU: "TADAWUL",
|
|
138
|
+
QE: "QE",
|
|
139
|
+
QSE: "QE",
|
|
140
|
+
DSMD: "QE",
|
|
141
|
+
DFM: "DFM",
|
|
142
|
+
XDFM: "DFM",
|
|
136
143
|
CCC: "CCC",
|
|
137
144
|
};
|
|
138
145
|
|
|
@@ -222,6 +229,9 @@ export const EXCHANGE_TIME_ZONES: Record<string, string> = {
|
|
|
222
229
|
BYMA: "America/Argentina/Buenos_Aires",
|
|
223
230
|
JSE: "Africa/Johannesburg",
|
|
224
231
|
TASE: "Asia/Jerusalem",
|
|
232
|
+
TADAWUL: "Asia/Riyadh",
|
|
233
|
+
QE: "Asia/Qatar",
|
|
234
|
+
DFM: "Asia/Dubai",
|
|
225
235
|
CCC: "UTC",
|
|
226
236
|
};
|
|
227
237
|
|
|
@@ -275,6 +285,9 @@ const EXCHANGE_NAMES: Record<string, string> = {
|
|
|
275
285
|
BYMA: "Bolsas y Mercados Argentinos",
|
|
276
286
|
JSE: "Johannesburg Stock Exchange",
|
|
277
287
|
TASE: "Tel Aviv Stock Exchange",
|
|
288
|
+
TADAWUL: "Saudi Exchange",
|
|
289
|
+
QE: "Qatar Stock Exchange",
|
|
290
|
+
DFM: "Dubai Financial Market",
|
|
278
291
|
};
|
|
279
292
|
|
|
280
293
|
const KNOWN_EXCHANGE_CODES: ReadonlySet<string> = new Set([
|
package/src/utils/format.ts
CHANGED
|
@@ -8,7 +8,8 @@ function getCurrencyFormatter(currency: string, maximumFractionDigits = 2): Intl
|
|
|
8
8
|
formatter = new Intl.NumberFormat("en-US", {
|
|
9
9
|
style: "currency",
|
|
10
10
|
currency,
|
|
11
|
-
|
|
11
|
+
// Whole units (0) drop the cents rather than throw.
|
|
12
|
+
minimumFractionDigits: Math.min(2, maximumFractionDigits),
|
|
12
13
|
maximumFractionDigits,
|
|
13
14
|
});
|
|
14
15
|
currencyFormatters.set(key, formatter);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Fundamentals } from "../types/financials";
|
|
1
|
+
import type { Fundamentals, TickerFinancials } from "../types/financials";
|
|
2
2
|
|
|
3
3
|
export const RETRACTABLE_VALUATION_FIELDS = ["enterpriseValue", "enterpriseToRevenue"] as const;
|
|
4
4
|
|
|
@@ -13,3 +13,19 @@ export function redactUnavailableFundamentals(value: Fundamentals | undefined):
|
|
|
13
13
|
for (const field of unavailableFields) delete result[field];
|
|
14
14
|
return result;
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
/** A source's enterprise value, or undefined when it has none: banks and insurers come back as 0, which is a gap, not a value. */
|
|
18
|
+
export function reportedEnterpriseValue(fundamentals: Pick<Fundamentals, "enterpriseValue"> | undefined): number | undefined {
|
|
19
|
+
const value = fundamentals?.enterpriseValue;
|
|
20
|
+
return typeof value === "number" && Number.isFinite(value) && value !== 0 ? value : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The currency of the trailing EPS, revenue, income and cash flows: the one the fundamentals declare, else the
|
|
25
|
+
* one the statements report in. Never the quote's, which a depositary receipt can differ from (USD quote, TWD books).
|
|
26
|
+
*/
|
|
27
|
+
export function fundamentalsCurrency(
|
|
28
|
+
financials: Pick<TickerFinancials, "fundamentals" | "financialCurrency"> | null | undefined,
|
|
29
|
+
): string | undefined {
|
|
30
|
+
return financials?.fundamentals?.financialCurrency?.trim() || financials?.financialCurrency?.trim() || undefined;
|
|
31
|
+
}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
+
import { canonicalExchange, parsePublicTickerKey } from "./exchanges";
|
|
2
|
+
import { zonedDateTimeParts, zonedWallClockToUtcMs } from "./zoned-date-time";
|
|
3
|
+
|
|
1
4
|
const HOUR_MS = 60 * 60_000;
|
|
5
|
+
const DAY_MS = 24 * HOUR_MS;
|
|
6
|
+
const NEW_YORK = "America/New_York";
|
|
7
|
+
|
|
8
|
+
/** A spot currency pair: `=X` listing syntax (EURUSD=X, JPY=X) or the CCY venue. */
|
|
9
|
+
export function isCurrencyPairListing(symbol: string | undefined, exchange: string | undefined): boolean {
|
|
10
|
+
return canonicalExchange(exchange) === "CCY" || (!!symbol && parsePublicTickerKey(symbol).symbol.endsWith("=X"));
|
|
11
|
+
}
|
|
2
12
|
|
|
3
13
|
const newYorkClock = new Intl.DateTimeFormat("en-US", {
|
|
4
14
|
timeZone: "America/New_York",
|
|
@@ -19,6 +29,30 @@ export function isFxMarketOpen(time: number): boolean {
|
|
|
19
29
|
return true;
|
|
20
30
|
}
|
|
21
31
|
|
|
32
|
+
/** The latest close of the FX week, Friday 17:00 New York, at or before `time`. */
|
|
33
|
+
export function latestFxWeekClose(time: number): number | null {
|
|
34
|
+
if (!Number.isFinite(time)) return null;
|
|
35
|
+
const { year, month, day } = zonedDateTimeParts(time, NEW_YORK);
|
|
36
|
+
const today = Date.UTC(year, month - 1, day);
|
|
37
|
+
for (let offset = 0; offset <= 7; offset += 1) {
|
|
38
|
+
const date = new Date(today - offset * DAY_MS);
|
|
39
|
+
if (date.getUTCDay() !== 5) continue;
|
|
40
|
+
const close = zonedWallClockToUtcMs(NEW_YORK, date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate(), 17, 0, 0);
|
|
41
|
+
if (close <= time) return close;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* When the closure the market is in began (Friday 17:00 New York time); null
|
|
48
|
+
* while it trades. That is `latestFxWeekClose` for every moment the week is
|
|
49
|
+
* shut, since a closure never lasts to the next Friday, daylight saving weeks
|
|
50
|
+
* included.
|
|
51
|
+
*/
|
|
52
|
+
export function currentFxClosureStart(time: number): number | null {
|
|
53
|
+
return isFxMarketOpen(time) ? null : latestFxWeekClose(time);
|
|
54
|
+
}
|
|
55
|
+
|
|
22
56
|
/**
|
|
23
57
|
* When an FX observation has aged `windowMs`, counting only time the market
|
|
24
58
|
* is open. Friday's last print stays current over the weekend, while a rate
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Option expiries as people type and read them. A chain stamps each expiry as
|
|
3
|
+
* Unix seconds at that calendar date's UTC midnight, so a date and its stamp
|
|
4
|
+
* convert both ways, and two stamps on the same UTC date are the same expiry.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// 9999-12-31 in Unix seconds; a longer number is milliseconds or a typo.
|
|
8
|
+
const MAX_UNIX_SECONDS = 253_402_300_799;
|
|
9
|
+
const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
10
|
+
const UNIX_SECONDS = /^\d{1,12}$/;
|
|
11
|
+
const DAY_MS = 86_400_000;
|
|
12
|
+
|
|
13
|
+
/** How `--expiration` is written, for usage lines and errors. */
|
|
14
|
+
export const OPTION_EXPIRATION_FORMAT = "YYYY-MM-DD (2028-01-21) or Unix seconds (1832025600)";
|
|
15
|
+
/** The value's name in a catalog flag, `--expiration <YYYY-MM-DD|unix>`. */
|
|
16
|
+
export const OPTION_EXPIRATION_PLACEHOLDER = "YYYY-MM-DD|unix";
|
|
17
|
+
|
|
18
|
+
/** The expiry's calendar date, `2028-01-21`; empty for a stamp that is not a time. */
|
|
19
|
+
export function expiryIsoDate(seconds: number): string {
|
|
20
|
+
const date = new Date(seconds * 1000);
|
|
21
|
+
return Number.isNaN(date.getTime()) ? "" : date.toISOString().slice(0, 10);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* `--expiration` as the Unix seconds a chain request takes: `2028-01-21` is that
|
|
26
|
+
* date at 00:00 UTC, a bare integer is already Unix seconds. Anything else,
|
|
27
|
+
* including a date the calendar does not have, is null.
|
|
28
|
+
*/
|
|
29
|
+
export function parseOptionExpiration(raw: string): number | null {
|
|
30
|
+
const text = raw.trim();
|
|
31
|
+
const date = ISO_DATE.exec(text);
|
|
32
|
+
if (date) {
|
|
33
|
+
const [year, month, day] = [Number(date[1]), Number(date[2]), Number(date[3])];
|
|
34
|
+
const at = new Date(Date.UTC(year, month - 1, day));
|
|
35
|
+
const real = at.getUTCFullYear() === year && at.getUTCMonth() === month - 1 && at.getUTCDate() === day;
|
|
36
|
+
return real && at.getTime() > 0 ? at.getTime() / 1000 : null;
|
|
37
|
+
}
|
|
38
|
+
if (!UNIX_SECONDS.test(text)) return null;
|
|
39
|
+
const seconds = Number(text);
|
|
40
|
+
return seconds > 0 && seconds <= MAX_UNIX_SECONDS ? seconds : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A pane function's typed expiry flag as the Unix seconds `parseOptionExpiration`
|
|
45
|
+
* reads; throws an Error naming both accepted forms.
|
|
46
|
+
*/
|
|
47
|
+
export function expirationOptionSeconds(value: string, flag = "expiration"): number {
|
|
48
|
+
const expiration = parseOptionExpiration(value);
|
|
49
|
+
if (expiration == null) throw new Error(`Invalid --${flag} "${value}". Use ${OPTION_EXPIRATION_FORMAT}.`);
|
|
50
|
+
return expiration;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A stored or typed expiry setting (seconds, or text `parseOptionExpiration` reads) as Unix seconds; null when it is neither. */
|
|
54
|
+
export function readOptionExpiration(value: unknown): number | null {
|
|
55
|
+
if (typeof value === "number") return Number.isFinite(value) && value > 0 && value <= MAX_UNIX_SECONDS ? value : null;
|
|
56
|
+
return typeof value === "string" ? parseOptionExpiration(value) : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The listed stamp for the requested expiry's date, which may differ from the requested stamp in time of day. */
|
|
60
|
+
export function findListedExpiry(requested: number, expirations: readonly number[]): number | undefined {
|
|
61
|
+
if (expirations.includes(requested)) return requested;
|
|
62
|
+
const date = expiryIsoDate(requested);
|
|
63
|
+
return date ? expirations.find((expiration) => expiryIsoDate(expiration) === date) : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Whole calendar days from the as-of date to the expiry, both read as UTC dates; negative once it has passed. */
|
|
67
|
+
export function daysToExpiry(expiration: number, asOfMs: number): number {
|
|
68
|
+
const expiry = Date.parse(expiryIsoDate(expiration));
|
|
69
|
+
const asOf = Date.parse(new Date(asOfMs).toISOString().slice(0, 10));
|
|
70
|
+
return Math.round((expiry - asOf) / DAY_MS);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** `469d` to expiry from the as-of date; `expired` once it has passed. */
|
|
74
|
+
export function formatDaysToExpiry(expiration: number, asOfMs: number): string {
|
|
75
|
+
const days = daysToExpiry(expiration, asOfMs);
|
|
76
|
+
return days < 0 ? "expired" : `${days}d`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const NEAREST_EXPIRIES = 4;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The listed expiries nearest a requested date, in date order, so a typo or an
|
|
83
|
+
* unlisted LEAPS date points at the neighbours to pick. `listed` says the date
|
|
84
|
+
* itself is listed (its chain came back empty); `more` counts the rest.
|
|
85
|
+
*/
|
|
86
|
+
function expiryChoices(requested: number, expirations: readonly number[]) {
|
|
87
|
+
const { nearest, more, ...rest } = nearestListedExpiries(requested, expirations);
|
|
88
|
+
return { ...rest, choices: `${nearest.join(", ")}${more > 0 ? ` (+${more} more)` : ""}` };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Why a requested expiry is not in a catalogue, for a line that already names
|
|
93
|
+
* the date: `not listed; nearest: 2027-12-17, 2028-01-21, … (+20 more)`.
|
|
94
|
+
*/
|
|
95
|
+
export function unlistedExpiryText(requested: number, expirations: readonly number[]): string {
|
|
96
|
+
const { total, choices } = expiryChoices(requested, expirations);
|
|
97
|
+
return total === 0 ? "not listed; the chain lists no expiries" : `not listed; nearest: ${choices}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function nearestListedExpiries(
|
|
101
|
+
requested: number,
|
|
102
|
+
expirations: readonly number[],
|
|
103
|
+
count = NEAREST_EXPIRIES,
|
|
104
|
+
): { date: string; listed: boolean; nearest: string[]; more: number; total: number } {
|
|
105
|
+
const date = expiryIsoDate(requested);
|
|
106
|
+
const dates = [...new Set(expirations.map(expiryIsoDate).filter(Boolean))].sort();
|
|
107
|
+
const others = dates.filter((listed) => listed !== date);
|
|
108
|
+
const distance = (listed: string) => Math.abs(Date.parse(listed) - Date.parse(date));
|
|
109
|
+
const nearest = [...others].sort((left, right) => distance(left) - distance(right) || left.localeCompare(right))
|
|
110
|
+
.slice(0, count).sort();
|
|
111
|
+
return { date, listed: dates.includes(date), nearest, more: others.length - nearest.length, total: dates.length };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Why an expiry has no contracts, naming the nearest listed ones:
|
|
116
|
+
* `no expiry 2028-01-22; available: 2027-12-17, 2028-01-21, … (+20 more)`.
|
|
117
|
+
* `subject` names the underlying where nothing else on screen does.
|
|
118
|
+
*/
|
|
119
|
+
export function missingExpiryText(requested: number, expirations: readonly number[], subject?: string): string {
|
|
120
|
+
const { date, listed, total, choices } = expiryChoices(requested, expirations);
|
|
121
|
+
const of = subject ? ` for ${subject}` : "";
|
|
122
|
+
if (total === 0) return `no expiry ${date}${of}; the chain lists no expiries`;
|
|
123
|
+
return listed
|
|
124
|
+
? `no option contracts returned for${subject ? ` ${subject}` : ""} expiry ${date}; nearest others: ${choices}`
|
|
125
|
+
: `no expiry ${date}${of}; available: ${choices}`;
|
|
126
|
+
}
|