gloomberb 0.18.1 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +106 -5
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +21 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/index.ts +2 -0
- package/src/api-client/request.ts +5 -3
- package/src/api-client/telemetry.ts +17 -0
- package/src/api-client/types.ts +5 -0
- package/src/app/global-shortcuts.ts +1 -16
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/app/runtime/update.ts +28 -8
- package/src/architecture/pane-tabs-allowlist.json +65 -0
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/market.ts +354 -187
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +167 -60
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/help.ts +1 -1
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +12 -1
- package/src/cli/issuer-membership.ts +103 -0
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +51 -3
- package/src/cli/name-entries.ts +51 -12
- package/src/cli/pane-functions/capabilities.ts +2 -0
- package/src/cli/pane-functions/catalog.ts +19 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +11 -5
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +27 -3
- package/src/cli/pane-functions/headless.ts +31 -11
- package/src/cli/pane-functions/index.ts +67 -7
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +21 -6
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/report-tables.ts +136 -18
- package/src/cli/result.ts +12 -4
- package/src/cli/series-search.ts +2 -1
- package/src/cli/symbol-list.ts +47 -11
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +25 -7
- package/src/components/chart/composite/drawing-store.ts +26 -6
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/interactions.ts +2 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +69 -8
- package/src/components/chart/composite/types.ts +6 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +1 -1
- package/src/components/command-bar/routes/root/command-items.ts +32 -6
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/data-table/view.tsx +23 -3
- package/src/components/index.ts +3 -0
- package/src/components/layout/detached-pane-shell.tsx +1 -0
- package/src/components/layout/floating-pane.tsx +1 -0
- package/src/components/layout/header.tsx +228 -55
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/layout/pane/header.tsx +10 -3
- package/src/components/layout/pane/index.tsx +1 -0
- package/src/components/layout/pane/pane-tabs.tsx +9 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/stat-grid.tsx +28 -3
- package/src/components/ui/status.tsx +20 -4
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/app-services.ts +4 -0
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +5 -5
- package/src/i18n/es.ts +31 -3
- package/src/i18n/ja.ts +31 -3
- package/src/i18n/ko.ts +31 -3
- package/src/i18n/zh-cn.ts +32 -4
- package/src/i18n/zh-tw.ts +32 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/alert-engine.ts +3 -1
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/events.ts +1 -0
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/alerts/research-rules.ts +1 -0
- package/src/plugins/builtin/analytics/risk-pane.tsx +10 -7
- package/src/plugins/builtin/attention/pane.tsx +16 -14
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/awards/pane.tsx +6 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/export.ts +1 -1
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +184 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +507 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +578 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +11 -7
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/catalysts/client.ts +2 -2
- package/src/plugins/builtin/catalysts/pane.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +32 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +39 -9
- package/src/plugins/builtin/company-attention/app-rank.tsx +4 -5
- package/src/plugins/builtin/company-attention/pane.tsx +13 -11
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +30 -15
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/correlation/headless.ts +2 -1
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
- package/src/plugins/builtin/correlation/settings.ts +1 -0
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/cot/pane.tsx +4 -3
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/doe/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +35 -0
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +30 -5
- package/src/plugins/builtin/equity-screener/model.ts +68 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +129 -119
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +43 -21
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +4 -1
- package/src/plugins/builtin/gpu/changes.tsx +10 -10
- package/src/plugins/builtin/gpu/equities.tsx +4 -1
- package/src/plugins/builtin/gpu/history.tsx +4 -2
- package/src/plugins/builtin/gpu/pane.tsx +5 -3
- package/src/plugins/builtin/holders/client.ts +2 -1
- package/src/plugins/builtin/holders/headless.ts +2 -1
- package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
- package/src/plugins/builtin/insider/base-values.ts +76 -0
- package/src/plugins/builtin/insider/client.ts +3 -0
- package/src/plugins/builtin/insider/headless.ts +48 -10
- package/src/plugins/builtin/insider/index.tsx +17 -8
- package/src/plugins/builtin/insider/issuer.ts +34 -4
- package/src/plugins/builtin/insider/signals-client.ts +2 -2
- package/src/plugins/builtin/insider/table-model.ts +35 -13
- package/src/plugins/builtin/insider/table.tsx +5 -2
- package/src/plugins/builtin/iv-history/events.ts +145 -0
- package/src/plugins/builtin/iv-history/format.ts +3 -0
- package/src/plugins/builtin/iv-history/headless.ts +91 -7
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/jodi/pane.tsx +4 -4
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-movers/client.ts +4 -3
- package/src/plugins/builtin/market-movers/screener.ts +4 -3
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +1 -1
- package/src/plugins/builtin/notes/export.ts +17 -6
- package/src/plugins/builtin/notes/files.ts +16 -5
- package/src/plugins/builtin/options/enrichment-model.ts +10 -9
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/model-spot.ts +70 -4
- package/src/plugins/builtin/options/view.tsx +118 -35
- package/src/plugins/builtin/options-calculator/pane.tsx +7 -4
- package/src/plugins/builtin/options-positioning/headless.ts +11 -3
- package/src/plugins/builtin/options-positioning/model.ts +24 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
- package/src/plugins/builtin/options-scenario/client.ts +19 -1
- package/src/plugins/builtin/options-scenario/headless.ts +13 -6
- package/src/plugins/builtin/options-scenario/pane.tsx +54 -11
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +5 -4
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -5
- package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/power/client.ts +2 -2
- package/src/plugins/builtin/power/pane.tsx +36 -31
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +4 -3
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/client.ts +2 -1
- package/src/plugins/builtin/sectors/client.ts +4 -3
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +64 -0
- package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
- package/src/plugins/builtin/shared/volatility/options.ts +2 -1
- package/src/plugins/builtin/social-mentions/client.ts +2 -1
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/client.ts +23 -3
- package/src/plugins/builtin/vol-surface/headless.ts +33 -14
- package/src/plugins/builtin/vol-surface/model.ts +30 -10
- package/src/plugins/builtin/vol-surface/pane.tsx +35 -15
- package/src/plugins/builtin/vol-surface/stored.ts +34 -3
- package/src/plugins/builtin/vol-surface/tables.ts +14 -0
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +5 -28
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +40 -8
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/query-bar.tsx +2 -0
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +22 -5
- package/src/renderers/dom/host/top-surface.ts +127 -0
- package/src/renderers/dom/styles.css +51 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +21 -1
- package/src/renderers/electrobun/bun/desktop/backend.ts +10 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/renderers/electrobun/view/news-source.ts +4 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +8 -1
- package/src/renderers/opentui/start.tsx +7 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/index.ts +57 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +18 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +62 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/telemetry/crash-reports.ts +4 -0
- package/src/telemetry/update-failures-node.ts +59 -0
- package/src/telemetry/update-failures.ts +307 -0
- package/src/tickers/company-name-listing.ts +9 -7
- package/src/tickers/company-name.ts +43 -0
- package/src/tickers/listing-fate.ts +57 -8
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +11 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +19 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/issuer-listings.ts +28 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/updater/failure.ts +163 -0
- package/src/updater/index.ts +87 -23
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +28 -1
- package/src/utils/currency-units.ts +32 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
- package/src/utils/sec.ts +68 -0
|
@@ -6,8 +6,9 @@ import type {
|
|
|
6
6
|
HeadlessPaneRow,
|
|
7
7
|
} from "../../../types/plugin";
|
|
8
8
|
import { formatCompact, formatCurrency } from "../../../utils/format";
|
|
9
|
-
import { loadParsedInsiderFilings, type InsiderFilingsLoad } from "./client";
|
|
10
|
-
import { insiderEmptyNotice } from "./issuer";
|
|
9
|
+
import { INSIDER_REPORT_FILINGS, loadParsedInsiderFilings, type InsiderFilingsLoad } from "./client";
|
|
10
|
+
import { insiderEmptyNotice, loadHomeInsiderReportsNote } from "./issuer";
|
|
11
|
+
import { FORM4_CURRENCY, withInsiderBaseValues } from "./base-values";
|
|
11
12
|
import {
|
|
12
13
|
buildInsiderRows,
|
|
13
14
|
buildInsiderSummary,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
import { isAmendedInsiderFiling, relevantInsiderAmendments } from "./amendments";
|
|
21
22
|
import { formatInsiderName } from "./display";
|
|
22
23
|
import { SEC_FILINGS } from "../shared/report-freshness";
|
|
23
|
-
import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
|
|
24
|
+
import { resolveHeadlessInstrument, resolveHeadlessIssuerListing } from "../shared/headless-market-data";
|
|
24
25
|
import { noSecReportsNotice } from "../../../utils/sec";
|
|
25
26
|
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
26
27
|
import type { InsiderSignalRoleGroup, InsiderSignalSide, InsiderSignalsPayload } from "../../../api-client/insider-signals";
|
|
@@ -58,6 +59,7 @@ import {
|
|
|
58
59
|
insiderUsualRatio,
|
|
59
60
|
} from "./signals-model";
|
|
60
61
|
import { REPORT_ROWS } from "../shared/headless-options";
|
|
62
|
+
import type { FxHistoryClient } from "../../../market-data/fx-closes";
|
|
61
63
|
|
|
62
64
|
const INSIDER_COLUMNS: HeadlessPaneColumn[] = [
|
|
63
65
|
{ key: "form", header: "Form" },
|
|
@@ -106,6 +108,20 @@ const INSIDER_COLUMNS: HeadlessPaneColumn[] = [
|
|
|
106
108
|
{ key: "note", header: "Note", underRow: true, format: (value) => typeof value === "string" ? value : "" },
|
|
107
109
|
];
|
|
108
110
|
|
|
111
|
+
/** With a base currency other than USD, the dollar columns say they are in USD ("Bought (USD)"), as $ alone is several currencies' sign. */
|
|
112
|
+
function labelDollarColumns(columns: HeadlessPaneColumn[], keys: readonly string[], baseCurrency: string): HeadlessPaneColumn[] {
|
|
113
|
+
if (baseCurrency.trim().toUpperCase() === FORM4_CURRENCY) return columns;
|
|
114
|
+
return columns.map((column) => keys.includes(column.key) ? { ...column, header: `${column.header} (${FORM4_CURRENCY})` } : column);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The trades' columns with the money ones labelled by currency and the converted value and rate after them (`withInsiderBaseValues`). */
|
|
118
|
+
function withMoneyColumns(money: readonly HeadlessPaneColumn[]): HeadlessPaneColumn[] {
|
|
119
|
+
const byKey = new Map(money.map((column) => [column.key, column]));
|
|
120
|
+
return INSIDER_COLUMNS.flatMap((column) => column.key === "totalValue"
|
|
121
|
+
? money.filter((entry) => entry.key !== "pricePerShare")
|
|
122
|
+
: [byKey.get(column.key) ?? column]);
|
|
123
|
+
}
|
|
124
|
+
|
|
109
125
|
const figureOrDash = (value: unknown) => (typeof value === "number" ? value : null);
|
|
110
126
|
|
|
111
127
|
/**
|
|
@@ -183,6 +199,7 @@ function insidersRows(activity: InsiderCompanyActivity, side: InsiderSignalSide)
|
|
|
183
199
|
stakeBothWays: line.buyer.twoWay === true,
|
|
184
200
|
bought: line.bought,
|
|
185
201
|
sold: line.sold,
|
|
202
|
+
currency: FORM4_CURRENCY,
|
|
186
203
|
shares: line.buyer.shares ?? null,
|
|
187
204
|
last: line.buyer.lastBuy,
|
|
188
205
|
}));
|
|
@@ -211,6 +228,7 @@ function signalRows(payload: InsiderSignalsPayload): HeadlessPaneRow[] {
|
|
|
211
228
|
insiders: row.insiders,
|
|
212
229
|
buys: row.buys,
|
|
213
230
|
value: row.value,
|
|
231
|
+
currency: FORM4_CURRENCY,
|
|
214
232
|
shares: row.shares,
|
|
215
233
|
firstBuy: row.firstBuy,
|
|
216
234
|
lastBuy: row.lastBuy,
|
|
@@ -251,6 +269,8 @@ function amendmentNote(entry: ParsedInsiderFiling): string {
|
|
|
251
269
|
}
|
|
252
270
|
|
|
253
271
|
export interface InsiderHeadlessDependencies {
|
|
272
|
+
/** Daily FX closes for values in a base currency other than USD; the report's API client unless a test stands in. */
|
|
273
|
+
fxHistory?: FxHistoryClient;
|
|
254
274
|
loadSignals?(query: InsiderSignalsQuery, limit: number, ctx: HeadlessPaneContext): Promise<InsiderSignalsPayload>;
|
|
255
275
|
loadCompany?(query: InsiderCompanyQuery, ctx: HeadlessPaneContext): Promise<InsiderCompanyActivity>;
|
|
256
276
|
loadParsed(
|
|
@@ -290,6 +310,20 @@ async function noSecReportsEmpty(ctx: HeadlessPaneContext, key: string): Promise
|
|
|
290
310
|
return noSecReportsNotice({ symbol: parsePublicTickerKey(key).symbol, exchange: listing.exchange, name: listing.name ?? null });
|
|
291
311
|
}
|
|
292
312
|
|
|
313
|
+
/**
|
|
314
|
+
* What INS says for a company with no Form 4 of its own: why there is none,
|
|
315
|
+
* and for a company from abroad, that its home market's insider reports are
|
|
316
|
+
* not covered either ("Canadian insider reports (SEDI) not covered.").
|
|
317
|
+
*/
|
|
318
|
+
async function insiderEmpty(ctx: HeadlessPaneContext, key: string, foreignPrivateIssuer: boolean, noFilings: boolean): Promise<string> {
|
|
319
|
+
const [noReports, { exchange }] = await Promise.all([
|
|
320
|
+
noFilings ? noSecReportsEmpty(ctx, key) : null,
|
|
321
|
+
resolveHeadlessInstrument(ctx, key),
|
|
322
|
+
]);
|
|
323
|
+
const home = await loadHomeInsiderReportsNote(ctx.marketData, key, exchange, foreignPrivateIssuer);
|
|
324
|
+
return noReports ? [noReports, home].filter(Boolean).join(" ") : insiderEmptyNotice(foreignPrivateIssuer, home);
|
|
325
|
+
}
|
|
326
|
+
|
|
293
327
|
export function createInsiderHeadless(
|
|
294
328
|
dependencies: InsiderHeadlessDependencies = defaultDependencies,
|
|
295
329
|
): HeadlessPaneDefinition<"rows"> {
|
|
@@ -326,7 +360,7 @@ export function createInsiderHeadless(
|
|
|
326
360
|
aliases: ["count", "rows"],
|
|
327
361
|
description: "Maximum of the company's own Form 4 and Form 4/A filings to parse; without a ticker, companies to rank.",
|
|
328
362
|
type: "integer",
|
|
329
|
-
defaultValue:
|
|
363
|
+
defaultValue: INSIDER_REPORT_FILINGS,
|
|
330
364
|
minimum: 1,
|
|
331
365
|
maximum: 100,
|
|
332
366
|
},
|
|
@@ -441,9 +475,9 @@ export function createInsiderHeadless(
|
|
|
441
475
|
const payload = await (dependencies.loadSignals ?? defaultDependencies.loadSignals!)(query, Number(args.options.limit), ctx);
|
|
442
476
|
const side = payload.side ?? "buy";
|
|
443
477
|
return {
|
|
444
|
-
columns: signalColumns(side),
|
|
478
|
+
columns: labelDollarColumns(signalColumns(side), ["value"], ctx.config.baseCurrency),
|
|
445
479
|
// The exports carry each row's insiders; the text leaves them to fn INS <ticker> --view insiders.
|
|
446
|
-
textColumns: signalColumns(side).filter((column) => column.key !== "buyers"),
|
|
480
|
+
textColumns: labelDollarColumns(signalColumns(side).filter((column) => column.key !== "buyers"), ["value"], ctx.config.baseCurrency),
|
|
447
481
|
rows: signalRows(payload),
|
|
448
482
|
...(payload.rows.length === 0 ? { empty: insiderSignalsEmptyTitle(payload.window, side, insiderSignalsFiltered(query)) } : {}),
|
|
449
483
|
metadata: {
|
|
@@ -472,7 +506,7 @@ export function createInsiderHeadless(
|
|
|
472
506
|
.filter((notice): notice is string => !!notice))];
|
|
473
507
|
const rows = insidersRows(activity, side);
|
|
474
508
|
return {
|
|
475
|
-
columns: INSIDERS_COLUMNS,
|
|
509
|
+
columns: labelDollarColumns(INSIDERS_COLUMNS, ["bought", "sold"], ctx.config.baseCurrency),
|
|
476
510
|
rows,
|
|
477
511
|
...(rows.length === 0 ? { empty: insiderActivityEmptyTitle(window) } : {}),
|
|
478
512
|
metadata: {
|
|
@@ -490,8 +524,7 @@ export function createInsiderHeadless(
|
|
|
490
524
|
}
|
|
491
525
|
const limit = Number(args.options.limit);
|
|
492
526
|
const { parsed, foreignPrivateIssuer, noFilings } = await dependencies.loadParsed(symbol, limit, args, ctx);
|
|
493
|
-
const empty = parsed.length > 0 ? null
|
|
494
|
-
: (noFilings ? await noSecReportsEmpty(ctx, symbol) : null) ?? insiderEmptyNotice(foreignPrivateIssuer);
|
|
527
|
+
const empty = parsed.length > 0 ? null : await insiderEmpty(ctx, symbol, foreignPrivateIssuer, noFilings);
|
|
495
528
|
const name = String(args.options.name ?? "").trim().toLocaleLowerCase();
|
|
496
529
|
const type = isInsiderTypeFilter(args.options.type) ? args.options.type : "all";
|
|
497
530
|
const owned = name
|
|
@@ -503,12 +536,17 @@ export function createInsiderHeadless(
|
|
|
503
536
|
.filter((row) => row.status !== "parsed" && row.status !== "disclosure")
|
|
504
537
|
.map((row) => row.accessionNumber))];
|
|
505
538
|
// A filing's lines sit together, so its note goes once, under the last of them.
|
|
506
|
-
const
|
|
539
|
+
const filed: HeadlessPaneRow[] = buildInsiderRows(filtered, parsed).map((row, index) => {
|
|
507
540
|
const entry = filtered[index]!;
|
|
508
541
|
const last = filtered[index + 1]?.filing.accessionNumber !== entry.filing.accessionNumber;
|
|
509
542
|
return isAmendedInsiderFiling(entry) && last ? { ...row, note: amendmentNote(entry) || null } : row;
|
|
510
543
|
});
|
|
544
|
+
// Every value says its currency; a base currency other than USD gets each value converted beside it.
|
|
545
|
+
const based = await withInsiderBaseValues(filed, ctx.config.baseCurrency, dependencies.fxHistory ?? ctx.apiClient);
|
|
546
|
+
const rows = based.rows;
|
|
511
547
|
return {
|
|
548
|
+
...(based.columns ? { columns: withMoneyColumns(based.columns) } : {}),
|
|
549
|
+
...(based.note ? { notes: [based.note] } : {}),
|
|
512
550
|
rows,
|
|
513
551
|
// Nothing at all: the company has no Form 4 of its own, which is the whole answer.
|
|
514
552
|
...(empty ? { empty, unavailableSymbols: [] } : {}),
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
type StatItem,
|
|
31
31
|
} from "../../../components";
|
|
32
32
|
import { useDebouncedPluginPaneState, usePluginPaneState } from "../../runtime";
|
|
33
|
-
import { mayBeSecEquityListing, mayBeUsEquityTicker, noSecReportsNotice, nonUsSecListingVenue } from "../../../utils/sec";
|
|
33
|
+
import { homeInsiderReportsNote, mayBeSecEquityListing, mayBeUsEquityTicker, noSecReportsNotice, nonUsSecListingVenue } from "../../../utils/sec";
|
|
34
34
|
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
35
35
|
import { formatCompact } from "../../../utils/format";
|
|
36
36
|
import { nextHeaderSort } from "../../../utils/sort-values";
|
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
} from "./model";
|
|
57
57
|
import { formatInsiderName } from "./display";
|
|
58
58
|
import { insiderHeadless } from "./headless";
|
|
59
|
+
import { FORM4_CURRENCY } from "./base-values";
|
|
59
60
|
import { isInsiderForm } from "./insider-data";
|
|
60
61
|
import { filesAsForeignPrivateIssuer, insiderEmptyNotice, isOtherIssuerFiling } from "./issuer";
|
|
61
62
|
import { relevantInsiderAmendments } from "./amendments";
|
|
@@ -116,9 +117,10 @@ const TYPE_FILTER_LABELS: Record<InsiderTypeFilter, string> = {
|
|
|
116
117
|
* security when there are several. A side with no trades shows 0 shares,
|
|
117
118
|
* muted. Buys read in the gain colour and sales in the loss colour, as in
|
|
118
119
|
* the table; when the Type filter hides open-market trades the figures dim
|
|
119
|
-
* and say so.
|
|
120
|
+
* and say so. With `currency` the dollars read with its code (1.2M USD), as
|
|
121
|
+
* the table's CCY column names them.
|
|
120
122
|
*/
|
|
121
|
-
function summaryItems(figures: InsiderSummaryFigures, typeFilter: InsiderTypeFilter): StatItem[] {
|
|
123
|
+
function summaryItems(figures: InsiderSummaryFigures, typeFilter: InsiderTypeFilter, currency?: string): StatItem[] {
|
|
122
124
|
const securities = new Set(figures.totals.map((total) => total.security));
|
|
123
125
|
const hidden = typeFilter === "other";
|
|
124
126
|
const items: StatItem[] = [];
|
|
@@ -132,7 +134,8 @@ function summaryItems(figures: InsiderSummaryFigures, typeFilter: InsiderTypeFil
|
|
|
132
134
|
label,
|
|
133
135
|
value: total.unreconciled ? "Unavailable" : `${formatCompact(total.shares)} shares`,
|
|
134
136
|
detail: [
|
|
135
|
-
total.unreconciled ? "amended" : total.knownValue ?
|
|
137
|
+
total.unreconciled ? "amended" : !total.knownValue ? "value unavailable"
|
|
138
|
+
: currency ? `${formatCompact(total.value)} ${currency}` : `$${formatCompact(total.value)}`,
|
|
136
139
|
securities.size > 1 ? total.security : null,
|
|
137
140
|
hidden ? "not in this view" : null,
|
|
138
141
|
].filter(Boolean).join(" · "),
|
|
@@ -337,7 +340,10 @@ function InsiderTradesView({ ticker, viewBar, state, width, height, focused, sta
|
|
|
337
340
|
const order = new Map(rows.map((row, index) => [row.id, index]));
|
|
338
341
|
return sortStackItems(rows, sort, compareInsiderRows, (a, b) => order.get(a.id)! - order.get(b.id)!);
|
|
339
342
|
}, [rows, sort]);
|
|
340
|
-
|
|
343
|
+
// Form 4 amounts are dollars: a reader whose base currency is another one gets them named, as PF names a holding's.
|
|
344
|
+
const baseCurrency = useAppSelector((state) => state.config.baseCurrency);
|
|
345
|
+
const amountCurrency = baseCurrency.trim().toUpperCase() === FORM4_CURRENCY ? undefined : FORM4_CURRENCY;
|
|
346
|
+
const columns = useMemo(() => buildInsiderColumns(width, rows, amountCurrency), [amountCurrency, rows, width]);
|
|
341
347
|
|
|
342
348
|
// A narrowed list that cannot scroll cannot ask for older filings, so they
|
|
343
349
|
// load here until it fills a page, back to a year at most.
|
|
@@ -358,7 +364,7 @@ function InsiderTradesView({ ticker, viewBar, state, width, height, focused, sta
|
|
|
358
364
|
const lastSummaryRef = useRef<{ scope: string; figures: InsiderSummaryFigures } | null>(null);
|
|
359
365
|
if (summary) lastSummaryRef.current = { scope: summaryScope, figures: summary };
|
|
360
366
|
const shownSummary = summary ?? (lastSummaryRef.current?.scope === summaryScope ? lastSummaryRef.current.figures : null);
|
|
361
|
-
const statItems = useMemo(() => shownSummary ? summaryItems(shownSummary, typeFilter) : [], [shownSummary, typeFilter]);
|
|
367
|
+
const statItems = useMemo(() => shownSummary ? summaryItems(shownSummary, typeFilter, amountCurrency) : [], [amountCurrency, shownSummary, typeFilter]);
|
|
362
368
|
// The totals miss window filings only when the window passes the cap.
|
|
363
369
|
const windowPartial = shownCount < windowCount;
|
|
364
370
|
const insiderOptions = useMemo(() => {
|
|
@@ -483,9 +489,12 @@ function InsiderTradesView({ ticker, viewBar, state, width, height, focused, sta
|
|
|
483
489
|
if (loading && allFilings.length === 0) return underBar(<Spinner label="Loading insider filings..." />);
|
|
484
490
|
if (error && allFilings.length === 0) return underBar(<EmptyState title="Insider filings unavailable." message={error} />);
|
|
485
491
|
if (noOwnFilings) {
|
|
492
|
+
// A company from abroad also says its home market's insider reports are not covered (RY:TSX: SEDI).
|
|
493
|
+
const foreignPrivateIssuer = filesAsForeignPrivateIssuer(allFilings);
|
|
494
|
+
const home = homeInsiderReportsNote({ exchange: abroadVenue, foreignPrivateIssuer });
|
|
486
495
|
return underBar(renderFilingNotice(abroadVenue && allFilings.length === 0
|
|
487
|
-
? noSecReportsNotice({ symbol: parsePublicTickerKey(ticker.metadata.ticker).symbol, exchange: abroadVenue, name: ticker.metadata.name ?? null })
|
|
488
|
-
: insiderEmptyNotice(
|
|
496
|
+
? [noSecReportsNotice({ symbol: parsePublicTickerKey(ticker.metadata.ticker).symbol, exchange: abroadVenue, name: ticker.metadata.name ?? null }), home].filter(Boolean).join(" ")
|
|
497
|
+
: insiderEmptyNotice(foreignPrivateIssuer, home)));
|
|
489
498
|
}
|
|
490
499
|
|
|
491
500
|
// Until a filing is read there is nothing to say; after that the empty
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type { SecFilingItem } from "../../../types/data-provider";
|
|
1
|
+
import type { DataProvider, SecFilingItem } from "../../../types/data-provider";
|
|
2
|
+
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
3
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
4
|
+
import { homeInsiderReportsNote, venueHomeCountry } from "../../../utils/sec";
|
|
2
5
|
import { parseForm4IssuerCik } from "./insider-data";
|
|
3
6
|
|
|
4
7
|
/**
|
|
@@ -40,10 +43,37 @@ export function filesAsForeignPrivateIssuer(filings: readonly SecFilingItem[]):
|
|
|
40
43
|
|
|
41
44
|
/**
|
|
42
45
|
* What the pane, `fn INS` and `insider` say when a company has no Form 4 of
|
|
43
|
-
* its own
|
|
46
|
+
* its own, and beside it, for a company from abroad, that its insiders'
|
|
47
|
+
* reports at home are not covered either (`homeInsiderReportsNote`). Each
|
|
48
|
+
* already names the company above it.
|
|
44
49
|
*/
|
|
45
|
-
export function insiderEmptyNotice(foreignPrivateIssuer: boolean): string {
|
|
46
|
-
|
|
50
|
+
export function insiderEmptyNotice(foreignPrivateIssuer: boolean, home: string | null = null): string {
|
|
51
|
+
const notice = foreignPrivateIssuer
|
|
47
52
|
? "No Form 4 insiders: foreign private issuers file none."
|
|
48
53
|
: "No Form 4 insider filings.";
|
|
54
|
+
return home ? `${notice} ${home}` : notice;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* `homeInsiderReportsNote` for a listing with no Form 4 of its own, placed by
|
|
59
|
+
* its company's country where the source knows the company (RY in New York
|
|
60
|
+
* is Royal Bank of Canada), else by its venue abroad (RY:TSX). A lookup that
|
|
61
|
+
* fails leaves the venue to say. A US listing of a company filing as a
|
|
62
|
+
* domestic one has no home market to name, and costs no lookup.
|
|
63
|
+
*/
|
|
64
|
+
export async function loadHomeInsiderReportsNote(
|
|
65
|
+
provider: Pick<DataProvider, "getIssuerListings">,
|
|
66
|
+
key: string,
|
|
67
|
+
exchange: string | undefined,
|
|
68
|
+
foreignPrivateIssuer: boolean,
|
|
69
|
+
): Promise<string | null> {
|
|
70
|
+
const parsed = parsePublicTickerKey(key);
|
|
71
|
+
const venue = parsed.exchange ?? exchange ?? "";
|
|
72
|
+
if (!foreignPrivateIssuer && !venueHomeCountry(venue)) return null;
|
|
73
|
+
const { getIssuerListings } = provider;
|
|
74
|
+
const issuer = getIssuerListings
|
|
75
|
+
? await Promise.resolve().then(() => getIssuerListings.call(provider, parsed.symbol, venue))
|
|
76
|
+
.catch(logFailure("plugins.insider.issuer", "Reading the company's listings failed", null))
|
|
77
|
+
: null;
|
|
78
|
+
return homeInsiderReportsNote({ country: issuer?.country, exchange: venue, foreignPrivateIssuer });
|
|
49
79
|
}
|
|
@@ -109,7 +109,7 @@ export async function loadInsiderSignals(
|
|
|
109
109
|
if (isAccessDenied(error)) throw new Error(CLOUD_SESSION_REQUIRED, { cause: error });
|
|
110
110
|
throw unavailableOnServer(error, INSIDER_SIGNALS_NOT_AVAILABLE);
|
|
111
111
|
}),
|
|
112
|
-
{ force, validate: validateInsiderSignals },
|
|
112
|
+
{ force, validate: validateInsiderSignals, signal },
|
|
113
113
|
);
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -187,7 +187,7 @@ export async function loadInsiderCompany(
|
|
|
187
187
|
if (isAccessDenied(error)) throw new Error(CLOUD_SESSION_REQUIRED, { cause: error });
|
|
188
188
|
throw unavailableOnServer(error, INSIDERS_NOT_AVAILABLE);
|
|
189
189
|
}),
|
|
190
|
-
{ force, validate: companyValidator(query.ticker) },
|
|
190
|
+
{ force, validate: companyValidator(query.ticker), signal },
|
|
191
191
|
)));
|
|
192
192
|
return bothSides(buy!, sell!);
|
|
193
193
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DataTableColumn } from "../../../components";
|
|
2
|
-
import { displayWidth, formatCompact, formatCurrency } from "../../../utils/format";
|
|
2
|
+
import { displayWidth, formatCompact, formatCurrency, formatNumber } from "../../../utils/format";
|
|
3
3
|
import { isAmendedInsiderFiling } from "./amendments";
|
|
4
4
|
import { insiderSecurityTag, insiderTypeLabel, insiderTypeTone, isOpenMarketTrade, type InsiderTypeTone } from "./display";
|
|
5
5
|
import {
|
|
@@ -10,12 +10,18 @@ import {
|
|
|
10
10
|
type ParsedInsiderFiling,
|
|
11
11
|
} from "./model";
|
|
12
12
|
|
|
13
|
-
export type InsiderColumnId = "date" | "insider" | "role" | "type" | "security" | "shares" | "price" | "value";
|
|
13
|
+
export type InsiderColumnId = "date" | "insider" | "role" | "type" | "security" | "shares" | "price" | "value" | "ccy";
|
|
14
14
|
|
|
15
15
|
export interface InsiderColumn extends DataTableColumn {
|
|
16
16
|
id: InsiderColumnId;
|
|
17
17
|
/** The type column has room for the 10b5-1 marker. */
|
|
18
18
|
plan?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The currency the CCY column names, and that Price and Value are written
|
|
21
|
+
* in without a sign, as PF writes LAST beside CCY. Unset when the amounts
|
|
22
|
+
* keep their $.
|
|
23
|
+
*/
|
|
24
|
+
currency?: string;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
/** One table row: a Form 4 line, or a filing without one (loading, unread, a disclosure). */
|
|
@@ -102,12 +108,21 @@ export function formatInsiderShares(value: number | null): string {
|
|
|
102
108
|
return value == null ? "—" : formatCompact(value);
|
|
103
109
|
}
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
/** A Form 4 price: $412.50, or 412.50 when a CCY column names its currency. */
|
|
112
|
+
export function formatInsiderPrice(value: number | null, currency?: string): string {
|
|
113
|
+
if (value == null) return "—";
|
|
114
|
+
return currency ? formatNumber(value, 2) : formatCurrency(value);
|
|
107
115
|
}
|
|
108
116
|
|
|
109
|
-
|
|
110
|
-
|
|
117
|
+
/** A Form 4 value: $1.2M, or 1.2M when a CCY column names its currency. */
|
|
118
|
+
export function formatInsiderValue(value: number | null, currency?: string): string {
|
|
119
|
+
if (value == null) return "—";
|
|
120
|
+
return currency ? formatCompact(value) : `$${formatCompact(value)}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** What a row's CCY cell says: the currency of its price and value, or a dash for a filing without either. */
|
|
124
|
+
export function insiderCurrencyText(row: Pick<InsiderTableRow, "price" | "value">, currency: string): string {
|
|
125
|
+
return row.price == null && row.value == null ? "—" : currency;
|
|
111
126
|
}
|
|
112
127
|
|
|
113
128
|
/** The quieter words after the type: "4/A" always, "10b5-1" when the column has room. */
|
|
@@ -136,9 +151,11 @@ function fitWidth(texts: readonly string[], label: string, max: number): number
|
|
|
136
151
|
* When the pane runs short, the role goes first, then the price, the
|
|
137
152
|
* 10b5-1 marker, the security and the share count, and only then does the name
|
|
138
153
|
* column shrink below 16 cells, so a narrow pane still reads date, name,
|
|
139
|
-
* type and value.
|
|
154
|
+
* type and value. With `currency` (Form 4 amounts read by someone whose base
|
|
155
|
+
* currency is not the dollar), CCY follows Value and is kept with it, as PF
|
|
156
|
+
* shows CCY once a row is in another currency than the totals.
|
|
140
157
|
*/
|
|
141
|
-
export function buildInsiderColumns(width: number, rows: readonly InsiderTableRow[]): InsiderColumn[] {
|
|
158
|
+
export function buildInsiderColumns(width: number, rows: readonly InsiderTableRow[], currency?: string): InsiderColumn[] {
|
|
142
159
|
const anyPlan = rows.some((row) => row.plan);
|
|
143
160
|
const anySecurity = rows.some((row) => row.security);
|
|
144
161
|
const names = rows.map((row) => row.name);
|
|
@@ -150,8 +167,9 @@ export function buildInsiderColumns(width: number, rows: readonly InsiderTableRo
|
|
|
150
167
|
typePlan: fitWidth(rows.map((row) => insiderTypeText(row, true)), "Type", 19),
|
|
151
168
|
security: fitWidth(rows.map((row) => row.security ?? ""), "Security", 12),
|
|
152
169
|
shares: fitWidth(rows.map((row) => formatInsiderShares(row.shares)), "Shares", 9),
|
|
153
|
-
price: fitWidth(rows.map((row) => formatInsiderPrice(row.price)), "Price", 10),
|
|
154
|
-
value: fitWidth(rows.map((row) => formatInsiderValue(row.value)), "Value", 9),
|
|
170
|
+
price: fitWidth(rows.map((row) => formatInsiderPrice(row.price, currency)), "Price", 10),
|
|
171
|
+
value: fitWidth(rows.map((row) => formatInsiderValue(row.value, currency)), "Value", 9),
|
|
172
|
+
ccy: currency ? fitWidth(rows.map((row) => insiderCurrencyText(row, currency)), "CCY", 4) : 0,
|
|
155
173
|
};
|
|
156
174
|
const show = { role: true, price: true, plan: anyPlan, security: anySecurity, shares: true };
|
|
157
175
|
let insider = widths.insider;
|
|
@@ -165,6 +183,7 @@ export function buildInsiderColumns(width: number, rows: readonly InsiderTableRo
|
|
|
165
183
|
show.shares ? widths.shares : 0,
|
|
166
184
|
show.price ? widths.price : 0,
|
|
167
185
|
widths.value,
|
|
186
|
+
widths.ccy,
|
|
168
187
|
].reduce((sum, columnWidth) => sum + (columnWidth > 0 ? columnWidth + COLUMN_GAP : 0), 0);
|
|
169
188
|
const shrinkInsider = (floor: number) => {
|
|
170
189
|
insider = Math.max(Math.min(insider, floor), insider - Math.max(0, total() - available));
|
|
@@ -187,10 +206,11 @@ export function buildInsiderColumns(width: number, rows: readonly InsiderTableRo
|
|
|
187
206
|
{ id: "type", label: "Type", width: typeWidth, align: "left", plan: show.plan },
|
|
188
207
|
...(show.security ? [{ id: "security" as const, label: "Security", width: widths.security, align: "left" as const }] : []),
|
|
189
208
|
...(show.shares ? [{ id: "shares" as const, label: "Shares", width: widths.shares, align: "right" as const }] : []),
|
|
190
|
-
...(show.price ? [{ id: "price" as const, label: "Price", width: widths.price, align: "right" as const }] : []),
|
|
191
|
-
{ id: "value", label: "Value", width: widths.value, align: "right" },
|
|
209
|
+
...(show.price ? [{ id: "price" as const, label: "Price", width: widths.price, align: "right" as const, currency }] : []),
|
|
210
|
+
{ id: "value", label: "Value", width: widths.value, align: "right", currency },
|
|
211
|
+
...(currency ? [{ id: "ccy" as const, label: "CCY", width: widths.ccy, align: "left" as const, currency }] : []),
|
|
192
212
|
];
|
|
193
|
-
// Spare cells spread over the columns, so the
|
|
213
|
+
// Spare cells spread over the columns, so the last column ends at the
|
|
194
214
|
// pane's edge in the terminal as it does on the desktop.
|
|
195
215
|
const used = columns.reduce((sum, column) => sum + column.width + COLUMN_GAP, 0);
|
|
196
216
|
const spare = Math.max(0, available - used);
|
|
@@ -222,5 +242,7 @@ export function compareInsiderRows(a: InsiderTableRow, b: InsiderTableRow, colum
|
|
|
222
242
|
case "shares": return compareNullable(a.shares, b.shares);
|
|
223
243
|
case "price": return compareNullable(a.price, b.price);
|
|
224
244
|
case "value": return compareNullable(a.value, b.value);
|
|
245
|
+
// Every amount is in one currency: rows with one before filings without, as the CCY cells read.
|
|
246
|
+
case "ccy": return Number(a.price == null && a.value == null) - Number(b.price == null && b.value == null);
|
|
225
247
|
}
|
|
226
248
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
formatInsiderPrice,
|
|
10
10
|
formatInsiderShares,
|
|
11
11
|
formatInsiderValue,
|
|
12
|
+
insiderCurrencyText,
|
|
12
13
|
insiderTypeMarkers,
|
|
13
14
|
insiderTypeText,
|
|
14
15
|
type InsiderColumn,
|
|
@@ -72,8 +73,10 @@ export function renderInsiderCell(
|
|
|
72
73
|
case "shares":
|
|
73
74
|
return { text: formatInsiderShares(row.shares), value: row.shares, color: figureColor };
|
|
74
75
|
case "price":
|
|
75
|
-
return { text: formatInsiderPrice(row.price), value: row.price, color: figureColor };
|
|
76
|
+
return { text: formatInsiderPrice(row.price, column.currency), value: row.price, color: figureColor };
|
|
76
77
|
case "value":
|
|
77
|
-
return { text: formatInsiderValue(row.value), value: row.value, color: figureColor };
|
|
78
|
+
return { text: formatInsiderValue(row.value, column.currency), value: row.value, color: figureColor };
|
|
79
|
+
case "ccy":
|
|
80
|
+
return { text: insiderCurrencyText(row, column.currency ?? ""), color: colors.textMuted };
|
|
78
81
|
}
|
|
79
82
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { getPublishedUsEquityCalendarDay, getPublishedUsEquitySession } from "../../../market-data/published-us-sessions";
|
|
2
|
+
import { zonedDateKey, zonedWallClockToUtcMs } from "../../../utils/zoned-date-time";
|
|
3
|
+
import type { MacroEventKind, MacroRelease } from "../macro-day/releases";
|
|
4
|
+
import type { DatedValue, IvHistoryModel, IvLookback } from "./model";
|
|
5
|
+
|
|
6
|
+
/** One release and the readings a set number of sessions from it. */
|
|
7
|
+
interface ReleaseOffsetRow {
|
|
8
|
+
kind: MacroEventKind;
|
|
9
|
+
/** The release's New York calendar day. */
|
|
10
|
+
releaseDate: string;
|
|
11
|
+
/** Sessions from the release: 0 is the first session on or after its day, -6 the sixth session before that. */
|
|
12
|
+
offset: number;
|
|
13
|
+
/** The session the readings are of. */
|
|
14
|
+
date: string;
|
|
15
|
+
iv30: number | null;
|
|
16
|
+
iv90: number | null;
|
|
17
|
+
iv365: number | null;
|
|
18
|
+
hv: number | null;
|
|
19
|
+
spread: number | null;
|
|
20
|
+
/** 25-delta put IV minus call IV, and the risk reversal, call minus put. */
|
|
21
|
+
skew25d: number | null;
|
|
22
|
+
rr25d: number | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ReleaseOffsets {
|
|
26
|
+
rows: ReleaseOffsetRow[];
|
|
27
|
+
/** Releases whose session at the offset has not closed or has no closing reading yet, oldest first. */
|
|
28
|
+
pending: string[];
|
|
29
|
+
/** Releases inside the stored history whose session at the offset is before the lookback. */
|
|
30
|
+
beforeLookback: number;
|
|
31
|
+
/** Releases before the IV history starts. */
|
|
32
|
+
beforeHistory: number;
|
|
33
|
+
/** The first session of the IV history read, when there is one. */
|
|
34
|
+
historyStart: string | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const NEW_YORK = "America/New_York";
|
|
38
|
+
const day = (date: Date) => date.toISOString().slice(0, 10);
|
|
39
|
+
const byDay = (points: readonly DatedValue[]) => new Map(points.map((point) => [day(point.date), point.value]));
|
|
40
|
+
const nextDay = (date: string) => new Date(Date.parse(`${date}T00:00:00Z`) + 86_400_000).toISOString().slice(0, 10);
|
|
41
|
+
|
|
42
|
+
/** Whether `date`'s regular session has closed at `now`: an earlier New York day, or today from its close on. */
|
|
43
|
+
function sessionClosed(date: string, now: number): boolean {
|
|
44
|
+
const today = zonedDateKey(now, NEW_YORK);
|
|
45
|
+
if (date !== today) return date < today;
|
|
46
|
+
const session = getPublishedUsEquitySession("NYSE", date);
|
|
47
|
+
const [year, month, dayOfMonth] = date.split("-").map(Number) as [number, number, number];
|
|
48
|
+
return now >= (session?.kind === "session" ? session.close : zonedWallClockToUtcMs(NEW_YORK, year, month, dayOfMonth, 16, 0, 0));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The US equity sessions after `after` through the first on or after `until`; null where the published calendar ends. */
|
|
52
|
+
function calendarSessions(after: string, until: string): string[] | null {
|
|
53
|
+
const sessions: string[] = [];
|
|
54
|
+
for (let date = nextDay(after); ; date = nextDay(date)) {
|
|
55
|
+
const kind = getPublishedUsEquityCalendarDay("NYSE", date);
|
|
56
|
+
if (kind == null) return null;
|
|
57
|
+
if (kind !== "session") continue;
|
|
58
|
+
sessions.push(date);
|
|
59
|
+
if (date >= until) return sessions;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* One row per release of `kind`, with the readings on the session `offset`
|
|
65
|
+
* sessions from it (the "week before FOMC" is `-5` or `-6`), oldest first.
|
|
66
|
+
* Offset 0 is the first session on or after the release day, so a release on
|
|
67
|
+
* a day with no session counts from the next one. Sessions are the history's
|
|
68
|
+
* own, from the first IV reading in the lookback through the last closed
|
|
69
|
+
* session read (`readThrough`, or the last IV reading), and past that the
|
|
70
|
+
* published exchange calendar: an upcoming release whose session at a
|
|
71
|
+
* negative offset has closed and been read gets its row. A release whose
|
|
72
|
+
* session at the offset has not closed or has no reading yet is pending; one
|
|
73
|
+
* whose session falls before the history read is counted, before the lookback
|
|
74
|
+
* or before the history.
|
|
75
|
+
*/
|
|
76
|
+
export function releaseOffsetRows(
|
|
77
|
+
model: Pick<IvHistoryModel, "iv30" | "iv90" | "iv365" | "hv" | "spread" | "skew" | "since">,
|
|
78
|
+
releases: readonly MacroRelease[],
|
|
79
|
+
kind: MacroEventKind,
|
|
80
|
+
offset: number,
|
|
81
|
+
{ lookback = "ALL", readThrough = null, now = Date.now() }: { lookback?: IvLookback; readThrough?: string | null; now?: number } = {},
|
|
82
|
+
): ReleaseOffsets {
|
|
83
|
+
const result: ReleaseOffsets = { rows: [], pending: [], beforeLookback: 0, beforeHistory: 0, historyStart: null };
|
|
84
|
+
// Sessions from the first IV reading on: realized volatility alone, before it or without it, is no IV row.
|
|
85
|
+
const first = model.iv30[0] ? day(model.iv30[0].date) : null;
|
|
86
|
+
if (!first) return result;
|
|
87
|
+
const lastReading = day(model.iv30.at(-1)!.date);
|
|
88
|
+
const through = readThrough && readThrough > lastReading ? readThrough : lastReading;
|
|
89
|
+
// A session counts once it has closed and been read: today's bar before the close, or a session the IV history has not reached, is no reading.
|
|
90
|
+
const known = [...new Set([...model.iv30, ...model.hv].map((point) => day(point.date)))]
|
|
91
|
+
.filter((session) => session >= first && session <= through && sessionClosed(session, now)).sort();
|
|
92
|
+
if (known.length === 0) return result;
|
|
93
|
+
result.historyStart = known[0]!;
|
|
94
|
+
// The lookback cut a longer stored history: a release before it is outside the lookback, not before the history.
|
|
95
|
+
const trimmed = lookback !== "ALL" && model.since != null && model.since < first;
|
|
96
|
+
const before = (release: MacroRelease) => {
|
|
97
|
+
if (trimmed && release.date >= model.since!) result.beforeLookback += 1;
|
|
98
|
+
else result.beforeHistory += 1;
|
|
99
|
+
};
|
|
100
|
+
const iv30 = byDay(model.iv30), iv90 = byDay(model.iv90), iv365 = byDay(model.iv365);
|
|
101
|
+
const hv = byDay(model.hv), spread = byDay(model.spread), skew = byDay(model.skew);
|
|
102
|
+
for (const release of releases) {
|
|
103
|
+
if (release.kind !== kind) continue;
|
|
104
|
+
if (release.date < known[0]!) {
|
|
105
|
+
before(release);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const anchor = known.findIndex((session) => session >= release.date);
|
|
109
|
+
let index: number;
|
|
110
|
+
if (anchor >= 0) index = anchor + offset;
|
|
111
|
+
else {
|
|
112
|
+
// After the last session read: count on through the exchange calendar to the release's own session.
|
|
113
|
+
const ahead = calendarSessions(known.at(-1)!, release.date);
|
|
114
|
+
if (!ahead) {
|
|
115
|
+
result.pending.push(release.date);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
index = known.length + ahead.length - 1 + offset;
|
|
119
|
+
}
|
|
120
|
+
if (index < 0) {
|
|
121
|
+
before(release);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (index >= known.length) {
|
|
125
|
+
result.pending.push(release.date);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const date = known[index]!;
|
|
129
|
+
const skewValue = skew.get(date) ?? null;
|
|
130
|
+
result.rows.push({
|
|
131
|
+
kind, releaseDate: release.date, offset, date,
|
|
132
|
+
iv30: iv30.get(date) ?? null, iv90: iv90.get(date) ?? null, iv365: iv365.get(date) ?? null,
|
|
133
|
+
hv: hv.get(date) ?? null, spread: spread.get(date) ?? null,
|
|
134
|
+
skew25d: skewValue, rr25d: skewValue == null ? null : -skewValue,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** "6 sessions before", "release day", "1 session after". */
|
|
141
|
+
export function offsetText(offset: number): string {
|
|
142
|
+
if (offset === 0) return "release day";
|
|
143
|
+
const sessions = `${Math.abs(offset)} session${Math.abs(offset) === 1 ? "" : "s"}`;
|
|
144
|
+
return offset < 0 ? `${sessions} before` : `${sessions} after`;
|
|
145
|
+
}
|
|
@@ -12,6 +12,9 @@ export const VCA_DEFINITIONS = [
|
|
|
12
12
|
"VIX is Cboe's 30-day S&P 500 implied vol from the whole SPX strike strip, skew included; IV30 here is at the money, so VIX usually reads higher.",
|
|
13
13
|
] as const;
|
|
14
14
|
|
|
15
|
+
/** Words for implied volatility that are no function's code: `fn IVOL` names HIVG and OVDV, which list them as `suggestFor`. */
|
|
16
|
+
export const IMPLIED_VOL_WORDS = ["IVOL", "IV", "OVOL", "IMPVOL", "IMPLIEDVOL"];
|
|
17
|
+
|
|
15
18
|
/**
|
|
16
19
|
* A skew column's header names its sign, since the conventions differ: the
|
|
17
20
|
* 25-delta skew is put minus call (HIVG, VCA), the risk reversal call minus
|