gloomberb 0.17.5 → 0.17.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/src/api-client/chat.ts +9 -0
- package/src/api-client/commodity-balances.ts +77 -0
- package/src/api-client/data.ts +31 -2
- package/src/api-client/earnings.ts +10 -0
- package/src/api-client/index.ts +5 -0
- package/src/api-client/insider-signals.ts +75 -0
- package/src/api-client/market-discovery.ts +1 -1
- package/src/api-client/normalizers.ts +1 -0
- package/src/api-client/paths.ts +9 -0
- package/src/api-client/socket.ts +10 -0
- package/src/api-client/types.ts +25 -1
- package/src/app/runtime/broker-import.ts +23 -21
- package/src/app/runtime/plugin-bindings.ts +13 -7
- package/src/app/runtime/update.ts +14 -1
- package/src/architecture/core-builtin-allowlist.json +2 -10
- package/src/brokers/broker-portfolio-sync.ts +66 -0
- package/src/brokers/sync-broker-instance.ts +75 -41
- package/src/cli/commands/market.ts +101 -34
- package/src/cli/commands/options-chain.ts +20 -6
- package/src/cli/commands/overview.ts +52 -11
- package/src/cli/commands/search.ts +1 -1
- package/src/cli/commands/ticker.ts +24 -4
- package/src/cli/context.ts +13 -0
- package/src/cli/errors.ts +15 -2
- package/src/cli/fx-pair.ts +77 -0
- package/src/cli/history-rows.ts +25 -11
- package/src/cli/index.ts +24 -12
- package/src/cli/listing-arg.ts +61 -138
- package/src/cli/listing-suggestion.ts +3 -21
- package/src/cli/pane-functions/catalog.ts +50 -0
- package/src/cli/pane-functions/freshness.ts +44 -9
- package/src/cli/pane-functions/function-help.ts +8 -8
- package/src/cli/pane-functions/headless.ts +19 -7
- package/src/cli/pane-functions/index.ts +63 -5
- package/src/cli/pane-functions/resolver.ts +29 -8
- package/src/cli/pane-functions/screenshot.ts +2 -0
- package/src/cli/report-tables.ts +11 -4
- package/src/cli/result.ts +2 -0
- package/src/cli/series-search.ts +6 -1
- package/src/cli/ticker-resolution.ts +1 -2
- package/src/cli/unknown-command.ts +18 -0
- package/src/components/chart/composite/composite-chart.tsx +80 -19
- package/src/components/chart/composite/format.ts +10 -0
- package/src/components/chart/composite/legend.tsx +18 -4
- package/src/components/chart/composite/panel-labels.ts +38 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +25 -7
- package/src/components/chart/composite/price-axis-labels.tsx +16 -3
- package/src/components/chart/composite/scene.ts +60 -2
- package/src/components/chart/composite/series-axis-tags.tsx +67 -0
- package/src/components/chart/composite/tools.ts +2 -2
- package/src/components/chart/composite/types.ts +28 -0
- package/src/components/chart/core/view-reset.ts +46 -0
- package/src/components/command-bar/commands/collection.ts +1 -1
- package/src/components/command-bar/commands/direct/index.ts +1 -1
- package/src/components/command-bar/list/model.ts +7 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +1 -1
- package/src/components/command-bar/routes/root/pick-prefetch.ts +38 -0
- package/src/components/command-bar/routes/root/provider-search.ts +6 -0
- package/src/components/command-bar/routes/root/runtime.ts +3 -0
- package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -1
- package/src/components/command-bar/routes/root/shortcut-items.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/actions.ts +34 -0
- package/src/components/command-bar/surface/index.tsx +5 -0
- package/src/components/command-bar/workflow/collection-actions.ts +6 -5
- package/src/components/command-bar/workflow/ops.ts +4 -2
- package/src/components/command-bar/workflow/pane-reuse.ts +3 -2
- package/src/components/command-bar/workflow/tickers.ts +1 -1
- package/src/components/layout/pane/content.tsx +11 -8
- package/src/components/layout/pane/ticker-fate-gate.tsx +56 -0
- package/src/components/onboarding/wizard-model.ts +1 -1
- package/src/components/onboarding/wizard-positions.ts +18 -10
- package/src/components/ticker/badge/format.ts +64 -15
- package/src/components/ticker/badge/index.tsx +32 -11
- package/src/components/ticker/badge/list.tsx +4 -3
- package/src/components/ticker/badge/text.tsx +2 -2
- package/src/components/ticker/list-table-view.tsx +8 -1
- package/src/components/ui/data-table/opentui/index.tsx +61 -20
- package/src/components/ui/data-table/opentui/model.ts +104 -0
- package/src/components/ui/inline-quick-add.tsx +4 -1
- package/src/components/ui/query-bar-keys.tsx +29 -5
- package/src/components/ui/query-bar.tsx +29 -4
- package/src/components/ui/windowed-list.tsx +27 -0
- package/src/docs/snapshot.json +248 -5
- package/src/i18n/es.ts +1 -1
- package/src/i18n/ja.ts +1 -1
- package/src/i18n/ko.ts +1 -1
- package/src/i18n/zh-cn.ts +1 -1
- package/src/i18n/zh-tw.ts +1 -1
- package/src/layout-marketplace/cloud.ts +16 -5
- package/src/layout-marketplace/gallery.tsx +4 -4
- package/src/layout-marketplace/linked-sync.ts +9 -3
- package/src/layout-marketplace/linked.ts +24 -3
- package/src/layout-marketplace/model.ts +39 -4
- package/src/market-data/coordinator/index.ts +34 -1
- package/src/market-data/futures-contract.ts +53 -0
- package/src/market-data/hooks.tsx +57 -1
- package/src/market-data/market/bar-period.ts +71 -0
- package/src/market-data/market/format.ts +14 -2
- package/src/market-data/market/freshness.ts +28 -1
- package/src/market-data/market/status.ts +152 -13
- package/src/market-data/model-dividend-yield.ts +76 -0
- package/src/market-data/quotes/freshness.ts +30 -1
- package/src/{plugins/builtin/portfolio-list → market-data/quotes}/live-valuation.ts +6 -6
- package/src/market-data/quotes/regular-session-valuation.ts +32 -1
- package/src/plugins/builtin/analytics/metrics.ts +8 -4
- package/src/plugins/builtin/analytics/risk-headless.ts +12 -5
- package/src/plugins/builtin/analytics/risk-model.ts +74 -15
- package/src/plugins/builtin/analytics/risk-pane.tsx +6 -6
- package/src/plugins/builtin/builtin-plugin-meta.ts +0 -1
- package/src/plugins/builtin/chart-composer/editor/controller.ts +14 -10
- package/src/plugins/builtin/chart-composer/formula.ts +233 -0
- package/src/plugins/builtin/chart-composer/headless.ts +25 -1
- package/src/plugins/builtin/chart-composer/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/pane.tsx +21 -8
- package/src/plugins/builtin/chart-composer/presets.ts +91 -2
- package/src/plugins/builtin/chart-composer/quick-add.tsx +9 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +44 -11
- package/src/plugins/builtin/chart-composer/series-expression.ts +1 -1
- package/src/plugins/builtin/chart-composer/settings.ts +16 -4
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/content/channel-navigation.ts +17 -6
- package/src/plugins/builtin/chat/content/index.tsx +12 -5
- package/src/plugins/builtin/chat/content/mentions.ts +50 -21
- package/src/plugins/builtin/chat/content/scroll.ts +12 -5
- package/src/plugins/builtin/chat/content/snapshot.ts +11 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +48 -37
- package/src/plugins/builtin/chat/content/whole-message-copy.ts +44 -2
- package/src/plugins/builtin/chat/controller/index.ts +68 -17
- package/src/plugins/builtin/chat/controller/mention-candidates.ts +104 -0
- package/src/plugins/builtin/chat/controller/realtime.ts +9 -0
- package/src/plugins/builtin/chat/controller/state.ts +10 -0
- package/src/plugins/builtin/chat/controller/view.ts +2 -0
- package/src/plugins/builtin/chat/conversation-recency.ts +73 -0
- package/src/plugins/builtin/chat/conversations.ts +12 -5
- package/src/plugins/builtin/chat/layout.ts +23 -5
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
- package/src/plugins/builtin/chat/sidebar-rows.ts +18 -5
- package/src/plugins/builtin/chat/sidebar.tsx +6 -1
- package/src/plugins/builtin/cloud/askg/manifest.ts +4 -3
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +16 -5
- package/src/plugins/builtin/commodities-energy/index.tsx +23 -4
- package/src/plugins/builtin/commodity-balances/client.ts +51 -193
- package/src/plugins/builtin/commodity-balances/headless.ts +10 -3
- package/src/plugins/builtin/commodity-balances/model.ts +5 -180
- package/src/plugins/builtin/commodity-balances/pane.tsx +3 -0
- package/src/plugins/builtin/custom-view/index.tsx +1 -1
- package/src/plugins/builtin/custom-view/loader.ts +57 -7
- package/src/plugins/builtin/custom-view/sources.ts +146 -0
- package/src/plugins/builtin/custom-view/view-spec.ts +37 -1
- package/src/plugins/builtin/earnings/board-model.ts +33 -3
- package/src/plugins/builtin/earnings/board-table.ts +59 -14
- package/src/plugins/builtin/earnings/board.tsx +36 -3
- package/src/plugins/builtin/earnings/headless.ts +24 -5
- package/src/plugins/builtin/earnings/history-model.ts +9 -1
- package/src/plugins/builtin/earnings/history-table.ts +16 -5
- package/src/plugins/builtin/earnings/history.tsx +40 -10
- package/src/plugins/builtin/earnings/index.tsx +3 -1
- package/src/plugins/builtin/earnings/no-earnings.ts +52 -0
- package/src/plugins/builtin/econ-statistics/detail.tsx +4 -1
- package/src/plugins/builtin/econ-statistics/headless.ts +5 -0
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +4 -0
- package/src/plugins/builtin/futures/model.ts +5 -4
- package/src/plugins/builtin/futures/series.ts +38 -1
- package/src/plugins/builtin/futures-curve/client.ts +4 -4
- package/src/plugins/builtin/futures-curve/headless.ts +20 -2
- package/src/plugins/builtin/futures-curve/index.tsx +2 -2
- package/src/plugins/builtin/futures-curve/model.ts +45 -2
- package/src/plugins/builtin/futures-curve/pane.tsx +14 -10
- package/src/plugins/builtin/insider/headless.ts +74 -4
- package/src/plugins/builtin/insider/index.tsx +99 -5
- package/src/plugins/builtin/insider/signals-client.ts +69 -0
- package/src/plugins/builtin/insider/signals-model.ts +146 -0
- package/src/plugins/builtin/insider/signals.tsx +176 -0
- package/src/plugins/builtin/iv-history/client.ts +41 -2
- package/src/plugins/builtin/iv-history/headless.ts +24 -16
- package/src/plugins/builtin/iv-history/screen-pane.tsx +25 -7
- package/src/plugins/builtin/jodi/client.ts +42 -205
- package/src/plugins/builtin/jodi/headless.ts +13 -4
- package/src/plugins/builtin/jodi/model.ts +18 -43
- package/src/plugins/builtin/jodi/pane.tsx +5 -0
- package/src/plugins/builtin/market-movers/client.ts +32 -6
- package/src/plugins/builtin/market-movers/headless.ts +7 -2
- package/src/plugins/builtin/market-movers/model.ts +64 -3
- package/src/plugins/builtin/market-movers/screener.ts +20 -9
- package/src/plugins/builtin/market-valuation/detail.tsx +7 -2
- package/src/plugins/builtin/market-valuation/headless.ts +17 -4
- package/src/plugins/builtin/market-valuation/pane.tsx +7 -3
- package/src/plugins/builtin/market-valuation/view.ts +10 -0
- package/src/plugins/builtin/options-positioning/client.ts +10 -0
- package/src/plugins/builtin/options-positioning/headless.ts +88 -13
- package/src/plugins/builtin/options-positioning/index.tsx +2 -2
- package/src/plugins/builtin/options-positioning/model.ts +33 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +38 -16
- package/src/plugins/builtin/options-scenario/charts.tsx +13 -4
- package/src/plugins/builtin/options-scenario/client.ts +111 -17
- package/src/plugins/builtin/options-scenario/collar.ts +141 -0
- package/src/plugins/builtin/options-scenario/csp.ts +1 -1
- package/src/plugins/builtin/options-scenario/freshness.ts +27 -0
- package/src/plugins/builtin/options-scenario/headless.ts +28 -12
- package/src/plugins/builtin/options-scenario/index.tsx +1 -1
- package/src/plugins/builtin/options-scenario/model.ts +55 -5
- package/src/plugins/builtin/options-scenario/pane.tsx +24 -5
- package/src/plugins/builtin/portfolio-list/allocation.ts +9 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +39 -20
- package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +15 -7
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +1 -1
- package/src/plugins/builtin/portfolio-list/column-values.ts +4 -3
- package/src/plugins/builtin/portfolio-list/command-bar.ts +1 -1
- package/src/plugins/builtin/portfolio-list/headless.ts +10 -3
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +36 -4
- package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +1 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +35 -6
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
- package/src/plugins/builtin/portfolio-list/settings.ts +22 -0
- package/src/plugins/builtin/portfolio-list/since-last-seen.ts +197 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +14 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +14 -5
- package/src/plugins/builtin/research/event-model.ts +41 -49
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/reverse-dcf/headless.ts +25 -9
- package/src/plugins/builtin/reverse-dcf/index.tsx +4 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +39 -5
- package/src/plugins/builtin/reverse-dcf/pane.tsx +16 -6
- package/src/plugins/builtin/shared/earnings-report.ts +48 -1
- package/src/plugins/builtin/shared/history-span.ts +55 -0
- package/src/plugins/builtin/shared/report-freshness.ts +20 -11
- package/src/plugins/builtin/shared/series-detail-chart.tsx +5 -2
- package/src/plugins/builtin/shared/series-list-detail.tsx +7 -3
- package/src/plugins/builtin/shared/ticker-surface.ts +16 -2
- package/src/plugins/builtin/shared/use-quote-board.ts +12 -5
- package/src/plugins/builtin/themes/headless.ts +15 -2
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +85 -17
- package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +92 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +26 -8
- package/src/plugins/builtin/ticker-detail/headless-schema.ts +4 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +95 -22
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +17 -3
- package/src/{plugins/builtin/portfolio-list → portfolio}/mutations.ts +6 -6
- package/src/portfolio/valuation-basis.ts +4 -21
- package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
- package/src/remote/controller.ts +27 -7
- package/src/remote/schema.ts +25 -9
- package/src/renderers/cloudflare/http-proxy.ts +21 -13
- package/src/renderers/dom/desktop/query-bar.tsx +1 -1
- package/src/renderers/dom/dom-ui-host.tsx +2 -0
- package/src/renderers/dom/windowed-list.tsx +201 -0
- package/src/renderers/dom/windowed-rows.ts +65 -0
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +2 -9
- package/src/renderers/electrobun/bun/desktop/http-stream.ts +21 -6
- package/src/renderers/electrobun/bun/desktop/update.ts +21 -6
- package/src/renderers/electrobun/shared/protocol.ts +4 -2
- package/src/renderers/electrobun/view/http-fetch.ts +12 -1
- package/src/sources/listing-symbols.ts +8 -4
- package/src/state/app/context/index.tsx +13 -0
- package/src/state/app/tab-cursor.ts +85 -0
- package/src/sync/core-contributors.ts +206 -59
- package/src/test-support/config-store.ts +31 -0
- package/src/test-support/moved-symbols.ts +64 -0
- package/src/theme/colors.ts +30 -0
- package/src/tickers/listing-fate.ts +242 -0
- package/src/tickers/search/index.ts +45 -3
- package/src/tickers/search/ranking.ts +19 -2
- package/src/tickers/search/types.ts +3 -0
- package/src/tickers/search/upsert.ts +22 -7
- package/src/tickers/ticker-changes.ts +26 -9
- package/src/time-series/bar-spacing.ts +19 -0
- package/src/time-series/chart-data.ts +37 -13
- package/src/time-series/field-catalog.ts +5 -2
- package/src/time-series/forward-valuation.ts +10 -7
- package/src/time-series/fundamentals.ts +49 -11
- package/src/time-series/hooks.ts +2 -0
- package/src/time-series/live-bars.ts +3 -1
- package/src/time-series/parsed-history-cache.ts +2 -0
- package/src/time-series/price-units.ts +69 -0
- package/src/time-series/resolve.ts +50 -5
- package/src/time-series/session-bar.ts +138 -0
- package/src/time-series/spec.ts +12 -3
- package/src/time-series/studies.ts +211 -12
- package/src/time-series/transforms.ts +2 -0
- package/src/time-series/types.ts +11 -1
- package/src/time-series/valuation-price.ts +13 -0
- package/src/types/config.ts +10 -1
- package/src/types/financials.ts +2 -0
- package/src/types/headless.ts +27 -0
- package/src/types/plugin.ts +7 -0
- package/src/ui/host.tsx +5 -0
- package/src/updater/index.ts +3 -1
- package/src/utils/asset-category.ts +20 -0
- package/src/utils/decimal-comma.ts +83 -3
- package/src/utils/fundamentals.ts +7 -2
- package/src/utils/http-proxy-response.ts +66 -9
- package/src/utils/plugin-proxy-hosts.json +1 -5
- package/src/utils/typo-distance.ts +22 -0
- package/src/plugins/builtin/shared/zip-text.ts +0 -46
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gloomberb",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.6",
|
|
4
4
|
"packageManager": "bun@1.3.11",
|
|
5
5
|
"description": "Market research and portfolio tracker for the terminal",
|
|
6
6
|
"module": "src/public/index.ts",
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
"benchmark:tui:compare": "bun run scripts/benchmark-tui-compare.ts",
|
|
84
84
|
"benchmark:tui:memory": "bun run scripts/benchmark-tui-memory.ts",
|
|
85
85
|
"check:pane-chrome": "bun run scripts/pane-chrome/checks.ts",
|
|
86
|
+
"check:chat-mentions": "bun scripts/pane-chrome/chat-mentions.ts",
|
|
86
87
|
"check:pane-screenshot": "bun run scripts/pane-chrome/screenshot.ts",
|
|
87
88
|
"check:pane-rest": "bun run scripts/pane-chrome/rest-diff.ts",
|
|
88
89
|
"check:pane-layout": "bun run scripts/pane-chrome/layout-sweep.ts",
|
|
@@ -111,6 +112,7 @@
|
|
|
111
112
|
"gloom-polls": "github:gloom-sh/gloom-polls",
|
|
112
113
|
"gloom-prediction-markets": "github:gloom-sh/gloom-prediction-markets",
|
|
113
114
|
"happy-dom": "^20.11.1",
|
|
115
|
+
"playwright-core": "1.60.0",
|
|
114
116
|
"png-to-ico": "^2.1.8",
|
|
115
117
|
"rcedit": "^4.0.1",
|
|
116
118
|
"typescript": "^5.9.2",
|
package/src/api-client/chat.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
ChatAttachment,
|
|
13
13
|
ChatChannel,
|
|
14
14
|
ChatChannelState,
|
|
15
|
+
ChatMentionCandidate,
|
|
15
16
|
ChatDiscordLink,
|
|
16
17
|
ChatMessage,
|
|
17
18
|
ChatNotification,
|
|
@@ -118,6 +119,14 @@ export class CloudChatApi {
|
|
|
118
119
|
this.emitAttachmentSupport();
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
getMentionCandidates(channelId: string): Promise<ChatMentionCandidate[]> {
|
|
123
|
+
return this.request(`/chat/channels/${encodeURIComponent(channelId)}/mention-candidates`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
subscribeMentionCandidatesChanged(listener: () => void): () => void {
|
|
127
|
+
return this.options.socket.subscribeChatMentionCandidatesChanged(listener);
|
|
128
|
+
}
|
|
129
|
+
|
|
121
130
|
async getChannels(): Promise<ChatChannel[]> {
|
|
122
131
|
const channels =
|
|
123
132
|
await this.request<ChatChannel[]>("/chat/channels");
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** Whether a board is whole, missing a part, or not loaded on the server yet. */
|
|
2
|
+
type BalanceBoardStatus = "available" | "partial" | "unavailable";
|
|
3
|
+
|
|
4
|
+
/** One country's crude or natural gas balance, as GET /cloud/jodi/board serves it. */
|
|
5
|
+
export interface JodiBalanceRow {
|
|
6
|
+
country: string;
|
|
7
|
+
/** The month the figures are for, YYYY-MM: each country's own latest report. Null when it never reported one. */
|
|
8
|
+
month: string | null;
|
|
9
|
+
production: number | null;
|
|
10
|
+
demand: number | null;
|
|
11
|
+
imports: number | null;
|
|
12
|
+
exports: number | null;
|
|
13
|
+
stocks: number | null;
|
|
14
|
+
unit: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface JodiBalanceBoard {
|
|
18
|
+
/** The newest month on the board, YYYY-MM. */
|
|
19
|
+
month: string;
|
|
20
|
+
commodity: "oil" | "gas";
|
|
21
|
+
rows: JodiBalanceRow[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The Oil & Gas board: JODI's monthly crude and natural gas balances. */
|
|
25
|
+
export interface JodiBoardPayload {
|
|
26
|
+
source: "JODI";
|
|
27
|
+
status: BalanceBoardStatus;
|
|
28
|
+
/** When JODI last confirmed the oldest copy on the board, ISO. */
|
|
29
|
+
checkedAt: string | null;
|
|
30
|
+
/** The server has not been able to read JODI for days: these are the last copies it read. */
|
|
31
|
+
stale: boolean;
|
|
32
|
+
gaps: string[];
|
|
33
|
+
oil: JodiBalanceBoard | null;
|
|
34
|
+
gas: JodiBalanceBoard | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** One crop, country and attribute of the latest market year, as GET /cloud/psd/board serves it. */
|
|
38
|
+
export interface PsdBalanceRow {
|
|
39
|
+
id: string;
|
|
40
|
+
crop: string;
|
|
41
|
+
country: string;
|
|
42
|
+
attribute: string;
|
|
43
|
+
value: number;
|
|
44
|
+
unit: string;
|
|
45
|
+
/** Market year, 2026/27. */
|
|
46
|
+
year: string;
|
|
47
|
+
cropOrder: number;
|
|
48
|
+
countryOrder: number;
|
|
49
|
+
attributeOrder: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A season-average farm price forecast. */
|
|
53
|
+
export interface PsdPriceForecast {
|
|
54
|
+
id: string;
|
|
55
|
+
crop: string;
|
|
56
|
+
forecast: number;
|
|
57
|
+
unit: string;
|
|
58
|
+
season: string;
|
|
59
|
+
cropOrder: number;
|
|
60
|
+
seasonYear: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The Crops board: USDA's world crop balances and season-average price forecasts. */
|
|
64
|
+
export interface PsdBoardPayload {
|
|
65
|
+
source: "USDA";
|
|
66
|
+
status: BalanceBoardStatus;
|
|
67
|
+
checkedAt: string | null;
|
|
68
|
+
stale: boolean;
|
|
69
|
+
gaps: string[];
|
|
70
|
+
/** YYYY-MM of the newest estimate on the board: the monthly supply and demand report that set it. */
|
|
71
|
+
month: string | null;
|
|
72
|
+
marketYear: string | null;
|
|
73
|
+
rows: PsdBalanceRow[];
|
|
74
|
+
prices: PsdPriceForecast[];
|
|
75
|
+
/** The model forecast date of the prices, YYYY-MM-DD. */
|
|
76
|
+
pricesDate: string | null;
|
|
77
|
+
}
|
package/src/api-client/data.ts
CHANGED
|
@@ -35,10 +35,12 @@ import type { CloudCurveHistory, CloudCurveId, CloudCurveView, CloudWorldCurves
|
|
|
35
35
|
import type { CdxBoardPayload, CloudCreditBoardParams, SovrBoardPayload } from "./credit-boards";
|
|
36
36
|
import type { ShortVolumePayload, ShortVolumeScope } from "./short-volume";
|
|
37
37
|
import type { BeneficialOwnersPayload, CloudBeneficialOwnersParams } from "./beneficial-owners";
|
|
38
|
+
import type { CloudInsiderSignalsParams, InsiderSignalsPayload } from "./insider-signals";
|
|
38
39
|
import type { SocialMentionDayPosts, SocialMentionsPayload, SocialMentionsRange } from "./social-mentions";
|
|
39
40
|
import type { FuturesContractTermsPayload, FuturesCurveAsOfPayload, FuturesCurvePayload } from "./futures-curve";
|
|
40
41
|
import type { CotBoardPayload, CotContractPayload, CotFamily, CotClass } from "./cot";
|
|
41
42
|
import type { DoeBoardPayload, DoePricesPayload } from "./doe";
|
|
43
|
+
import type { JodiBoardPayload, PsdBoardPayload } from "./commodity-balances";
|
|
42
44
|
import type { CarbonBoard, CarbonHistory, EuropePowerBoard, EuropePowerHistory, GasStorageBoard, GasStorageHistory, GasStorageSeasonal } from "./europe-energy";
|
|
43
45
|
import type { GpuBoardPayload, GpuEventsPayload, GpuHistoryPayload, GpuHistoryQuery } from "./gpu";
|
|
44
46
|
import type { CpiBoardPayload } from "./cpi";
|
|
@@ -81,6 +83,7 @@ import {
|
|
|
81
83
|
cloudExchangeRatePath,
|
|
82
84
|
cloudSec13FPath,
|
|
83
85
|
cloudSecBeneficialOwnersPath,
|
|
86
|
+
cloudSecInsiderSignalsPath,
|
|
84
87
|
cloudSecFilingContentPath,
|
|
85
88
|
cloudSecFilingDocumentsPath,
|
|
86
89
|
cloudSecFilingsPath,
|
|
@@ -143,6 +146,7 @@ import type {
|
|
|
143
146
|
CloudMarketResponse,
|
|
144
147
|
CloudMarketScreenerCategory,
|
|
145
148
|
CloudMarketScreenerPayload,
|
|
149
|
+
CloudMoverSession,
|
|
146
150
|
CloudNewsListResponse,
|
|
147
151
|
CloudNewsPayload,
|
|
148
152
|
CloudSavedSearch,
|
|
@@ -283,8 +287,10 @@ export class CloudDataApi {
|
|
|
283
287
|
return this.request(`/news/article-summary?${new URLSearchParams({ url })}`);
|
|
284
288
|
}
|
|
285
289
|
|
|
286
|
-
|
|
290
|
+
/** `session` is what Gainers and Losers are ranked by (see CloudMoverSession). */
|
|
291
|
+
getMarketMovers(category: "day_gainers" | "day_losers" | "most_actives", count = 25, refresh = false, session?: CloudMoverSession): Promise<CloudMarketResponse<MarketMoversPayload>> {
|
|
287
292
|
const params = new URLSearchParams({ category, count: String(count), mode: refresh ? "refresh" : "cache-first" });
|
|
293
|
+
if (session && category !== "most_actives") params.set("session", session);
|
|
288
294
|
return this.request(`/market/movers?${params}`);
|
|
289
295
|
}
|
|
290
296
|
|
|
@@ -300,12 +306,17 @@ export class CloudDataApi {
|
|
|
300
306
|
return this.requestMarketSymbol("/market/dividends", symbol, exchange);
|
|
301
307
|
}
|
|
302
308
|
|
|
303
|
-
/**
|
|
309
|
+
/**
|
|
310
|
+
* Gainers, losers and most active, or the pre-market, after-hours and gap
|
|
311
|
+
* lists with a `side`. `session` is what Gainers and Losers are ranked by
|
|
312
|
+
* (see CloudMoverSession).
|
|
313
|
+
*/
|
|
304
314
|
async getCloudMarketScreener<Category extends CloudMarketScreenerCategory | CloudSessionMoversCategory>(
|
|
305
315
|
category: Category,
|
|
306
316
|
count = 25,
|
|
307
317
|
mode: "cache-first" | "refresh" = "cache-first",
|
|
308
318
|
side?: CloudSessionMoversSide,
|
|
319
|
+
session?: CloudMoverSession,
|
|
309
320
|
): Promise<CloudMarketResponse<Category extends CloudSessionMoversCategory ? CloudSessionMoversPayload : CloudMarketScreenerPayload>> {
|
|
310
321
|
const requestedCount = Number.isFinite(count) ? Math.round(count) : 25;
|
|
311
322
|
const params = new URLSearchParams({
|
|
@@ -314,6 +325,7 @@ export class CloudDataApi {
|
|
|
314
325
|
mode,
|
|
315
326
|
});
|
|
316
327
|
if (side && isSessionMoversCategory(category)) params.set("side", side);
|
|
328
|
+
if (session && (category === "gainers" || category === "losers")) params.set("session", session);
|
|
317
329
|
return this.request(`/market/screener?${params.toString()}`);
|
|
318
330
|
}
|
|
319
331
|
|
|
@@ -590,6 +602,14 @@ export class CloudDataApi {
|
|
|
590
602
|
return this.request<DoePricesPayload>("/cloud/doe/prices");
|
|
591
603
|
}
|
|
592
604
|
|
|
605
|
+
getCloudJodiBoard(signal?: AbortSignal): Promise<JodiBoardPayload> {
|
|
606
|
+
return this.request<JodiBoardPayload>("/cloud/jodi/board", { signal });
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
getCloudPsdBoard(signal?: AbortSignal): Promise<PsdBoardPayload> {
|
|
610
|
+
return this.request<PsdBoardPayload>("/cloud/psd/board", { signal });
|
|
611
|
+
}
|
|
612
|
+
|
|
593
613
|
async getCloudAttention(window: AttentionWindow = "now", symbol?: string): Promise<AttentionPayload> {
|
|
594
614
|
const path = symbol ? `/cloud/attention/${encodeURIComponent(symbol)}` : "/cloud/attention";
|
|
595
615
|
return this.request<AttentionPayload>(`${path}?window=${window}`);
|
|
@@ -934,6 +954,15 @@ export class CloudDataApi {
|
|
|
934
954
|
});
|
|
935
955
|
}
|
|
936
956
|
|
|
957
|
+
async getCloudSecInsiderSignals(
|
|
958
|
+
params: CloudInsiderSignalsParams = {},
|
|
959
|
+
options?: { signal?: AbortSignal },
|
|
960
|
+
): Promise<InsiderSignalsPayload> {
|
|
961
|
+
return this.request<InsiderSignalsPayload>(cloudSecInsiderSignalsPath(params), {
|
|
962
|
+
signal: options?.signal,
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
937
966
|
async getCloudSecFilingDocuments(
|
|
938
967
|
params: CloudSecFilingParams,
|
|
939
968
|
): Promise<CloudSecDocumentsResponse> {
|
|
@@ -27,6 +27,8 @@ interface EarningsRealizedMove {
|
|
|
27
27
|
afterClose: number;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
type EarningsActualSource = "calendar" | "press-release";
|
|
31
|
+
|
|
30
32
|
export interface EarningsReport {
|
|
31
33
|
symbol: string;
|
|
32
34
|
name: string | null;
|
|
@@ -43,6 +45,12 @@ export interface EarningsReport {
|
|
|
43
45
|
/** For an upcoming report, the consensus every earnings view shows, with `epsAnalysts` behind it. */
|
|
44
46
|
epsEstimate: number | null;
|
|
45
47
|
epsActual: number | null;
|
|
48
|
+
/**
|
|
49
|
+
* Where `epsActual` comes from: `press-release` is the company's own
|
|
50
|
+
* release, read the day it reports and provisional until the calendar's
|
|
51
|
+
* figure replaces it. Absent from older servers.
|
|
52
|
+
*/
|
|
53
|
+
epsActualSource?: EarningsActualSource | null;
|
|
46
54
|
epsAnalysts: number | null;
|
|
47
55
|
revenueEstimate: number | null;
|
|
48
56
|
revenueAnalysts: number | null;
|
|
@@ -69,6 +77,8 @@ export interface EarningsCalendarPayload {
|
|
|
69
77
|
|
|
70
78
|
interface EarningsHistoryReport extends EarningsReport {
|
|
71
79
|
revenueActual: number | null;
|
|
80
|
+
/** `press-release` until the statements have the quarter; absent from older servers. */
|
|
81
|
+
revenueActualSource?: "statements" | "press-release" | null;
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
export interface EarningsHistoryPayload {
|
package/src/api-client/index.ts
CHANGED
|
@@ -645,6 +645,8 @@ class GloomApiClient {
|
|
|
645
645
|
getChannels = this.chat.getChannels.bind(this.chat);
|
|
646
646
|
getChatPresence = this.chat.getPresence.bind(this.chat);
|
|
647
647
|
getChatState = this.chat.getState.bind(this.chat);
|
|
648
|
+
getChatMentionCandidates = this.chat.getMentionCandidates.bind(this.chat);
|
|
649
|
+
subscribeChatMentionCandidatesChanged = this.chat.subscribeMentionCandidatesChanged.bind(this.chat);
|
|
648
650
|
updateChatChannelState = this.chat.updateChannelState.bind(this.chat);
|
|
649
651
|
markChatNotificationsDelivered = this.chat.markNotificationsDelivered.bind(this.chat);
|
|
650
652
|
getUnseenChatNotificationIds = this.chat.getUnseenNotificationIds.bind(this.chat);
|
|
@@ -826,6 +828,8 @@ class GloomApiClient {
|
|
|
826
828
|
getCloudAward = this.data.getCloudAward.bind(this.data);
|
|
827
829
|
getCloudDoeBoard = this.data.getCloudDoeBoard.bind(this.data);
|
|
828
830
|
getCloudDoePrices = this.data.getCloudDoePrices.bind(this.data);
|
|
831
|
+
getCloudJodiBoard = this.data.getCloudJodiBoard.bind(this.data);
|
|
832
|
+
getCloudPsdBoard = this.data.getCloudPsdBoard.bind(this.data);
|
|
829
833
|
getCloudAttention = this.data.getCloudAttention.bind(this.data);
|
|
830
834
|
getCloudPowerBoard = this.data.getCloudPowerBoard.bind(this.data);
|
|
831
835
|
getCloudPowerHistory = this.data.getCloudPowerHistory.bind(this.data);
|
|
@@ -864,6 +868,7 @@ class GloomApiClient {
|
|
|
864
868
|
getRiskReport = this.data.getRiskReport.bind(this.data);
|
|
865
869
|
getCloudSecFilings = this.data.getCloudSecFilings.bind(this.data);
|
|
866
870
|
getCloudSecBeneficialOwners = this.data.getCloudSecBeneficialOwners.bind(this.data);
|
|
871
|
+
getCloudSecInsiderSignals = this.data.getCloudSecInsiderSignals.bind(this.data);
|
|
867
872
|
getCloudSecFilingDocuments = this.data.getCloudSecFilingDocuments.bind(this.data);
|
|
868
873
|
getCloudSecFilingContent = this.data.getCloudSecFilingContent.bind(this.data);
|
|
869
874
|
getCloudSec13F = this.data.getCloudSec13F.bind(this.data);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Companies ranked by recent open-market insider buying, as
|
|
3
|
+
* `GET /cloud/sec/insider-signals` returns them. Only Form 4 purchases (code
|
|
4
|
+
* P) by officers, directors and 10% owners count. Dates are `YYYY-MM-DD`,
|
|
5
|
+
* values USD.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type InsiderSignalRole =
|
|
9
|
+
| "CEO"
|
|
10
|
+
| "CFO"
|
|
11
|
+
| "COO"
|
|
12
|
+
| "President"
|
|
13
|
+
| "Chair"
|
|
14
|
+
| "Officer"
|
|
15
|
+
| "Director"
|
|
16
|
+
| "10% owner";
|
|
17
|
+
|
|
18
|
+
interface InsiderSignalBuyer {
|
|
19
|
+
name: string;
|
|
20
|
+
role: InsiderSignalRole;
|
|
21
|
+
value: number;
|
|
22
|
+
/** Days the insider bought in the window. */
|
|
23
|
+
buys: number;
|
|
24
|
+
lastBuy: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface InsiderSignalRow {
|
|
28
|
+
ticker: string;
|
|
29
|
+
issuerCik: string;
|
|
30
|
+
company: string;
|
|
31
|
+
/** 0 to 100. */
|
|
32
|
+
score: number;
|
|
33
|
+
/** Why the company ranks, strongest first, in plain words. */
|
|
34
|
+
reasons: string[];
|
|
35
|
+
/** Distinct insiders who bought in the window. */
|
|
36
|
+
insiders: number;
|
|
37
|
+
/** Insider buy days in the window. */
|
|
38
|
+
buys: number;
|
|
39
|
+
value: number;
|
|
40
|
+
shares: number;
|
|
41
|
+
firstBuy: string;
|
|
42
|
+
lastBuy: string;
|
|
43
|
+
lastFiled: string;
|
|
44
|
+
signals: {
|
|
45
|
+
clusterInsiders: number;
|
|
46
|
+
repeatBuys: number;
|
|
47
|
+
usualRatio: number | null;
|
|
48
|
+
stakeIncreasePct: number | null;
|
|
49
|
+
newPosition: boolean;
|
|
50
|
+
};
|
|
51
|
+
/** The biggest buyers, up to five. */
|
|
52
|
+
buyers: InsiderSignalBuyer[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface InsiderSignalsPayload {
|
|
56
|
+
/** Days of buying each row covers, ending `through`. */
|
|
57
|
+
window: number;
|
|
58
|
+
through: string;
|
|
59
|
+
/** The newest filing day read. */
|
|
60
|
+
asOf: string | null;
|
|
61
|
+
/**
|
|
62
|
+
* Days whose filings have all been read. `missingDays`: weekdays since
|
|
63
|
+
* `from` the server no longer reads on its own and has not read whole, a gap
|
|
64
|
+
* an outage left (left out by servers that predate it).
|
|
65
|
+
*/
|
|
66
|
+
coverage: { from: string | null; through: string | null; missingDays?: string[] };
|
|
67
|
+
rows: InsiderSignalRow[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface CloudInsiderSignalsParams {
|
|
71
|
+
/** Days, 1 to 90; the server reads 30 when left out. */
|
|
72
|
+
window?: number;
|
|
73
|
+
/** Rows, 1 to 200; the server reads 50 when left out. */
|
|
74
|
+
limit?: number;
|
|
75
|
+
}
|
|
@@ -19,7 +19,7 @@ export interface ScreenerQuote {
|
|
|
19
19
|
lastUpdated?: number;
|
|
20
20
|
/** Session-fixed, so the price column reads its decimals from it rather than from each tick. */
|
|
21
21
|
previousClose?: number;
|
|
22
|
-
/** The quote's sessions beside the live print the
|
|
22
|
+
/** The quote's sessions beside the live print the server ranked the row by, when the server sends them. */
|
|
23
23
|
sessions?: CloudMoverSessionFields;
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -220,6 +220,7 @@ export function normalizeChatChannel(
|
|
|
220
220
|
...channel,
|
|
221
221
|
kind: channel.kind ?? fallbackKind,
|
|
222
222
|
created_at: normalizeTimestamp(channel.created_at),
|
|
223
|
+
...(typeof channel.lastMessageAt === "string" ? { lastMessageAt: normalizeTimestamp(channel.lastMessageAt) } : {}),
|
|
223
224
|
...(channel.dmUser ? { dmUser: normalizeChatUser(channel.dmUser) } : {}),
|
|
224
225
|
...(Array.isArray(channel.members) ? { members: channel.members.map(normalizeChatUser) } : {}),
|
|
225
226
|
};
|
package/src/api-client/paths.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { isUsListingExchange, normalizeSymbol, parsePublicTickerKey, publicTicke
|
|
|
7
7
|
import { nonUsSecListingVenue } from "../utils/sec";
|
|
8
8
|
import type { HistoryRetention } from "../sources/history-retention";
|
|
9
9
|
import type { CloudBeneficialOwnersParams } from "./beneficial-owners";
|
|
10
|
+
import type { CloudInsiderSignalsParams } from "./insider-signals";
|
|
10
11
|
import type { RevenueBreakdownView } from "./revenue-breakdown";
|
|
11
12
|
|
|
12
13
|
export type CloudHistoryParams = {
|
|
@@ -444,6 +445,14 @@ export function cloudSecBeneficialOwnersPath(params: CloudBeneficialOwnersParams
|
|
|
444
445
|
return appendQuery("/cloud/sec/beneficial-owners", search);
|
|
445
446
|
}
|
|
446
447
|
|
|
448
|
+
/** Companies ranked by recent open-market insider buying. */
|
|
449
|
+
export function cloudSecInsiderSignalsPath(params: CloudInsiderSignalsParams = {}): string {
|
|
450
|
+
const search = new URLSearchParams();
|
|
451
|
+
if (params.window != null) search.set("window", String(params.window));
|
|
452
|
+
if (params.limit != null) search.set("limit", String(params.limit));
|
|
453
|
+
return appendQuery("/cloud/sec/insider-signals", search);
|
|
454
|
+
}
|
|
455
|
+
|
|
447
456
|
export function cloudSecFilingDocumentsPath(
|
|
448
457
|
params: CloudSecFilingParams,
|
|
449
458
|
): string {
|
package/src/api-client/socket.ts
CHANGED
|
@@ -109,6 +109,7 @@ export class CloudApiSocket {
|
|
|
109
109
|
private reconnectDelayMs = 1000;
|
|
110
110
|
|
|
111
111
|
private readonly channelListeners = new Map<string, Set<ChannelListener>>();
|
|
112
|
+
private readonly chatMentionCandidatesListeners = new Set<() => void>();
|
|
112
113
|
private readonly chatNotificationListeners =
|
|
113
114
|
new Set<ChatNotificationListener>();
|
|
114
115
|
private readonly chatChannelStateListeners =
|
|
@@ -254,6 +255,11 @@ export class CloudApiSocket {
|
|
|
254
255
|
};
|
|
255
256
|
}
|
|
256
257
|
|
|
258
|
+
subscribeChatMentionCandidatesChanged(listener: () => void): () => void {
|
|
259
|
+
this.chatMentionCandidatesListeners.add(listener);
|
|
260
|
+
return () => { this.chatMentionCandidatesListeners.delete(listener); };
|
|
261
|
+
}
|
|
262
|
+
|
|
257
263
|
/**
|
|
258
264
|
* The reader's read state or mute changed on another device or window. Like
|
|
259
265
|
* notifications, it never opens the socket by itself.
|
|
@@ -631,6 +637,10 @@ export class CloudApiSocket {
|
|
|
631
637
|
return;
|
|
632
638
|
}
|
|
633
639
|
|
|
640
|
+
if (parsed?.type === "chat.mentions_changed") {
|
|
641
|
+
for (const listener of this.chatMentionCandidatesListeners) listener();
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
634
644
|
if (parsed?.type === "chat.channel_state") {
|
|
635
645
|
const state = normalizeChatChannelStateFrame(parsed.data);
|
|
636
646
|
if (state) {
|
package/src/api-client/types.ts
CHANGED
|
@@ -143,6 +143,21 @@ export interface ChatChannel {
|
|
|
143
143
|
teamId?: string;
|
|
144
144
|
dmUser?: ChatUserSummary | null;
|
|
145
145
|
members?: ChatUserSummary[];
|
|
146
|
+
/**
|
|
147
|
+
* When the newest message you can see in a direct or group conversation was
|
|
148
|
+
* sent; null when it has none. Absent from channels of other kinds and from
|
|
149
|
+
* servers that do not report it.
|
|
150
|
+
*/
|
|
151
|
+
lastMessageAt?: string | null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Minimal identities scoped by the server to this channel and existing DMs. */
|
|
155
|
+
export interface ChatMentionCandidate {
|
|
156
|
+
id: string;
|
|
157
|
+
username: string;
|
|
158
|
+
displayName: string | null;
|
|
159
|
+
channelActivityAt: string | null;
|
|
160
|
+
conversationActivityAt: string | null;
|
|
146
161
|
}
|
|
147
162
|
|
|
148
163
|
export interface ChatChannelState {
|
|
@@ -1517,6 +1532,15 @@ export interface CloudMarketBatchPayload<T> {
|
|
|
1517
1532
|
|
|
1518
1533
|
export type CloudMarketScreenerCategory = "gainers" | "losers" | "most-active";
|
|
1519
1534
|
|
|
1535
|
+
/**
|
|
1536
|
+
* What Gloom Cloud selects and ranks Gainers and Losers by: `regular` the last
|
|
1537
|
+
* completed regular session's move, the Chg% a row shows; `extended` the
|
|
1538
|
+
* latest print, after-hours or pre-market included. While the regular session
|
|
1539
|
+
* trades the two are one list. A server that predates the choice ignores it
|
|
1540
|
+
* and ranks by the latest print.
|
|
1541
|
+
*/
|
|
1542
|
+
export type CloudMoverSession = "regular" | "extended";
|
|
1543
|
+
|
|
1520
1544
|
/**
|
|
1521
1545
|
* A movers row's quote sessions, under the quote's field names and meaning:
|
|
1522
1546
|
* the session it was read in, the day its change is dated to, the last
|
|
@@ -1529,7 +1553,7 @@ export type CloudMoverSessionFields = Partial<Pick<Quote,
|
|
|
1529
1553
|
| "preMarketPrice" | "preMarketChange" | "preMarketChangePercent"
|
|
1530
1554
|
| "postMarketPrice" | "postMarketChange" | "postMarketChangePercent">>;
|
|
1531
1555
|
|
|
1532
|
-
/** price, change and changePercent are the live print the
|
|
1556
|
+
/** price, change and changePercent are the live print the server ranks the list by; the session fields say how it reads. */
|
|
1533
1557
|
export interface CloudMarketScreenerItem extends CloudMoverSessionFields {
|
|
1534
1558
|
rank: number;
|
|
1535
1559
|
symbol: string;
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import type { SyncBrokerInstanceResult } from "../../brokers/sync-broker-instance";
|
|
9
9
|
import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
|
|
10
10
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
11
|
-
import { adoptHeldPortfolioCurrencies } from "../../
|
|
11
|
+
import { adoptHeldPortfolioCurrencies } from "../../portfolio/mutations";
|
|
12
12
|
import { saveConfigImmediately } from "../../state/config-save-scheduler";
|
|
13
13
|
import type { AppAction, AppState } from "../../state/app/context";
|
|
14
14
|
import type { AppConfig } from "../../types/config";
|
|
@@ -18,6 +18,7 @@ import { getBrokerInstance } from "../../utils/broker-instances";
|
|
|
18
18
|
export interface AppBrokerImportRuntime {
|
|
19
19
|
importBrokerPositions: (
|
|
20
20
|
instanceId: string,
|
|
21
|
+
/** What the sync reads. It lands on the app's tickers as they are when it commits, whatever was passed. */
|
|
21
22
|
tickerMap?: Map<string, TickerRecord>,
|
|
22
23
|
options?: BrokerImportOptions,
|
|
23
24
|
) => Promise<SyncBrokerInstanceResult>;
|
|
@@ -52,6 +53,13 @@ export function useBrokerImportRuntime({
|
|
|
52
53
|
stateRef: { current: AppState };
|
|
53
54
|
tickerRepository: AppTickerRepositoryPort;
|
|
54
55
|
}): AppBrokerImportRuntime {
|
|
56
|
+
/**
|
|
57
|
+
* Commits a sync: carried onto the app as it is now (a pull or an edit while
|
|
58
|
+
* the broker answered stays) and handed to the app before anything is
|
|
59
|
+
* written. With a wait in between, a pull landing in it would be overwritten
|
|
60
|
+
* by this older copy and pushed from here as an edit. The writes start in
|
|
61
|
+
* the same tick, so the copy of anyone writing after lands on disk after it.
|
|
62
|
+
*/
|
|
55
63
|
const applyBrokerImportResult = useCallback(async (
|
|
56
64
|
instanceId: string,
|
|
57
65
|
result: SyncBrokerInstanceResult,
|
|
@@ -62,31 +70,27 @@ export function useBrokerImportRuntime({
|
|
|
62
70
|
},
|
|
63
71
|
) => {
|
|
64
72
|
throwIfBrokerImportCancelled(options?.signal);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const liveConfig = stateRef.current.config;
|
|
73
|
+
const live = stateRef.current;
|
|
74
|
+
result.rebase(live);
|
|
68
75
|
const nextConfig = options?.callerOwnsConfig
|
|
69
|
-
? (result.config !== baseConfig ? result.config :
|
|
70
|
-
: rebaseBrokerSyncConfig(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
? (result.config !== baseConfig ? result.config : live.config)
|
|
77
|
+
: rebaseBrokerSyncConfig(live.config, baseConfig, result.config, instanceId);
|
|
78
|
+
const configChanged = nextConfig !== live.config;
|
|
79
|
+
|
|
80
|
+
dispatch({ type: "SET_BROKER_ACCOUNTS", instanceId, accounts: result.brokerAccounts });
|
|
81
|
+
if (configChanged) dispatch({ type: "SET_CONFIG", config: nextConfig });
|
|
82
|
+
for (const ticker of [...result.addedTickers, ...result.updatedTickers]) {
|
|
83
|
+
dispatch({ type: "UPDATE_TICKER", ticker });
|
|
77
84
|
}
|
|
85
|
+
await Promise.all([result.commit(), configChanged ? saveConfigImmediately(nextConfig) : undefined]);
|
|
78
86
|
|
|
87
|
+
if (configChanged) pluginRegistry.events.emit("config:changed", { config: nextConfig });
|
|
79
88
|
for (const ticker of result.addedTickers) {
|
|
80
|
-
throwIfBrokerImportCancelled(options?.signal);
|
|
81
89
|
pluginRegistry.events.emit("ticker:added", { symbol: ticker.metadata.ticker, ticker });
|
|
82
90
|
}
|
|
83
|
-
for (const ticker of [...result.addedTickers, ...result.updatedTickers]) {
|
|
84
|
-
throwIfBrokerImportCancelled(options?.signal);
|
|
85
|
-
dispatch({ type: "UPDATE_TICKER", ticker: { ...ticker } });
|
|
86
|
-
}
|
|
87
91
|
|
|
88
92
|
for (const position of result.positions) {
|
|
89
|
-
|
|
93
|
+
if (options?.signal?.aborted) break;
|
|
90
94
|
// Skip market data for broker option symbols; position marks are already available.
|
|
91
95
|
// Position data (markPrice, marketValue, unrealizedPnl) is used directly.
|
|
92
96
|
if (options?.refreshImportedTickers !== false && position.assetCategory !== "OPT") {
|
|
@@ -120,8 +124,6 @@ export function useBrokerImportRuntime({
|
|
|
120
124
|
throwIfBrokerImportCancelled(options?.signal);
|
|
121
125
|
options?.onCommitStart?.();
|
|
122
126
|
commitStarted = true;
|
|
123
|
-
await result.commit();
|
|
124
|
-
throwIfBrokerImportCancelled(options?.signal);
|
|
125
127
|
await applyBrokerImportResult(instanceId, result, baseConfig, {
|
|
126
128
|
...options,
|
|
127
129
|
callerOwnsConfig: options?.config !== undefined,
|
|
@@ -155,7 +157,7 @@ export function useBrokerImportRuntime({
|
|
|
155
157
|
deferPersistence: true,
|
|
156
158
|
onResult: async (result, instance, previousConfig) => {
|
|
157
159
|
if (!getBrokerInstance(stateRef.current.config.brokerInstances, instance.id)) return;
|
|
158
|
-
|
|
160
|
+
// Startup syncs run beside the first pull: each lands on what it left.
|
|
159
161
|
await applyBrokerImportResult(instance.id, result, previousConfig, {
|
|
160
162
|
refreshImportedTickers: false,
|
|
161
163
|
});
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
clearPersistedBrokerAccounts,
|
|
4
4
|
getBrokerAccountCacheSourceKey,
|
|
5
5
|
} from "../../brokers/account-cache";
|
|
6
|
-
import { findPortfolioHeir } from "../../brokers/broker-portfolio-sync";
|
|
6
|
+
import { clearLeftoverBrokerHoldings, findPortfolioHeir, knownBrokerHoldings } from "../../brokers/broker-portfolio-sync";
|
|
7
7
|
import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
|
|
8
8
|
import type { MarketDataCoordinator } from "../../market-data/coordinator";
|
|
9
9
|
import { instrumentFromTicker } from "../../market-data/request-types";
|
|
@@ -216,15 +216,20 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
216
216
|
return [heirId ? { ...portfolio, brokerInstanceId: heirId } : portfolio];
|
|
217
217
|
});
|
|
218
218
|
const nextTickers = new Map(stateRef.current.tickers);
|
|
219
|
+
const nextBrokerInstances = stateRef.current.config.brokerInstances.filter((entry) => entry.id !== instanceId);
|
|
220
|
+
// What older removals and pulls from other devices left behind goes as well.
|
|
221
|
+
const known = knownBrokerHoldings({ portfolios: nextPortfolios, brokerInstances: nextBrokerInstances });
|
|
219
222
|
|
|
220
223
|
for (const ticker of stateRef.current.tickers.values()) {
|
|
221
|
-
|
|
224
|
+
// A position here without a profile came from another device's sync of this one.
|
|
225
|
+
const nextPositions = ticker.metadata.positions.filter((position) => position.brokerInstanceId !== instanceId
|
|
226
|
+
&& !(removedPortfolioIds.has(position.portfolio) && !position.brokerInstanceId));
|
|
222
227
|
const heldPortfolioIds = new Set(nextPositions.map((position) => position.portfolio));
|
|
223
228
|
const nextPortfolioRefs = ticker.metadata.portfolios.filter((portfolioId) =>
|
|
224
229
|
!removedPortfolioIds.has(portfolioId) && (!heirs.has(portfolioId) || heldPortfolioIds.has(portfolioId)));
|
|
225
230
|
const nextBrokerContracts = (ticker.metadata.broker_contracts ?? []).filter((contract) => contract.brokerInstanceId !== instanceId);
|
|
226
231
|
|
|
227
|
-
const
|
|
232
|
+
const removedTicker: TickerRecord = {
|
|
228
233
|
...ticker,
|
|
229
234
|
metadata: {
|
|
230
235
|
...ticker.metadata,
|
|
@@ -233,12 +238,13 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
233
238
|
broker_contracts: nextBrokerContracts,
|
|
234
239
|
},
|
|
235
240
|
};
|
|
241
|
+
const nextTicker = clearLeftoverBrokerHoldings(removedTicker, known) ?? removedTicker;
|
|
236
242
|
|
|
237
243
|
const shouldDeleteTicker =
|
|
238
|
-
|
|
239
|
-
&&
|
|
244
|
+
nextTicker.metadata.positions.length === 0
|
|
245
|
+
&& nextTicker.metadata.portfolios.length === 0
|
|
240
246
|
&& nextTicker.metadata.watchlists.length === 0
|
|
241
|
-
&&
|
|
247
|
+
&& (nextTicker.metadata.broker_contracts ?? []).length === 0
|
|
242
248
|
&& nextTicker.metadata.tags.length === 0
|
|
243
249
|
&& Object.keys(nextTicker.metadata.custom).length === 0;
|
|
244
250
|
|
|
@@ -256,7 +262,7 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
256
262
|
|
|
257
263
|
const nextConfig = {
|
|
258
264
|
...stateRef.current.config,
|
|
259
|
-
brokerInstances:
|
|
265
|
+
brokerInstances: nextBrokerInstances,
|
|
260
266
|
portfolios: nextPortfolios,
|
|
261
267
|
};
|
|
262
268
|
|
|
@@ -68,11 +68,24 @@ export function useAppUpdateRuntime({
|
|
|
68
68
|
runUpdateCommand: () => void;
|
|
69
69
|
} {
|
|
70
70
|
const restartingRef = useRef(false);
|
|
71
|
+
/** The tag of a release whose download found nothing to take; the hourly check does not offer it again. */
|
|
72
|
+
const declinedTagRef = useRef<string | null>(null);
|
|
71
73
|
|
|
72
74
|
const startUpdate = useCallback((release: ReleaseInfo) => {
|
|
73
75
|
dispatch({ type: "SET_UPDATE_PROGRESS", progress: { phase: "downloading", percent: 0 } });
|
|
74
76
|
void performUpdate(release, (progress) => {
|
|
75
77
|
logUpdateFailure(progress);
|
|
78
|
+
if (progress.phase === "done") {
|
|
79
|
+
// The download had nothing to take (that build is the installed one):
|
|
80
|
+
// an answer like a check's, so it passes as a notice like one. The
|
|
81
|
+
// release goes before the progress, so the effect below never starts
|
|
82
|
+
// it again, and the hourly check does not offer it again.
|
|
83
|
+
declinedTagRef.current = release.tagName;
|
|
84
|
+
dispatch({ type: "SET_UPDATE_AVAILABLE", release: null });
|
|
85
|
+
dispatch({ type: "SET_UPDATE_PROGRESS", progress: null });
|
|
86
|
+
dispatch({ type: "SET_UPDATE_NOTICE", notice: progress.message ?? null });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
76
89
|
dispatch({ type: "SET_UPDATE_PROGRESS", progress });
|
|
77
90
|
});
|
|
78
91
|
}, [dispatch]);
|
|
@@ -116,7 +129,7 @@ export function useAppUpdateRuntime({
|
|
|
116
129
|
if (result.kind === "error") updateLog.warn("update check failed", { error: result.error });
|
|
117
130
|
|
|
118
131
|
if (!manual) {
|
|
119
|
-
if (result.kind === "available") {
|
|
132
|
+
if (result.kind === "available" && result.release.tagName !== declinedTagRef.current) {
|
|
120
133
|
dispatch({ type: "SET_UPDATE_AVAILABLE", release: result.release });
|
|
121
134
|
}
|
|
122
135
|
return;
|