gloomberb 0.15.7 → 0.16.0
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/README.md +11 -2
- package/package.json +1 -1
- package/src/api-client/app-attention.ts +147 -0
- package/src/api-client/attention.ts +26 -0
- package/src/api-client/awards.ts +215 -0
- package/src/api-client/catalysts.ts +160 -0
- package/src/api-client/chat.ts +137 -14
- package/src/api-client/company-kpis.ts +153 -0
- package/src/api-client/cpi.ts +52 -0
- package/src/api-client/credit-documents.ts +107 -0
- package/src/api-client/data.ts +192 -3
- package/src/api-client/distress.ts +164 -0
- package/src/api-client/doe.ts +75 -0
- package/src/api-client/equity-screener.ts +11 -0
- package/src/api-client/exposure.ts +202 -0
- package/src/api-client/gpu.ts +93 -0
- package/src/api-client/hiring.ts +110 -0
- package/src/api-client/index.ts +78 -3
- package/src/api-client/normalizers.ts +31 -1
- package/src/api-client/paths.ts +3 -0
- package/src/api-client/perps.ts +127 -0
- package/src/api-client/power.ts +148 -0
- package/src/api-client/request.ts +58 -1
- package/src/api-client/research-activity.ts +172 -13
- package/src/api-client/socket.ts +75 -27
- package/src/api-client/supply-chain-graph.ts +40 -0
- package/src/api-client/supply-chain.ts +98 -0
- package/src/api-client/telemetry.ts +29 -0
- package/src/api-client/themes.ts +28 -0
- package/src/api-client/types.ts +33 -1
- package/src/api-client/wall-experiments.ts +100 -0
- package/src/api-client/wall-summary.ts +49 -0
- package/src/api-client/web-experiments.ts +136 -0
- package/src/app/pane-runtime/plugin-bindings.ts +5 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +8 -2
- package/src/app/runtime/update.ts +13 -1
- package/src/cli/commands/system.ts +2 -0
- package/src/cli/desktop-pane-shot.ts +3 -4
- package/src/cli/pane-functions/data.ts +78 -0
- package/src/cli/pane-functions/financial-ratio-shot.ts +160 -0
- package/src/cli/pane-functions/function-help.ts +220 -8
- package/src/cli/pane-functions/headless.ts +4 -0
- package/src/cli/pane-functions/screenshot-evidence.ts +21 -0
- package/src/cli/pane-functions/screenshot.ts +49 -5
- package/src/components/chart/composite/composite-chart.tsx +15 -2
- package/src/components/chart/composite/format.ts +24 -11
- package/src/components/chart/composite/rasterizer.ts +67 -16
- package/src/components/chart/composite/scene.ts +53 -2
- package/src/components/chart/composite/session-shading.ts +3 -2
- package/src/components/chart/composite/text-renderer.ts +52 -1
- package/src/components/chart/composite/time-axis.ts +100 -30
- package/src/components/chart/composite/tools.ts +2 -1
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/chart-table/index.ts +1 -0
- package/src/components/chart-table/layout.ts +15 -0
- package/src/components/command-bar/action-runtime.ts +1 -0
- package/src/components/command-bar/commands/direct/index.ts +32 -0
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/pane-templates/items.ts +11 -0
- package/src/components/command-bar/pane-templates/workflow.ts +19 -4
- package/src/components/command-bar/routes/root/command-items.ts +6 -0
- package/src/components/command-bar/routes/root/provider-search.ts +2 -0
- package/src/components/command-bar/routes/root/results.ts +64 -0
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/ticker-search/results.ts +89 -24
- package/src/components/command-bar/routes/ticker-search/route.ts +2 -0
- package/src/components/command-bar/surface/index.tsx +15 -0
- package/src/components/command-bar/view-model.ts +3 -0
- package/src/components/command-bar/workflow/collection-actions.ts +9 -7
- package/src/components/command-bar/workflow/ops.ts +19 -0
- package/src/components/command-bar/workflow/tickers.ts +5 -0
- package/src/components/company-logo.tsx +15 -2
- package/src/components/data-table/table-pane.ts +2 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/detached-pane-shell.tsx +12 -1
- package/src/components/layout/missing-pane.tsx +87 -23
- package/src/components/layout/pane/footer/index.tsx +64 -18
- package/src/components/layout/pane/footer/model.ts +85 -0
- package/src/components/layout/pane/sidebar.tsx +19 -1
- package/src/components/layout/shell/index.tsx +38 -28
- package/src/components/layout/shell/pane/management-shortcuts.ts +13 -1
- package/src/components/layout/status-bar.tsx +93 -28
- package/src/components/layout/status-widget-space.ts +72 -0
- package/src/components/onboarding/first-run-workspace.ts +8 -2
- package/src/components/onboarding/onboarding-wizard.tsx +35 -16
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -5
- package/src/components/onboarding/wizard-model.ts +13 -5
- package/src/components/onboarding/wizard-positions.ts +56 -7
- package/src/components/price-performance.tsx +31 -34
- package/src/components/ui/data-table/index.tsx +11 -8
- package/src/components/ui/display.tsx +15 -6
- package/src/components/ui/fields.tsx +5 -0
- package/src/components/ui/figure.tsx +94 -0
- package/src/components/ui/header-case.ts +25 -3
- package/src/components/ui/icon.tsx +3 -1
- package/src/components/ui/message-composer.tsx +8 -1
- package/src/components/ui/query-bar.tsx +8 -1
- package/src/components/ui/ratio-bar.tsx +84 -0
- package/src/components/ui/toggle.tsx +3 -1
- package/src/components/use-scrollbox-layout.ts +17 -0
- package/src/core/state/app/layout.ts +35 -1
- package/src/core/state/app/state.ts +15 -0
- package/src/core/state/app/types.ts +1 -0
- package/src/data/config/store/normalize.ts +24 -0
- package/src/i18n/es.ts +2 -0
- package/src/i18n/ja.ts +2 -0
- package/src/i18n/ko.ts +2 -0
- package/src/i18n/zh-cn.ts +2 -0
- package/src/i18n/zh-tw.ts +2 -0
- package/src/layout-marketplace/pane-imagery.ts +1 -0
- package/src/market-data/coordinator/entries.ts +13 -0
- package/src/market-data/coordinator/financials.ts +33 -8
- package/src/market-data/market/freshness.ts +16 -5
- package/src/market-data/market/trading-sessions.ts +65 -1
- package/src/market-data/published-cn-sessions.ts +25 -0
- package/src/market-data/published-nse-sessions.ts +20 -0
- package/src/news/aggregator.ts +4 -0
- package/src/plugins/builtin/account-management/model.ts +6 -1
- package/src/plugins/builtin/account-management/pane.tsx +36 -25
- package/src/plugins/builtin/alerts/catalysts.ts +77 -0
- package/src/plugins/builtin/alerts/events-pane.tsx +3 -0
- package/src/plugins/builtin/alerts/events.ts +10 -4
- package/src/plugins/builtin/alerts/history-pane.tsx +1 -1
- package/src/plugins/builtin/alerts/history.ts +1 -0
- package/src/plugins/builtin/alerts/index.tsx +6 -0
- package/src/plugins/builtin/alerts/research-builder.ts +10 -3
- package/src/plugins/builtin/alerts/research-rules.ts +15 -1
- package/src/plugins/builtin/analytics/risk-client.ts +15 -3
- package/src/plugins/builtin/analytics/risk-headless.ts +123 -18
- package/src/plugins/builtin/attention/client.ts +80 -0
- package/src/plugins/builtin/attention/evidence.ts +30 -0
- package/src/plugins/builtin/attention/headless.ts +33 -0
- package/src/plugins/builtin/attention/index.tsx +18 -0
- package/src/plugins/builtin/attention/model.ts +59 -0
- package/src/plugins/builtin/attention/pane.tsx +211 -0
- package/src/plugins/builtin/attention/trending.tsx +59 -0
- package/src/plugins/builtin/awards/alerts.ts +32 -0
- package/src/plugins/builtin/awards/client.ts +82 -0
- package/src/plugins/builtin/awards/detail.tsx +107 -0
- package/src/plugins/builtin/awards/evidence.ts +36 -0
- package/src/plugins/builtin/awards/headless.ts +55 -0
- package/src/plugins/builtin/awards/index.tsx +20 -0
- package/src/plugins/builtin/awards/model.ts +42 -0
- package/src/plugins/builtin/awards/pane.tsx +263 -0
- package/src/plugins/builtin/catalysts/client.ts +28 -0
- package/src/plugins/builtin/catalysts/detail.tsx +88 -0
- package/src/plugins/builtin/catalysts/evidence.ts +47 -0
- package/src/plugins/builtin/catalysts/headless.ts +42 -0
- package/src/plugins/builtin/catalysts/history.ts +26 -0
- package/src/plugins/builtin/catalysts/index.tsx +29 -0
- package/src/plugins/builtin/catalysts/model.ts +135 -0
- package/src/plugins/builtin/catalysts/pane.tsx +159 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +6 -0
- package/src/plugins/builtin/chart-composer/index.tsx +8 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +28 -1
- package/src/plugins/builtin/chat/attachments/desktop.tsx +610 -0
- package/src/plugins/builtin/chat/attachments/files.ts +45 -0
- package/src/plugins/builtin/chat/attachments/model.ts +164 -0
- package/src/plugins/builtin/chat/attachments/terminal.tsx +72 -0
- package/src/plugins/builtin/chat/attachments/transfer.ts +165 -0
- package/src/plugins/builtin/chat/content/channel-navigation.ts +13 -12
- package/src/plugins/builtin/chat/content/composer-runtime.ts +25 -4
- package/src/plugins/builtin/chat/content/composer.tsx +46 -4
- package/src/plugins/builtin/chat/content/footer.tsx +46 -8
- package/src/plugins/builtin/chat/content/index.tsx +263 -62
- package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -2
- package/src/plugins/builtin/chat/content/open-targets.ts +17 -3
- package/src/plugins/builtin/chat/content/scroll.ts +7 -1
- package/src/plugins/builtin/chat/content/shortcuts.ts +36 -11
- package/src/plugins/builtin/chat/content/snapshot.ts +3 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +17 -1
- package/src/plugins/builtin/chat/content/types.ts +7 -0
- package/src/plugins/builtin/chat/controller/attachments.ts +213 -0
- package/src/plugins/builtin/chat/controller/connections.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +114 -2
- package/src/plugins/builtin/chat/controller/lifecycle.ts +9 -0
- package/src/plugins/builtin/chat/controller/messages.ts +35 -2
- package/src/plugins/builtin/chat/controller/send.ts +128 -25
- package/src/plugins/builtin/chat/controller/state.ts +12 -1
- package/src/plugins/builtin/chat/controller/utils.ts +7 -5
- package/src/plugins/builtin/chat/controller/view.ts +2 -0
- package/src/plugins/builtin/chat/index.tsx +21 -0
- package/src/plugins/builtin/chat/layout.ts +9 -4
- package/src/plugins/builtin/chat/message/desktop.tsx +55 -20
- package/src/plugins/builtin/chat/message/header.tsx +30 -5
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +9 -0
- package/src/plugins/builtin/chat/message/pending-send.ts +35 -0
- package/src/plugins/builtin/chat/message/profile-popover.tsx +5 -1
- package/src/plugins/builtin/chat/message/render-state.ts +12 -2
- package/src/plugins/builtin/chat/message/terminal.tsx +51 -4
- package/src/plugins/builtin/chat/message/types.ts +6 -1
- package/src/plugins/builtin/chat/pane-state.ts +59 -1
- package/src/plugins/builtin/chat/profile-popover.ts +50 -1
- package/src/plugins/builtin/chat/sidebar.tsx +8 -3
- package/src/plugins/builtin/chat/status-widget.tsx +29 -12
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +143 -0
- package/src/plugins/builtin/chat/unread-inbox.ts +91 -0
- package/src/plugins/builtin/clinical-trials/client.ts +237 -0
- package/src/plugins/builtin/clinical-trials/index.tsx +74 -0
- package/src/plugins/builtin/clinical-trials/pane.tsx +225 -0
- package/src/plugins/builtin/clinical-trials/types.ts +2 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +37 -6
- package/src/plugins/builtin/cloud/askg/executor.ts +59 -44
- package/src/plugins/builtin/cloud/askg/host.ts +8 -1
- package/src/plugins/builtin/cloud/askg/manifest.ts +29 -2
- package/src/plugins/builtin/cloud/askg/model.ts +47 -13
- package/src/plugins/builtin/cloud/askg/notes.ts +100 -0
- package/src/plugins/builtin/cloud/askg/pane.tsx +50 -14
- package/src/plugins/builtin/cloud/askg/protocol.ts +39 -1
- package/src/plugins/builtin/cloud/askg/result-budget.ts +156 -0
- package/src/plugins/builtin/cloud/askg/user-data.ts +89 -0
- package/src/plugins/builtin/cloud/auth-actions.tsx +55 -31
- package/src/plugins/builtin/cloud/plugin.tsx +4 -1
- package/src/plugins/builtin/cloud/team/pane-sections.tsx +10 -1
- package/src/plugins/builtin/cloud/team/pane.tsx +3 -2
- package/src/plugins/builtin/cloud/thesis/board-pane.tsx +2 -2
- package/src/plugins/builtin/cloud/thesis/detail.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/ticker-tab.tsx +2 -2
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +202 -0
- package/src/plugins/builtin/cloud/upgrade-command.ts +1 -1
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +29 -3
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +1 -1
- package/src/plugins/builtin/cloud-tweets/table.tsx +1 -1
- package/src/plugins/builtin/comment-letters/client.ts +342 -0
- package/src/plugins/builtin/comment-letters/index.tsx +74 -0
- package/src/plugins/builtin/comment-letters/pane.tsx +295 -0
- package/src/plugins/builtin/comment-letters/types.ts +43 -0
- package/src/plugins/builtin/company-attention/app-rank.tsx +79 -0
- package/src/plugins/builtin/company-attention/client.ts +69 -0
- package/src/plugins/builtin/company-attention/evidence.ts +45 -0
- package/src/plugins/builtin/company-attention/headless.ts +47 -0
- package/src/plugins/builtin/company-attention/index.tsx +30 -0
- package/src/plugins/builtin/company-attention/model.ts +129 -0
- package/src/plugins/builtin/company-attention/pane.tsx +183 -0
- package/src/plugins/builtin/company-kpis/client.ts +92 -0
- package/src/plugins/builtin/company-kpis/headless.ts +32 -0
- package/src/plugins/builtin/company-kpis/index.tsx +26 -0
- package/src/plugins/builtin/company-kpis/model.ts +124 -0
- package/src/plugins/builtin/company-kpis/pane.tsx +226 -0
- package/src/plugins/builtin/company-kpis/related.ts +11 -0
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +44 -0
- package/src/plugins/builtin/composite-plugins.ts +34 -0
- package/src/plugins/builtin/correlation/history.ts +4 -1
- package/src/plugins/builtin/correlation/index.tsx +139 -80
- package/src/plugins/builtin/correlation/matrix/pair-chart.tsx +48 -0
- package/src/plugins/builtin/correlation/matrix/selection.ts +77 -0
- package/src/plugins/builtin/correlation/relationship/model.ts +2 -2
- package/src/plugins/builtin/cpi/client.ts +66 -0
- package/src/plugins/builtin/cpi/headless.ts +78 -0
- package/src/plugins/builtin/cpi/index.tsx +31 -0
- package/src/plugins/builtin/cpi/model.ts +198 -0
- package/src/plugins/builtin/cpi/pane.tsx +157 -0
- package/src/plugins/builtin/credit-documents/client.ts +99 -0
- package/src/plugins/builtin/credit-documents/detail.tsx +109 -0
- package/src/plugins/builtin/credit-documents/evidence.ts +64 -0
- package/src/plugins/builtin/credit-documents/gauge.tsx +55 -0
- package/src/plugins/builtin/credit-documents/headless.ts +40 -0
- package/src/plugins/builtin/credit-documents/index.tsx +19 -0
- package/src/plugins/builtin/credit-documents/model.ts +336 -0
- package/src/plugins/builtin/credit-documents/pane.tsx +185 -0
- package/src/plugins/builtin/custom-view/view-spec.ts +14 -2
- package/src/plugins/builtin/debt-maturities/pane.tsx +1 -0
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/doe/client.ts +73 -0
- package/src/plugins/builtin/doe/headless.ts +93 -0
- package/src/plugins/builtin/doe/index.tsx +41 -0
- package/src/plugins/builtin/doe/model.ts +215 -0
- package/src/plugins/builtin/doe/pane.tsx +148 -0
- package/src/plugins/builtin/doe/range-bar.tsx +45 -0
- package/src/plugins/builtin/earnings/board.tsx +3 -0
- package/src/plugins/builtin/earnings/index.tsx +2 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +7 -1
- package/src/plugins/builtin/earnings-calls/transcript-view.tsx +7 -7
- package/src/plugins/builtin/earnings-ripple/client.ts +60 -0
- package/src/plugins/builtin/earnings-ripple/headless.ts +35 -0
- package/src/plugins/builtin/earnings-ripple/index.tsx +35 -0
- package/src/plugins/builtin/earnings-ripple/model.ts +98 -0
- package/src/plugins/builtin/earnings-ripple/pane.tsx +103 -0
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/criterion-editor.tsx +1 -1
- package/src/plugins/builtin/equity-screener/model.ts +74 -9
- package/src/plugins/builtin/equity-screener/pane.tsx +2 -0
- package/src/plugins/builtin/estimate-revisions/pane.tsx +3 -0
- package/src/plugins/builtin/executives/pane.tsx +7 -5
- package/src/plugins/builtin/exposure/charts.tsx +66 -0
- package/src/plugins/builtin/exposure/client.ts +53 -0
- package/src/plugins/builtin/exposure/drivers.ts +29 -0
- package/src/plugins/builtin/exposure/editor.tsx +61 -0
- package/src/plugins/builtin/exposure/evidence.ts +39 -0
- package/src/plugins/builtin/exposure/headless.ts +51 -0
- package/src/plugins/builtin/exposure/holdings.ts +56 -0
- package/src/plugins/builtin/exposure/index.tsx +13 -0
- package/src/plugins/builtin/exposure/model.ts +104 -0
- package/src/plugins/builtin/exposure/pane.tsx +247 -0
- package/src/plugins/builtin/fear-greed/charts.tsx +1 -1
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -9
- package/src/plugins/builtin/filing-events/pane.tsx +2 -1
- package/src/plugins/builtin/gpu/board.tsx +163 -0
- package/src/plugins/builtin/gpu/changes.tsx +94 -0
- package/src/plugins/builtin/gpu/client.ts +130 -0
- package/src/plugins/builtin/gpu/equities.tsx +112 -0
- package/src/plugins/builtin/gpu/headless.ts +67 -0
- package/src/plugins/builtin/gpu/history.tsx +140 -0
- package/src/plugins/builtin/gpu/index.tsx +18 -0
- package/src/plugins/builtin/gpu/ladder.tsx +127 -0
- package/src/plugins/builtin/gpu/model.ts +317 -0
- package/src/plugins/builtin/gpu/pane.tsx +63 -0
- package/src/plugins/builtin/help/index.tsx +19 -2
- package/src/plugins/builtin/holders/pane.tsx +5 -9
- package/src/plugins/builtin/insider/detail.tsx +119 -0
- package/src/plugins/builtin/insider/display.ts +221 -0
- package/src/plugins/builtin/insider/headless.ts +24 -3
- package/src/plugins/builtin/insider/index.tsx +206 -108
- package/src/plugins/builtin/insider/insider-data.ts +31 -3
- package/src/plugins/builtin/insider/model.ts +53 -4
- package/src/plugins/builtin/insider/table-model.ts +226 -0
- package/src/plugins/builtin/insider/table.tsx +79 -0
- package/src/plugins/builtin/iv-history/screen-pane.tsx +1 -1
- package/src/plugins/builtin/jobs/pane.tsx +14 -7
- package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
- package/src/plugins/builtin/macro-day/client.ts +50 -0
- package/src/plugins/builtin/macro-day/headless.ts +51 -0
- package/src/plugins/builtin/macro-day/index.tsx +21 -0
- package/src/plugins/builtin/macro-day/model.ts +137 -0
- package/src/plugins/builtin/macro-day/pane.tsx +118 -0
- package/src/plugins/builtin/macro-day/releases.ts +58 -0
- package/src/plugins/builtin/market-halts/model.ts +30 -17
- package/src/plugins/builtin/market-halts/pane.tsx +15 -21
- package/src/plugins/builtin/market-heatmap/data.ts +1 -1
- package/src/plugins/builtin/market-heatmap/index.tsx +198 -50
- package/src/plugins/builtin/market-heatmap/portfolio.ts +220 -0
- package/src/plugins/builtin/market-movers/index.tsx +1 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +5 -2
- package/src/plugins/builtin/mna/distress/client.ts +326 -0
- package/src/plugins/builtin/mna/distress/detail.tsx +247 -0
- package/src/plugins/builtin/mna/distress/headless.ts +257 -0
- package/src/plugins/builtin/mna/distress/model.ts +427 -0
- package/src/plugins/builtin/mna/distress/pane.tsx +606 -0
- package/src/plugins/builtin/mna/index.tsx +15 -0
- package/src/plugins/builtin/mna/pane.tsx +42 -1
- package/src/plugins/builtin/news/index.tsx +62 -16
- package/src/plugins/builtin/news/wire/filter-articles.ts +54 -0
- package/src/plugins/builtin/news/wire/index.tsx +37 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +29 -13
- package/src/plugins/builtin/news/wire/mutes.ts +253 -0
- package/src/plugins/builtin/news/wire/news/command-bar-search.ts +117 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +4 -0
- package/src/plugins/builtin/news/wire/news/list-search.ts +34 -0
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +158 -0
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +64 -19
- package/src/plugins/builtin/openfda/client.ts +300 -0
- package/src/plugins/builtin/openfda/index.tsx +71 -0
- package/src/plugins/builtin/openfda/pane.tsx +382 -0
- package/src/plugins/builtin/openfda/types.ts +76 -0
- package/src/plugins/builtin/options/footer.ts +1 -0
- package/src/plugins/builtin/options/view.tsx +1 -1
- package/src/plugins/builtin/options-calculator/pane.tsx +0 -1
- package/src/plugins/builtin/options-positioning/index.tsx +4 -2
- package/src/plugins/builtin/options-positioning/pane.tsx +1 -1
- package/src/plugins/builtin/pe-band/client.ts +61 -0
- package/src/plugins/builtin/pe-band/headless.ts +51 -0
- package/src/plugins/builtin/pe-band/index.tsx +20 -0
- package/src/plugins/builtin/pe-band/model.ts +267 -0
- package/src/plugins/builtin/pe-band/pane.tsx +159 -0
- package/src/plugins/builtin/perps/client.ts +66 -0
- package/src/plugins/builtin/perps/equity-row.tsx +28 -0
- package/src/plugins/builtin/perps/headless.ts +34 -0
- package/src/plugins/builtin/perps/index.tsx +14 -0
- package/src/plugins/builtin/perps/model.ts +58 -0
- package/src/plugins/builtin/perps/pane.tsx +185 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +0 -1
- package/src/plugins/builtin/portfolio-list/cli/render.ts +177 -87
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +6 -20
- package/src/plugins/builtin/portfolio-list/command-bar.ts +6 -2
- package/src/plugins/builtin/portfolio-list/headless.ts +237 -0
- package/src/plugins/builtin/portfolio-list/index.tsx +2 -0
- package/src/plugins/builtin/portfolio-list/mutations.ts +70 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +88 -5
- package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +85 -0
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +18 -12
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +6 -0
- package/src/plugins/builtin/power/client.ts +100 -0
- package/src/plugins/builtin/power/detail.tsx +82 -0
- package/src/plugins/builtin/power/evidence.ts +56 -0
- package/src/plugins/builtin/power/generation.tsx +37 -0
- package/src/plugins/builtin/power/headless.ts +43 -0
- package/src/plugins/builtin/power/index.tsx +16 -0
- package/src/plugins/builtin/power/model.ts +89 -0
- package/src/plugins/builtin/power/pane.tsx +209 -0
- package/src/plugins/builtin/power/query.ts +25 -0
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -3
- package/src/plugins/builtin/research/analyst-pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +13 -4
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +11 -9
- package/src/plugins/builtin/research-search/pane.tsx +21 -7
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +1 -1
- package/src/plugins/builtin/reverse-dcf/client.ts +35 -0
- package/src/plugins/builtin/reverse-dcf/headless.ts +44 -0
- package/src/plugins/builtin/reverse-dcf/index.tsx +20 -0
- package/src/plugins/builtin/reverse-dcf/model.ts +87 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +107 -0
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/scanner/denied.tsx +4 -2
- package/src/plugins/builtin/scanner/feed.ts +1 -0
- package/src/plugins/builtin/scanner/flow-pane.tsx +3 -2
- package/src/plugins/builtin/scanner/hilo-pane.tsx +1 -1
- package/src/plugins/builtin/seasonality/client.ts +4 -1
- package/src/plugins/builtin/sec/command-bar-search.ts +217 -0
- package/src/plugins/builtin/sec/forms.ts +54 -0
- package/src/plugins/builtin/sec/headless.ts +31 -5
- package/src/plugins/builtin/sec/index.tsx +67 -11
- package/src/plugins/builtin/sec/model.ts +35 -5
- package/src/plugins/builtin/sectors/headless.ts +1 -0
- package/src/plugins/builtin/sectors/index.tsx +72 -26
- package/src/plugins/builtin/shared/cloud-upgrade.ts +143 -39
- package/src/plugins/builtin/shared/pro-feature-wall.tsx +8 -4
- package/src/plugins/builtin/shared/research-cells.tsx +107 -0
- package/src/plugins/builtin/shared/series-list-detail.tsx +5 -1
- package/src/plugins/builtin/shared/ticker-request.ts +1 -1
- package/src/plugins/builtin/shared/wall-teaser-catalog.ts +80 -0
- package/src/plugins/builtin/shared/wall-teaser.tsx +192 -0
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +43 -0
- package/src/plugins/builtin/short-interest/index.tsx +31 -0
- package/src/plugins/builtin/short-interest/watch-client.ts +70 -0
- package/src/plugins/builtin/short-interest/watch-headless.ts +49 -0
- package/src/plugins/builtin/short-interest/watch-model.ts +185 -0
- package/src/plugins/builtin/short-interest/watch-pane.tsx +169 -0
- 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/client.ts +104 -0
- package/src/plugins/builtin/supply-chain/disclosure-quote.tsx +17 -0
- package/src/plugins/builtin/supply-chain/evidence.ts +88 -0
- package/src/plugins/builtin/supply-chain/flow-layout.ts +133 -0
- package/src/plugins/builtin/supply-chain/flow.tsx +323 -0
- package/src/plugins/builtin/supply-chain/graph-client.ts +83 -0
- package/src/plugins/builtin/supply-chain/graph-evidence.ts +25 -0
- package/src/plugins/builtin/supply-chain/graph-model.ts +45 -0
- package/src/plugins/builtin/supply-chain/graph-pane.tsx +218 -0
- package/src/plugins/builtin/supply-chain/graph.tsx +163 -0
- package/src/plugins/builtin/supply-chain/headless.ts +105 -0
- package/src/plugins/builtin/supply-chain/index.tsx +17 -0
- package/src/plugins/builtin/supply-chain/model.ts +145 -0
- package/src/plugins/builtin/supply-chain/pane.tsx +276 -0
- package/src/plugins/builtin/supply-chain/row-evidence.tsx +96 -0
- package/src/plugins/builtin/supply-chain/trust.ts +34 -0
- package/src/plugins/builtin/themes/client.ts +39 -0
- package/src/plugins/builtin/themes/headless.ts +38 -0
- package/src/plugins/builtin/themes/model.ts +92 -0
- package/src/plugins/builtin/themes/pane.tsx +125 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +6 -15
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +201 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +327 -0
- package/src/plugins/builtin/ticker-detail/financials/period-end-history.ts +5 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +60 -12
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +7 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +22 -0
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +53 -30
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +112 -47
- package/src/plugins/builtin/ticker-detail/pane.tsx +50 -6
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +21 -42
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +3 -0
- package/src/plugins/builtin/time-sales/pane.tsx +1 -1
- package/src/plugins/builtin/world-indices/indices.ts +1 -0
- package/src/plugins/catalog-browser.ts +40 -0
- package/src/plugins/catalog-manifest.ts +3 -0
- package/src/plugins/catalog-ui.ts +6 -0
- package/src/plugins/current-target.ts +0 -9
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/renderers/browser/cloud-transport.ts +66 -1
- package/src/renderers/browser/main.tsx +2 -0
- package/src/renderers/dom/data-table/row.tsx +2 -0
- package/src/renderers/dom/desktop/controls.tsx +38 -25
- package/src/renderers/dom/desktop/icons.tsx +8 -0
- package/src/renderers/dom/host/chart-surface.tsx +36 -2
- package/src/renderers/dom/host/focus-scope.ts +11 -1
- package/src/renderers/dom/styles.css +28 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +3 -2
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/http-fetch.ts +2 -0
- package/src/renderers/electrobun/view/main.tsx +3 -1
- package/src/renderers/electrobun/view/rpc-timeout.ts +59 -0
- package/src/renderers/opentui/start.tsx +3 -0
- package/src/shares/api.ts +3 -1
- package/src/sources/gloomberb-cloud/index.ts +47 -13
- package/src/sources/history-result.ts +1 -0
- package/src/sources/history-retention.ts +16 -0
- package/src/sources/provider-router/history.ts +4 -2
- package/src/sources/sec-edgar/identity.ts +36 -0
- package/src/sources/sec-edgar.ts +1 -34
- package/src/state/app/context/index.tsx +8 -0
- package/src/sync/core-contributors.ts +20 -2
- package/src/telemetry/attention-counts.ts +164 -0
- package/src/telemetry/live-config.ts +7 -0
- package/src/test-support/rpc-loopback.ts +7 -3
- package/src/test-support/shell-pane-keys.tsx +34 -0
- package/src/tickers/instrument-kind.ts +12 -6
- package/src/tickers/open-target.ts +1 -1
- package/src/tickers/search/asset-classes.ts +91 -0
- package/src/tickers/search/index.ts +62 -15
- package/src/tickers/search/ranking.ts +22 -3
- package/src/tickers/search/upsert.ts +4 -1
- package/src/time-series/live-bars.ts +8 -3
- package/src/time-series/resolve.ts +224 -17
- package/src/time-series/spec.ts +1 -0
- package/src/time-series/types.ts +9 -1
- package/src/types/config.ts +23 -1
- package/src/types/data-provider.ts +2 -0
- package/src/types/headless.ts +17 -0
- package/src/types/price-history.ts +2 -0
- package/src/ui/host.tsx +6 -0
- package/src/utils/double-escape-close.ts +20 -0
- package/src/utils/exchanges.ts +1 -0
- package/src/utils/html-entities.ts +15 -0
- package/src/utils/http-proxy-response.ts +44 -7
- package/src/utils/plugin-proxy-hosts.json +3 -0
- package/src/utils/price-history.ts +35 -1
- package/src/utils/sec.ts +21 -2
- package/src/utils/text-wrap.ts +13 -8
- package/src/utils/zoned-date-time.ts +67 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { DataTableColumn } from "../../../components";
|
|
2
|
+
import { displayWidth, formatCompact, formatCurrency } from "../../../utils/format";
|
|
3
|
+
import { isAmendedInsiderFiling } from "./amendments";
|
|
4
|
+
import { insiderSecurityTag, insiderTypeLabel, insiderTypeTone, isOpenMarketTrade, type InsiderTypeTone } from "./display";
|
|
5
|
+
import {
|
|
6
|
+
insiderDisplayName,
|
|
7
|
+
insiderRole,
|
|
8
|
+
insiderTransactionId,
|
|
9
|
+
isInsiderDisclosureOnly,
|
|
10
|
+
type ParsedInsiderFiling,
|
|
11
|
+
} from "./model";
|
|
12
|
+
|
|
13
|
+
export type InsiderColumnId = "date" | "insider" | "role" | "type" | "security" | "shares" | "price" | "value";
|
|
14
|
+
|
|
15
|
+
export interface InsiderColumn extends DataTableColumn {
|
|
16
|
+
id: InsiderColumnId;
|
|
17
|
+
/** The type column has room for the 10b5-1 marker. */
|
|
18
|
+
plan?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** One table row: a Form 4 line, or a filing without one (loading, unread, a disclosure). */
|
|
22
|
+
export interface InsiderTableRow {
|
|
23
|
+
id: string;
|
|
24
|
+
entry: ParsedInsiderFiling;
|
|
25
|
+
date: Date | null;
|
|
26
|
+
name: string;
|
|
27
|
+
role: string;
|
|
28
|
+
type: string;
|
|
29
|
+
tone: InsiderTypeTone;
|
|
30
|
+
/** An amended filing (Form 4/A), marked after the type at every width. */
|
|
31
|
+
amended: boolean;
|
|
32
|
+
/** An open-market trade on a form with the Rule 10b5-1 box checked, marked after the type when there is room. */
|
|
33
|
+
plan: boolean;
|
|
34
|
+
security: string | null;
|
|
35
|
+
shares: number | null;
|
|
36
|
+
price: number | null;
|
|
37
|
+
value: number | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function validDate(value: Date | string | number | null | undefined): Date | null {
|
|
41
|
+
if (value == null) return null;
|
|
42
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
43
|
+
return Number.isFinite(date.getTime()) ? date : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Joint filers (a fund and its general partners) read as the first one and how many more. */
|
|
47
|
+
function tableName(entry: ParsedInsiderFiling): string | null {
|
|
48
|
+
const name = insiderDisplayName(entry);
|
|
49
|
+
const owners = name?.split("; ") ?? [];
|
|
50
|
+
return owners.length > 1 ? `${owners[0]} +${owners.length - 1}` : name;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function buildInsiderTableRows(parsed: readonly ParsedInsiderFiling[]): InsiderTableRow[] {
|
|
54
|
+
return parsed.map((entry) => {
|
|
55
|
+
const { filing, transaction } = entry;
|
|
56
|
+
const amendment = isAmendedInsiderFiling(entry);
|
|
57
|
+
const name = tableName(entry);
|
|
58
|
+
if (!transaction) {
|
|
59
|
+
const disclosure = !entry.isLoading && isInsiderDisclosureOnly(entry);
|
|
60
|
+
return {
|
|
61
|
+
id: insiderTransactionId(entry),
|
|
62
|
+
entry,
|
|
63
|
+
date: validDate(filing.filingDate),
|
|
64
|
+
name: name ?? (entry.isLoading ? "Loading..." : "Unknown filer"),
|
|
65
|
+
role: insiderRole(entry),
|
|
66
|
+
type: entry.isLoading ? "" : disclosure ? "NOTICE" : "—",
|
|
67
|
+
tone: "neutral",
|
|
68
|
+
amended: amendment,
|
|
69
|
+
plan: false,
|
|
70
|
+
security: null,
|
|
71
|
+
shares: null,
|
|
72
|
+
price: null,
|
|
73
|
+
value: null,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
id: insiderTransactionId(entry),
|
|
78
|
+
entry,
|
|
79
|
+
date: validDate(transaction.filingDate ?? filing.filingDate),
|
|
80
|
+
name: name ?? "Unknown filer",
|
|
81
|
+
role: insiderRole(entry),
|
|
82
|
+
type: insiderTypeLabel(transaction.transactionType),
|
|
83
|
+
tone: insiderTypeTone(transaction.transactionType),
|
|
84
|
+
amended: amendment,
|
|
85
|
+
// The box covers the whole form; it is about the trade, not the vesting or tax lines beside it.
|
|
86
|
+
plan: transaction.rule10b51 === true && isOpenMarketTrade(transaction.transactionType),
|
|
87
|
+
security: insiderSecurityTag(transaction),
|
|
88
|
+
shares: transaction.shares,
|
|
89
|
+
price: transaction.pricePerShare,
|
|
90
|
+
value: transaction.totalValue,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A transaction date as m/d/yy, read in UTC because it names a calendar day. */
|
|
96
|
+
export function formatInsiderDate(date: Date | null): string {
|
|
97
|
+
if (!date) return "—";
|
|
98
|
+
return `${date.getUTCMonth() + 1}/${date.getUTCDate()}/${String(date.getUTCFullYear()).slice(-2)}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function formatInsiderShares(value: number | null): string {
|
|
102
|
+
return value == null ? "—" : formatCompact(value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function formatInsiderPrice(value: number | null): string {
|
|
106
|
+
return value == null ? "—" : formatCurrency(value);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function formatInsiderValue(value: number | null): string {
|
|
110
|
+
return value == null ? "—" : `$${formatCompact(value)}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The quieter words after the type: "4/A" always, "10b5-1" when the column has room. */
|
|
114
|
+
export function insiderTypeMarkers(row: InsiderTableRow, plan: boolean): string[] {
|
|
115
|
+
return [...(row.amended ? ["4/A"] : []), ...(plan && row.plan ? ["10b5-1"] : [])];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function insiderTypeText(row: InsiderTableRow, plan: boolean): string {
|
|
119
|
+
return [row.type, ...insiderTypeMarkers(row, plan)].filter(Boolean).join(" ");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Cells left and right of the columns, and between two of them. */
|
|
123
|
+
const TABLE_PADDING = 2;
|
|
124
|
+
const COLUMN_GAP = 1;
|
|
125
|
+
/** The kit widens a column to fit its header plus the sort arrow. */
|
|
126
|
+
const HEADER_EXTRA = 2;
|
|
127
|
+
|
|
128
|
+
function fitWidth(texts: readonly string[], label: string, max: number): number {
|
|
129
|
+
const widest = texts.reduce((width, text) => Math.max(width, displayWidth(text)), 0);
|
|
130
|
+
return Math.max(displayWidth(label) + HEADER_EXTRA, Math.min(max, widest));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The columns that fit `width` terminal cells (the desktop passes its width
|
|
135
|
+
* in the same cells). Every column takes the width its widest value needs.
|
|
136
|
+
* When the pane runs short, the role goes first, then the price, the
|
|
137
|
+
* 10b5-1 marker, the security and the share count, and only then does the name
|
|
138
|
+
* column shrink below 16 cells, so a narrow pane still reads date, name,
|
|
139
|
+
* type and value.
|
|
140
|
+
*/
|
|
141
|
+
export function buildInsiderColumns(width: number, rows: readonly InsiderTableRow[]): InsiderColumn[] {
|
|
142
|
+
const anyPlan = rows.some((row) => row.plan);
|
|
143
|
+
const anySecurity = rows.some((row) => row.security);
|
|
144
|
+
const names = rows.map((row) => row.name);
|
|
145
|
+
const widths = {
|
|
146
|
+
date: fitWidth(rows.map((row) => formatInsiderDate(row.date)), "Date", 8),
|
|
147
|
+
insider: fitWidth(names, "Insider", 28),
|
|
148
|
+
role: fitWidth(rows.map((row) => row.role), "Role", 12),
|
|
149
|
+
typeBase: fitWidth(rows.map((row) => insiderTypeText(row, false)), "Type", 12),
|
|
150
|
+
typePlan: fitWidth(rows.map((row) => insiderTypeText(row, true)), "Type", 19),
|
|
151
|
+
security: fitWidth(rows.map((row) => row.security ?? ""), "Security", 12),
|
|
152
|
+
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),
|
|
155
|
+
};
|
|
156
|
+
const show = { role: true, price: true, plan: anyPlan, security: anySecurity, shares: true };
|
|
157
|
+
let insider = widths.insider;
|
|
158
|
+
const available = Math.max(0, Math.floor(width) - TABLE_PADDING);
|
|
159
|
+
const total = () => [
|
|
160
|
+
widths.date,
|
|
161
|
+
insider,
|
|
162
|
+
show.role ? widths.role : 0,
|
|
163
|
+
show.plan ? widths.typePlan : widths.typeBase,
|
|
164
|
+
show.security ? widths.security : 0,
|
|
165
|
+
show.shares ? widths.shares : 0,
|
|
166
|
+
show.price ? widths.price : 0,
|
|
167
|
+
widths.value,
|
|
168
|
+
].reduce((sum, columnWidth) => sum + (columnWidth > 0 ? columnWidth + COLUMN_GAP : 0), 0);
|
|
169
|
+
const shrinkInsider = (floor: number) => {
|
|
170
|
+
insider = Math.max(Math.min(insider, floor), insider - Math.max(0, total() - available));
|
|
171
|
+
};
|
|
172
|
+
shrinkInsider(16);
|
|
173
|
+
for (const drop of ["role", "price", "plan", "security", "shares"] as const) {
|
|
174
|
+
if (total() <= available) break;
|
|
175
|
+
show[drop] = false;
|
|
176
|
+
shrinkInsider(16);
|
|
177
|
+
}
|
|
178
|
+
shrinkInsider(6);
|
|
179
|
+
// A dropped column can leave room for more of the names.
|
|
180
|
+
insider = Math.min(widths.insider, insider + Math.max(0, available - total()));
|
|
181
|
+
|
|
182
|
+
const typeWidth = show.plan ? widths.typePlan : widths.typeBase;
|
|
183
|
+
const columns: InsiderColumn[] = [
|
|
184
|
+
{ id: "date", label: "Date", width: widths.date, align: "left" },
|
|
185
|
+
{ id: "insider", label: "Insider", width: insider, align: "left" },
|
|
186
|
+
...(show.role ? [{ id: "role" as const, label: "Role", width: widths.role, align: "left" as const }] : []),
|
|
187
|
+
{ id: "type", label: "Type", width: typeWidth, align: "left", plan: show.plan },
|
|
188
|
+
...(show.security ? [{ id: "security" as const, label: "Security", width: widths.security, align: "left" as const }] : []),
|
|
189
|
+
...(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" },
|
|
192
|
+
];
|
|
193
|
+
// Spare cells spread over the columns, so the value column ends at the
|
|
194
|
+
// pane's edge in the terminal as it does on the desktop.
|
|
195
|
+
const used = columns.reduce((sum, column) => sum + column.width + COLUMN_GAP, 0);
|
|
196
|
+
const spare = Math.max(0, available - used);
|
|
197
|
+
const each = Math.floor(spare / columns.length);
|
|
198
|
+
return columns.map((column) => ({
|
|
199
|
+
...column,
|
|
200
|
+
width: column.width + each + (column.id === "insider" ? spare - each * columns.length : 0),
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function compareNullable(a: number | null, b: number | null): number {
|
|
205
|
+
if (a == null && b == null) return 0;
|
|
206
|
+
if (a == null) return 1;
|
|
207
|
+
if (b == null) return -1;
|
|
208
|
+
return a - b;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function compareText(a: string, b: string): number {
|
|
212
|
+
return a.localeCompare(b, "en-US", { sensitivity: "base" });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function compareInsiderRows(a: InsiderTableRow, b: InsiderTableRow, columnId: InsiderColumnId): number {
|
|
216
|
+
switch (columnId) {
|
|
217
|
+
case "date": return compareNullable(a.date?.getTime() ?? null, b.date?.getTime() ?? null);
|
|
218
|
+
case "insider": return compareText(a.name, b.name);
|
|
219
|
+
case "role": return compareText(a.role, b.role);
|
|
220
|
+
case "type": return compareText(a.type, b.type);
|
|
221
|
+
case "security": return compareText(a.security ?? "", b.security ?? "");
|
|
222
|
+
case "shares": return compareNullable(a.shares, b.shares);
|
|
223
|
+
case "price": return compareNullable(a.price, b.price);
|
|
224
|
+
case "value": return compareNullable(a.value, b.value);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Box, Text } from "../../../ui";
|
|
2
|
+
import type { DataTableCell } from "../../../components";
|
|
3
|
+
import { colors } from "../../../theme/colors";
|
|
4
|
+
import { blendForContrast } from "../../../theme/color-utils";
|
|
5
|
+
import { clipToDisplayWidth, displayWidth } from "../../../utils/format";
|
|
6
|
+
import type { InsiderTypeTone } from "./display";
|
|
7
|
+
import {
|
|
8
|
+
formatInsiderDate,
|
|
9
|
+
formatInsiderPrice,
|
|
10
|
+
formatInsiderShares,
|
|
11
|
+
formatInsiderValue,
|
|
12
|
+
insiderTypeMarkers,
|
|
13
|
+
insiderTypeText,
|
|
14
|
+
type InsiderColumn,
|
|
15
|
+
type InsiderTableRow,
|
|
16
|
+
} from "./table-model";
|
|
17
|
+
|
|
18
|
+
/** The contrast a buy or sell keeps against the selected row. */
|
|
19
|
+
const SELECTED_TONE_MIN_CONTRAST = 3.6;
|
|
20
|
+
|
|
21
|
+
/** The theme's gain and loss colours, as every pane draws a buy and a sale. */
|
|
22
|
+
export function insiderToneColor(tone: InsiderTypeTone): string {
|
|
23
|
+
return tone === "buy" ? colors.positive : tone === "sell" ? colors.negative : colors.textDim;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function typeColor(tone: InsiderTypeTone, selected: boolean): string {
|
|
27
|
+
if (tone === "neutral") return selected ? colors.selectedText : colors.textDim;
|
|
28
|
+
const color = insiderToneColor(tone);
|
|
29
|
+
return selected ? blendForContrast(color, colors.selected, colors.selectedText, SELECTED_TONE_MIN_CONTRAST) : color;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isoDay(date: Date | null): string | null {
|
|
33
|
+
return date ? date.toISOString().slice(0, 10) : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function renderInsiderCell(
|
|
37
|
+
row: InsiderTableRow,
|
|
38
|
+
column: InsiderColumn,
|
|
39
|
+
_index: number,
|
|
40
|
+
rowState: { selected: boolean },
|
|
41
|
+
): DataTableCell {
|
|
42
|
+
// Awards, exercises and tax lines are context; buys and sales carry the figures.
|
|
43
|
+
const figureColor = row.tone === "neutral" ? colors.textDim : colors.text;
|
|
44
|
+
switch (column.id) {
|
|
45
|
+
case "date":
|
|
46
|
+
return { text: formatInsiderDate(row.date), value: isoDay(row.date), color: colors.textDim };
|
|
47
|
+
case "insider":
|
|
48
|
+
return { text: row.name, color: row.entry.isLoading ? colors.textDim : colors.text };
|
|
49
|
+
case "role":
|
|
50
|
+
return { text: row.role, color: colors.textMuted };
|
|
51
|
+
case "type": {
|
|
52
|
+
const color = typeColor(row.tone, rowState.selected);
|
|
53
|
+
const text = insiderTypeText(row, column.plan === true);
|
|
54
|
+
const markers = insiderTypeMarkers(row, column.plan === true);
|
|
55
|
+
if (markers.length === 0) return { text, color, keepColorWhenSelected: true };
|
|
56
|
+
// The markers read after the type in a quieter colour.
|
|
57
|
+
const markerColor = rowState.selected ? colors.selectedText : colors.textMuted;
|
|
58
|
+
const label = clipToDisplayWidth(row.type, column.width);
|
|
59
|
+
const room = Math.max(0, column.width - displayWidth(label) - (label ? 1 : 0));
|
|
60
|
+
return {
|
|
61
|
+
text,
|
|
62
|
+
content: (
|
|
63
|
+
<Box flexDirection="row" width={column.width} overflow="hidden">
|
|
64
|
+
{label ? <Text fg={color}>{label}</Text> : null}
|
|
65
|
+
{room > 0 ? <Text fg={markerColor}>{`${label ? " " : ""}${clipToDisplayWidth(markers.join(" "), room)}`}</Text> : null}
|
|
66
|
+
</Box>
|
|
67
|
+
),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
case "security":
|
|
71
|
+
return { text: row.security ?? "", value: row.entry.transaction?.securityTitle || null, color: colors.textMuted };
|
|
72
|
+
case "shares":
|
|
73
|
+
return { text: formatInsiderShares(row.shares), value: row.shares, color: figureColor };
|
|
74
|
+
case "price":
|
|
75
|
+
return { text: formatInsiderPrice(row.price), value: row.price, color: figureColor };
|
|
76
|
+
case "value":
|
|
77
|
+
return { text: formatInsiderValue(row.value), value: row.value, color: figureColor };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -35,7 +35,7 @@ export function IvScreenPane({ width, height, focused }: PaneProps) {
|
|
|
35
35
|
const { collectionId } = usePaneCollection();
|
|
36
36
|
const tickers = useTickers();
|
|
37
37
|
const { createPaneFromTemplate } = usePluginAppActions();
|
|
38
|
-
const universe = useMemo(() => vcaUniverse(scope, symbolsText, collectionId,
|
|
38
|
+
const universe = useMemo(() => vcaUniverse(scope, symbolsText, collectionId, [...tickers.values()]), [scope, symbolsText, collectionId, tickers]);
|
|
39
39
|
const key = universe.instruments.map((instrument) => instrument.symbol).join(",");
|
|
40
40
|
const controller = useRef<AbortController | null>(null);
|
|
41
41
|
const [hv, setHv] = useState<Map<string, number | null>>(new Map());
|
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
} from "./model";
|
|
71
71
|
import { ShareBars } from "./share-bars";
|
|
72
72
|
import { usePaneTickerIdentity } from "../../../state/hooks/pane-ticker";
|
|
73
|
+
import { listingIdentity } from "../shared/ticker-request";
|
|
73
74
|
|
|
74
75
|
export const JOBS_PANE_ID = "jobs";
|
|
75
76
|
|
|
@@ -79,9 +80,11 @@ const MOVERS_PAGE = 200;
|
|
|
79
80
|
|
|
80
81
|
type DetailTab = "roles" | "locations" | "seniority" | "salary";
|
|
81
82
|
|
|
82
|
-
function HiringProWall({ symbol }: { symbol: string | null }) {
|
|
83
|
+
function HiringProWall({ symbol, exchange, width, height }: { symbol: string | null; exchange?: string; width: number; height: number }) {
|
|
83
84
|
return (
|
|
84
85
|
<ProWall
|
|
86
|
+
placement="jobs-wall"
|
|
87
|
+
symbol={symbol} exchange={exchange} width={width} height={height}
|
|
85
88
|
title="Hiring data is part of Gloom Cloud Pro."
|
|
86
89
|
message={`Gloomberb reads every listed company's own careers system daily: open roles over time, hiring by function and location, new roles, and pay ranges.${symbol ? ` Open ${symbol}'s hiring picture with Pro.` : ""}`}
|
|
87
90
|
/>
|
|
@@ -511,6 +514,7 @@ function CompanyView({
|
|
|
511
514
|
*/
|
|
512
515
|
function CompanyPanel({
|
|
513
516
|
symbol,
|
|
517
|
+
exchange = "US",
|
|
514
518
|
companyName,
|
|
515
519
|
width,
|
|
516
520
|
height,
|
|
@@ -519,6 +523,7 @@ function CompanyPanel({
|
|
|
519
523
|
embedded = false,
|
|
520
524
|
}: {
|
|
521
525
|
symbol: string;
|
|
526
|
+
exchange?: string;
|
|
522
527
|
companyName: string | null;
|
|
523
528
|
width: number;
|
|
524
529
|
height: number;
|
|
@@ -544,8 +549,8 @@ function CompanyPanel({
|
|
|
544
549
|
}, [data, reload]);
|
|
545
550
|
|
|
546
551
|
if (data?.kind === "denied") {
|
|
547
|
-
if (data.status === 402) return <HiringProWall symbol={symbol} />;
|
|
548
|
-
return <SignInWall action="see who is hiring" needsVerification={data.status === 403} />;
|
|
552
|
+
if (data.status === 402) return <HiringProWall symbol={symbol} exchange={exchange} width={width} height={height} />;
|
|
553
|
+
return <SignInWall placement="jobs-detail-signin" width={width} height={height} symbol={symbol} exchange={exchange} action="see who is hiring" needsVerification={data.status === 403} />;
|
|
549
554
|
}
|
|
550
555
|
if ((status === "idle" || status === "loading") && !data) {
|
|
551
556
|
return <PaneStatusBody loading align="center" loadingLabel={`Loading ${symbol} hiring...`} />;
|
|
@@ -733,15 +738,16 @@ interface JobsViewProps {
|
|
|
733
738
|
}
|
|
734
739
|
|
|
735
740
|
function JobsView({ width, height, focused, companyOnly = false }: JobsViewProps) {
|
|
736
|
-
const { ticker } = usePaneTickerIdentity();
|
|
741
|
+
const { ticker, symbol: boundSymbol } = usePaneTickerIdentity();
|
|
737
742
|
const symbol = ticker?.metadata.ticker ?? null;
|
|
743
|
+
const exchange = listingIdentity(boundSymbol, ticker?.metadata.exchange)?.exchange;
|
|
738
744
|
const companyName = ticker?.metadata.name ?? null;
|
|
739
745
|
const access = usePlanAccess();
|
|
740
746
|
const registrationId = JOBS_PANE_ID;
|
|
741
747
|
|
|
742
|
-
if (!access.signedIn) return <SignInWall action="see who is hiring" />;
|
|
743
|
-
if (!access.emailVerified) return <SignInWall action="see who is hiring" needsVerification />;
|
|
744
|
-
if (!access.hasProAccess) return <HiringProWall symbol={symbol} />;
|
|
748
|
+
if (!access.signedIn) return <SignInWall placement="jobs-signin" width={width} height={height} symbol={symbol} exchange={exchange} action="see who is hiring" />;
|
|
749
|
+
if (!access.emailVerified) return <SignInWall placement="jobs-signin" width={width} height={height} symbol={symbol} exchange={exchange} action="see who is hiring" needsVerification />;
|
|
750
|
+
if (!access.hasProAccess) return <HiringProWall symbol={symbol} exchange={exchange} width={width} height={height} />;
|
|
745
751
|
|
|
746
752
|
if (!symbol) {
|
|
747
753
|
if (companyOnly) return <EmptyState title="No ticker selected." message="Select a ticker to see its hiring." />;
|
|
@@ -750,6 +756,7 @@ function JobsView({ width, height, focused, companyOnly = false }: JobsViewProps
|
|
|
750
756
|
return (
|
|
751
757
|
<CompanyPanel
|
|
752
758
|
symbol={symbol}
|
|
759
|
+
exchange={exchange}
|
|
753
760
|
companyName={companyName}
|
|
754
761
|
width={width}
|
|
755
762
|
height={height}
|
|
@@ -449,7 +449,7 @@ export function KellySizerPane({ focused, width, height }: PaneProps) {
|
|
|
449
449
|
const bodyRows = Math.max(0, height - 1 - tabRows - gridRows - resultRows);
|
|
450
450
|
const chartHeight = showSensitivity ? 0 : bodyRows;
|
|
451
451
|
const showChart = !showSensitivity && chartHeight >= 6 && curvePoints.length > 0;
|
|
452
|
-
const quoteCurrency = positionFinancials?.quote?.currency
|
|
452
|
+
const quoteCurrency = positionFinancials?.quote?.currency || ticker?.metadata.currency || config.baseCurrency;
|
|
453
453
|
// The search shows the ticker and the Portfolio filter the account, so the
|
|
454
454
|
// context is the price first, then the account only when there is no filter.
|
|
455
455
|
const meta = [
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getSharedMarketDataCoordinator, MarketDataCoordinator, resolveEntryValue } from "../../../market-data/coordinator";
|
|
2
|
+
import type { InstrumentRef } from "../../../market-data/request-types";
|
|
3
|
+
import { tickerHasListingSuffix } from "../../../sources/listing-symbols";
|
|
4
|
+
import type { DataProvider } from "../../../types/data-provider";
|
|
5
|
+
import type { PricePoint } from "../../../types/financials";
|
|
6
|
+
import { abortable, abortError } from "../../../utils/async-deadline";
|
|
7
|
+
import { errorMessage } from "../../../utils/errors";
|
|
8
|
+
import { resolveExchangeTimeZone } from "../../../utils/exchanges";
|
|
9
|
+
|
|
10
|
+
export interface MacroDayHistory {
|
|
11
|
+
history: PricePoint[];
|
|
12
|
+
stale: boolean;
|
|
13
|
+
error: string | null;
|
|
14
|
+
fetchedAt: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const CANCELLED = "Macro-day history load was cancelled";
|
|
18
|
+
const US_LISTINGS_ONLY = "Macro-day moves are for US listings.";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A listing whose venue sits outside New York trades US releases on a different clock.
|
|
22
|
+
* A bare symbol such as 7203.T carries no exchange, so its venue suffix names it.
|
|
23
|
+
*/
|
|
24
|
+
function isNonUsVenue(instrument: InstrumentRef): boolean {
|
|
25
|
+
if (tickerHasListingSuffix(instrument.symbol)) return true;
|
|
26
|
+
const zone = resolveExchangeTimeZone(instrument.exchange);
|
|
27
|
+
return !!zone && zone !== "America/New_York";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Five years of daily closes, the longest daily history served; the lookback is cut in the model. */
|
|
31
|
+
export async function loadMacroDayHistory(
|
|
32
|
+
request: { instrument: InstrumentRef; signal?: AbortSignal; forceRefresh?: boolean },
|
|
33
|
+
marketData?: DataProvider,
|
|
34
|
+
): Promise<MacroDayHistory> {
|
|
35
|
+
if (request.signal?.aborted) throw abortError(CANCELLED);
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
if (isNonUsVenue(request.instrument)) return { history: [], stale: false, error: US_LISTINGS_ONLY, fetchedAt: now };
|
|
38
|
+
const coordinator = marketData ? new MarketDataCoordinator(marketData) : getSharedMarketDataCoordinator();
|
|
39
|
+
try {
|
|
40
|
+
if (!coordinator) throw new Error("Market data coordinator unavailable");
|
|
41
|
+
const entry = await abortable(coordinator.loadChart({ instrument: request.instrument, bufferRange: "5Y",
|
|
42
|
+
granularity: "resolution", resolution: "1d" }, { forceRefresh: request.forceRefresh }), request.signal, CANCELLED);
|
|
43
|
+
const history = resolveEntryValue(entry) ?? [];
|
|
44
|
+
return { history, stale: !!entry.error || (entry.staleAt != null && entry.staleAt <= now),
|
|
45
|
+
error: entry.error?.message ?? (history.length ? null : "Daily price history unavailable"), fetchedAt: entry.fetchedAt ?? now };
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (request.signal?.aborted || (error instanceof Error && error.name === "AbortError")) throw abortError(CANCELLED);
|
|
48
|
+
return { history: [], stale: false, error: errorMessage(error), fetchedAt: now };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
2
|
+
import { resolveHeadlessInstrument } from "../shared/headless-market-data";
|
|
3
|
+
import { loadMacroDayHistory } from "./client";
|
|
4
|
+
import { projectMacroDays, type MacroDayStats } from "./model";
|
|
5
|
+
import { MACRO_EVENT_KINDS, MACRO_EVENT_LABELS, MACRO_RELEASE_BASIS } from "./releases";
|
|
6
|
+
|
|
7
|
+
// A mean that rounds to zero reads 0.00%, never -0.00%.
|
|
8
|
+
const percent = (value: unknown) => typeof value !== "number" ? "--" : /[1-9]/.test((value * 100).toFixed(2)) ? `${(value * 100).toFixed(2)}%` : "0.00%";
|
|
9
|
+
const multiple = (value: unknown) => typeof value === "number" ? `${value.toFixed(2)}x` : "--";
|
|
10
|
+
const share = (value: unknown) => typeof value === "number" ? `${Math.round(value * 100)}%` : "--";
|
|
11
|
+
|
|
12
|
+
export const macroDayHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
13
|
+
shape: "bundle", argument: { kind: "ticker", description: "Ticker" },
|
|
14
|
+
describe: (args) => `MDAY ${args.symbols[0] ?? ""}`,
|
|
15
|
+
discovery: { screenshotReadiness: "live-dom", limitations: [
|
|
16
|
+
"US listings; close-to-close on the release day.",
|
|
17
|
+
`Published CPI, jobs and FOMC days through ${MACRO_RELEASE_BASIS.coveredThrough}.`,
|
|
18
|
+
] },
|
|
19
|
+
options: [
|
|
20
|
+
{ key: "release", type: "enum", values: [{ value: "all" }, ...MACRO_EVENT_KINDS.map((value) => ({ value }))], defaultValue: "all",
|
|
21
|
+
description: "Release the pane lists", pluginState: { pluginId: "ticker-research", key: "release" } },
|
|
22
|
+
{ key: "lookbackYears", type: "integer", minimum: 1, maximum: 5, defaultValue: 5, description: "Years of daily closes" },
|
|
23
|
+
],
|
|
24
|
+
async load(args, ctx) {
|
|
25
|
+
const instrument = await resolveHeadlessInstrument(ctx, args.symbols[0]!);
|
|
26
|
+
const history = await loadMacroDayHistory({ instrument, signal: ctx.signal }, ctx.marketData);
|
|
27
|
+
ctx.signal.throwIfAborted();
|
|
28
|
+
const model = projectMacroDays(history.history, { symbol: instrument.symbol, lookbackYears: Number(args.options.lookbackYears) || 5 });
|
|
29
|
+
const row = (label: string, stats: MacroDayStats) => ({ label, ...stats });
|
|
30
|
+
const release = String(args.options.release ?? "all");
|
|
31
|
+
const events = release === "all" ? model.events : model.events.filter((event) => event.kind === release);
|
|
32
|
+
return {
|
|
33
|
+
sections: [
|
|
34
|
+
{ title: "By release", columns: [{ key: "label", header: "Day" }, { key: "count", header: "Days" },
|
|
35
|
+
{ key: "meanAbs", header: "Avg |move|", format: percent }, { key: "multiple", header: "vs normal", format: multiple },
|
|
36
|
+
{ key: "mean", header: "Avg move", format: percent }, { key: "hitRate", header: "Up", format: share }],
|
|
37
|
+
rows: [...MACRO_EVENT_KINDS.map((kind) => row(MACRO_EVENT_LABELS[kind], model.byKind[kind])),
|
|
38
|
+
row("Any release", model.allEvents), row("Normal day", model.normal)] },
|
|
39
|
+
{ title: "Release days", columns: [{ key: "date", header: "Date" }, { key: "event", header: "Release" },
|
|
40
|
+
{ key: "session", header: "Session" }, { key: "move", header: "Move", format: percent }, { key: "multiple", header: "vs normal", format: multiple }],
|
|
41
|
+
rows: events.map((event) => ({ ...event, event: MACRO_EVENT_LABELS[event.kind] })) },
|
|
42
|
+
],
|
|
43
|
+
complete: !history.stale && !history.error && model.events.length > 0,
|
|
44
|
+
unavailableSymbols: model.events.length ? [] : [instrument.symbol],
|
|
45
|
+
errors: history.error ? [history.error] : [],
|
|
46
|
+
metadata: { unit: "decimal return, close to close on the release day", start: model.start, asOf: model.asOf,
|
|
47
|
+
releasesThrough: MACRO_RELEASE_BASIS.coveredThrough, releaseSources: MACRO_RELEASE_BASIS.sources,
|
|
48
|
+
stale: history.stale, fetchedAt: history.fetchedAt, methodology: "docs/research-data.md#macro-day-reaction" },
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PluginModule } from "../plugin-module";
|
|
2
|
+
import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
3
|
+
import { macroDayHeadless } from "./headless";
|
|
4
|
+
import { LOOKBACK_OPTIONS, MacroDayPane } from "./pane";
|
|
5
|
+
|
|
6
|
+
export const macroDayModule: PluginModule = {
|
|
7
|
+
panes: [{
|
|
8
|
+
id: "macro-day", name: "Macro-Day Reaction", icon: "M", component: MacroDayPane,
|
|
9
|
+
defaultPosition: "right", tickerFollower: true, defaultMode: "floating", defaultFloatingSize: { width: 76, height: 26 },
|
|
10
|
+
tableExport: true,
|
|
11
|
+
settings: { title: "Macro-Day Reaction Settings", fields: [
|
|
12
|
+
{ key: "lookbackYears", label: "Lookback", type: "select", options: LOOKBACK_OPTIONS },
|
|
13
|
+
] },
|
|
14
|
+
}],
|
|
15
|
+
paneTemplates: [{ ...createTickerSurfacePaneTemplate({
|
|
16
|
+
id: "macro-day-pane", paneId: "macro-day", label: "Macro-Day Reaction",
|
|
17
|
+
description: "How a name moves on CPI, jobs and FOMC days against a normal day: average move, direction, hit rate and every release day.",
|
|
18
|
+
keywords: ["mday", "macro day", "cpi day", "fomc day", "jobs day", "nfp", "payrolls", "event study", "release reaction"],
|
|
19
|
+
shortcut: "MDAY", publicShare: true,
|
|
20
|
+
}), headless: macroDayHeadless }],
|
|
21
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { PricePoint } from "../../../types/financials";
|
|
2
|
+
import { zonedDateTimeParts } from "../../../utils/zoned-date-time";
|
|
3
|
+
import { MACRO_EVENT_KINDS, MACRO_RELEASE_BASIS, macroReleases, type MacroEventKind, type MacroRelease } from "./releases";
|
|
4
|
+
|
|
5
|
+
const DAY_MS = 86_400_000;
|
|
6
|
+
/** Longest calendar gap between two closes that is still one session apart: Thursday to Monday over a Friday holiday. */
|
|
7
|
+
const MAX_SESSION_GAP_DAYS = 4;
|
|
8
|
+
|
|
9
|
+
export interface MacroDayEvent {
|
|
10
|
+
kind: MacroEventKind;
|
|
11
|
+
/** Release day, New York date. */
|
|
12
|
+
date: string;
|
|
13
|
+
/** The session whose close carries the reaction: the release day, or the next session when the market was shut. */
|
|
14
|
+
session: string;
|
|
15
|
+
/** Close before the release to the session's close. */
|
|
16
|
+
move: number;
|
|
17
|
+
/** |move| over the normal day's average |move|. */
|
|
18
|
+
multiple: number | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MacroDayStats {
|
|
22
|
+
count: number;
|
|
23
|
+
meanAbs: number | null;
|
|
24
|
+
mean: number | null;
|
|
25
|
+
/** Share of days that closed up. */
|
|
26
|
+
hitRate: number | null;
|
|
27
|
+
/** meanAbs over the normal day's. */
|
|
28
|
+
multiple: number | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MacroDayModel {
|
|
32
|
+
symbol: string;
|
|
33
|
+
/** Newest first. */
|
|
34
|
+
events: MacroDayEvent[];
|
|
35
|
+
byKind: Record<MacroEventKind, MacroDayStats>;
|
|
36
|
+
/** Every release day of the three kinds together; a day with two releases counts once. */
|
|
37
|
+
allEvents: MacroDayStats;
|
|
38
|
+
/** Sessions with no release of the three kinds. */
|
|
39
|
+
normal: MacroDayStats;
|
|
40
|
+
start: string | null;
|
|
41
|
+
asOf: string | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Daily labels at UTC midnight name that date; any other stamp reads in New York. */
|
|
45
|
+
function sessionDate(time: number): string {
|
|
46
|
+
if (time % DAY_MS === 0) return new Date(time).toISOString().slice(0, 10);
|
|
47
|
+
const { year, month, day } = zonedDateTimeParts(time, "America/New_York");
|
|
48
|
+
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const dayNumber = (date: string) => Date.parse(`${date}T00:00:00Z`) / DAY_MS;
|
|
52
|
+
const isWeekday = (date: string) => { const day = new Date(`${date}T00:00:00Z`).getUTCDay(); return day > 0 && day < 6; };
|
|
53
|
+
|
|
54
|
+
function stats(moves: number[], normalAbs: number | null): MacroDayStats {
|
|
55
|
+
if (!moves.length) return { count: 0, meanAbs: null, mean: null, hitRate: null, multiple: null };
|
|
56
|
+
const meanAbs = moves.reduce((sum, move) => sum + Math.abs(move), 0) / moves.length;
|
|
57
|
+
return { count: moves.length, meanAbs, mean: moves.reduce((sum, move) => sum + move, 0) / moves.length,
|
|
58
|
+
hitRate: moves.filter((move) => move > 0).length / moves.length,
|
|
59
|
+
multiple: normalAbs ? meanAbs / normalAbs : null };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Close to close on the release day: 8:30 ET releases land before the open
|
|
64
|
+
* and 14:00 FOMC statements before the close, so the release day's close
|
|
65
|
+
* carries either. A release on a weekday the market was shut (Good Friday)
|
|
66
|
+
* is read on the next session, from the close before the release. A release
|
|
67
|
+
* whose close-to-close would span a gap in the history is left out rather
|
|
68
|
+
* than read across it. A lone missing bar on the release day itself cannot
|
|
69
|
+
* be told from a holiday and is read on the next session.
|
|
70
|
+
*/
|
|
71
|
+
export function projectMacroDays(history: readonly PricePoint[], options: {
|
|
72
|
+
symbol: string;
|
|
73
|
+
lookbackYears: number;
|
|
74
|
+
releases?: readonly MacroRelease[];
|
|
75
|
+
coveredThrough?: string;
|
|
76
|
+
}): MacroDayModel {
|
|
77
|
+
const coveredThrough = options.coveredThrough ?? MACRO_RELEASE_BASIS.coveredThrough;
|
|
78
|
+
const byDate = new Map<string, number>();
|
|
79
|
+
for (const point of history) {
|
|
80
|
+
const time = new Date(point.date).getTime();
|
|
81
|
+
if (!Number.isFinite(time) || !Number.isFinite(point.close) || point.close <= 0) continue;
|
|
82
|
+
byDate.set(sessionDate(time), point.close);
|
|
83
|
+
}
|
|
84
|
+
const sessions = [...byDate.keys()].sort();
|
|
85
|
+
const last = sessions.at(-1) ?? null;
|
|
86
|
+
const empty = (): MacroDayStats => stats([], null);
|
|
87
|
+
if (!last) return { symbol: options.symbol, events: [], byKind: { cpi: empty(), jobs: empty(), fomc: empty() },
|
|
88
|
+
allEvents: empty(), normal: empty(), start: null, asOf: null };
|
|
89
|
+
|
|
90
|
+
// Sessions after the published lists end may be unlisted release days, so they are left out of both sides.
|
|
91
|
+
const end = last < coveredThrough ? last : coveredThrough;
|
|
92
|
+
const startYear = Number(end.slice(0, 4)) - Math.max(1, options.lookbackYears);
|
|
93
|
+
const start = `${startYear}${end.slice(4)}`;
|
|
94
|
+
/** Session date -> close-to-close move from the session before, when the two are one session apart. */
|
|
95
|
+
const moves = new Map<string, number>();
|
|
96
|
+
for (let index = 1; index < sessions.length; index++) {
|
|
97
|
+
const date = sessions[index]!, previous = sessions[index - 1]!;
|
|
98
|
+
if (date <= start || date > end || dayNumber(date) - dayNumber(previous) > MAX_SESSION_GAP_DAYS) continue;
|
|
99
|
+
moves.set(date, byDate.get(date)! / byDate.get(previous)! - 1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const reactionSessions = new Set<string>();
|
|
103
|
+
const found: Omit<MacroDayEvent, "multiple">[] = [];
|
|
104
|
+
for (const release of options.releases ?? macroReleases()) {
|
|
105
|
+
if (release.date <= start || release.date > end || !isWeekday(release.date)) continue;
|
|
106
|
+
const at = sessions.findIndex((date) => date >= release.date);
|
|
107
|
+
if (at < 1) continue;
|
|
108
|
+
const session = sessions[at]!;
|
|
109
|
+
const move = moves.get(session);
|
|
110
|
+
if (move == null || (session !== release.date && skipsAnotherWeekday(release.date, sessions[at - 1]!, session))) continue;
|
|
111
|
+
reactionSessions.add(session);
|
|
112
|
+
found.push({ kind: release.kind, date: release.date, session, move });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const normalMoves = [...moves].filter(([date]) => !reactionSessions.has(date)).map(([, move]) => move);
|
|
116
|
+
const normal = stats(normalMoves, null);
|
|
117
|
+
const normalAbs = normal.meanAbs;
|
|
118
|
+
const events = found.map((event) => ({ ...event, multiple: normalAbs ? Math.abs(event.move) / normalAbs : null }))
|
|
119
|
+
.sort((left, right) => right.date.localeCompare(left.date) || left.kind.localeCompare(right.kind));
|
|
120
|
+
const byKind = Object.fromEntries(MACRO_EVENT_KINDS.map((kind) =>
|
|
121
|
+
[kind, stats(events.filter((event) => event.kind === kind).map((event) => event.move), normalAbs)])) as Record<MacroEventKind, MacroDayStats>;
|
|
122
|
+
const allEvents = stats([...reactionSessions].map((session) => moves.get(session)!), normalAbs);
|
|
123
|
+
return { symbol: options.symbol, events, byKind, allEvents, normal, start, asOf: end };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* A release weekday with no close of its own reads as a holiday only when the
|
|
128
|
+
* closes either side have nothing but it and a weekend between them; any other
|
|
129
|
+
* weekday between them is a gap in the data, and the move would span it.
|
|
130
|
+
*/
|
|
131
|
+
function skipsAnotherWeekday(release: string, previous: string, next: string): boolean {
|
|
132
|
+
for (let day = dayNumber(previous) + 1; day < dayNumber(next); day++) {
|
|
133
|
+
const date = new Date(day * DAY_MS).toISOString().slice(0, 10);
|
|
134
|
+
if (date !== release && isWeekday(date)) return true;
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|