gloomberb 0.12.0 → 0.13.2
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 +2 -0
- package/package.json +1 -2
- package/src/api-client/auth.ts +2 -1
- package/src/api-client/index.test.ts +34 -0
- package/src/api-client/index.ts +10 -2
- package/src/api-client/request.ts +4 -1
- package/src/api-client/research-activity.ts +61 -0
- package/src/api-client/types.ts +2 -1
- package/src/cli/commands/plugins.ts +4 -3
- package/src/cli/desktop-pane-shot.ts +79 -10
- package/src/cli/pane-functions/capabilities.ts +76 -23
- package/src/cli/pane-functions/cloud-session.test.ts +54 -0
- package/src/cli/pane-functions/cloud-session.ts +38 -0
- package/src/cli/pane-functions/dom.test.ts +41 -0
- package/src/cli/pane-functions/dom.ts +199 -0
- package/src/cli/pane-functions/index.test.ts +13 -2
- package/src/cli/pane-functions/index.ts +9 -5
- package/src/cli/pane-functions/intraday-shot.test.ts +307 -0
- package/src/cli/pane-functions/intraday-shot.ts +334 -0
- package/src/cli/pane-functions/report.test.ts +29 -1
- package/src/cli/pane-functions/report.ts +68 -7
- package/src/cli/pane-functions/screenshot.ts +195 -42
- package/src/cli/restore-plugins.ts +10 -3
- package/src/components/chart/composite/composite-chart.test.tsx +68 -1
- package/src/components/chart/composite/composite-chart.tsx +87 -21
- package/src/components/chart/composite/rasterizer.ts +33 -0
- package/src/components/chart/composite/renderers.test.ts +51 -4
- package/src/components/chart/composite/scene.test.ts +128 -2
- package/src/components/chart/composite/scene.ts +92 -16
- package/src/components/chart/composite/text-renderer.ts +13 -0
- package/src/components/chart/composite/time-scale.ts +30 -10
- package/src/components/chart/composite/types.ts +26 -0
- package/src/components/command-bar/assist/runtime.ts +38 -30
- package/src/components/command-bar/surface/assist.test.tsx +63 -9
- package/src/components/command-bar/surface/test-harness.tsx +23 -4
- package/src/components/onboarding/account-step/chooser-panel.tsx +2 -2
- package/src/components/onboarding/account-step/qr-panel.tsx +2 -10
- package/src/components/onboarding/onboarding-wizard.test.tsx +38 -12
- package/src/components/onboarding/onboarding-wizard.tsx +49 -14
- package/src/data/config/store/index.test.ts +23 -0
- package/src/data/config/store/node.ts +7 -1
- package/src/data/config/store/normalize.ts +4 -0
- package/src/news/types.ts +1 -0
- package/src/plugins/builtin/cds/client.test.ts +20 -0
- package/src/plugins/builtin/cds/client.ts +13 -3
- package/src/plugins/builtin/cds/headless.test.ts +30 -0
- package/src/plugins/builtin/cds/headless.ts +113 -0
- package/src/plugins/builtin/cds/index.tsx +4 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -1
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chat/controller/channels.ts +2 -1
- package/src/plugins/builtin/chat/controller/index.ts +13 -5
- package/src/plugins/builtin/chat/controller/realtime.ts +24 -0
- package/src/plugins/builtin/chat/controller/session-runtime.ts +76 -8
- package/src/plugins/builtin/chat/controller/state.ts +1 -0
- package/src/plugins/builtin/chat/controller.test.ts +260 -4
- package/src/plugins/builtin/cloud/askg/executor.test.ts +160 -0
- package/src/plugins/builtin/cloud/askg/executor.ts +429 -0
- package/src/plugins/builtin/cloud/askg/manifest.test.ts +153 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +314 -0
- package/src/plugins/builtin/cloud/askg/protocol.test.ts +190 -0
- package/src/plugins/builtin/cloud/askg/protocol.ts +286 -0
- package/src/plugins/builtin/cloud/askg/undo.test.ts +44 -0
- package/src/plugins/builtin/cloud/askg/undo.ts +277 -0
- package/src/plugins/builtin/cloud/askg/verify.ts +130 -0
- package/src/plugins/builtin/cloud/auth-dialog.tsx +7 -1
- package/src/plugins/builtin/cloud/device-signin-dialog.tsx +21 -8
- package/src/plugins/builtin/cloud/plugin.tsx +2 -0
- package/src/plugins/builtin/cloud/verification-panel.tsx +56 -0
- package/src/plugins/builtin/congress-trades/client.ts +11 -0
- package/src/plugins/builtin/congress-trades/detail.tsx +3 -3
- package/src/plugins/builtin/congress-trades/headless.test.ts +98 -0
- package/src/plugins/builtin/congress-trades/headless.ts +145 -0
- package/src/plugins/builtin/congress-trades/pane.tsx +3 -3
- package/src/plugins/builtin/credit-conditions/client.ts +1 -1
- package/src/plugins/builtin/credit-conditions/headless.test.ts +31 -0
- package/src/plugins/builtin/credit-conditions/headless.ts +55 -0
- package/src/plugins/builtin/credit-conditions/index.tsx +4 -0
- package/src/plugins/builtin/dividend-yield/headless.test.ts +88 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +135 -0
- package/src/plugins/builtin/dividend-yield/index.tsx +12 -8
- package/src/plugins/builtin/dividend-yield/model.ts +4 -22
- package/src/plugins/builtin/dividend-yield/view.ts +17 -0
- package/src/plugins/builtin/earnings/headless.test.ts +82 -0
- package/src/plugins/builtin/earnings/headless.ts +162 -0
- package/src/plugins/builtin/earnings/index.tsx +2 -0
- package/src/plugins/builtin/earnings-calls/data.ts +29 -8
- package/src/plugins/builtin/earnings-calls/headless.test.ts +192 -0
- package/src/plugins/builtin/earnings-calls/headless.ts +300 -0
- package/src/plugins/builtin/earnings-calls/index.tsx +19 -9
- package/src/plugins/builtin/earnings-calls/model.ts +238 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +4 -0
- package/src/plugins/builtin/earnings-calls/transcript-view.tsx +13 -11
- package/src/plugins/builtin/fear-greed/headless.test.ts +53 -0
- package/src/plugins/builtin/fear-greed/headless.ts +84 -0
- package/src/plugins/builtin/fear-greed/index.tsx +4 -0
- package/src/plugins/builtin/holders/client.ts +29 -0
- package/src/plugins/builtin/holders/headless.test.ts +58 -0
- package/src/plugins/builtin/holders/headless.ts +162 -0
- package/src/plugins/builtin/holders/index.tsx +14 -8
- package/src/plugins/builtin/holders/pane.tsx +7 -1
- package/src/plugins/builtin/insider/client.ts +50 -0
- package/src/plugins/builtin/insider/headless.test.ts +122 -0
- package/src/plugins/builtin/insider/headless.ts +117 -0
- package/src/plugins/builtin/insider/index.tsx +21 -50
- package/src/plugins/builtin/insider/insider-data.ts +1 -1
- package/src/plugins/builtin/insider/model.ts +72 -0
- package/src/plugins/builtin/ipo-calendar/client.ts +15 -0
- package/src/plugins/builtin/ipo-calendar/headless.test.ts +89 -0
- package/src/plugins/builtin/ipo-calendar/headless.ts +127 -0
- package/src/plugins/builtin/ipo-calendar/index.tsx +2 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +3 -15
- package/src/plugins/builtin/market-movers/client.ts +76 -0
- package/src/plugins/builtin/market-movers/headless.test.ts +34 -0
- package/src/plugins/builtin/market-movers/headless.ts +73 -0
- package/src/plugins/builtin/market-movers/index.tsx +11 -51
- package/src/plugins/builtin/news/headless.test.ts +138 -0
- package/src/plugins/builtin/news/headless.ts +190 -0
- package/src/plugins/builtin/news/index.tsx +13 -9
- package/src/plugins/builtin/news/wire/index.tsx +2 -1
- package/src/plugins/builtin/research/analyst-headless.test.ts +60 -0
- package/src/plugins/builtin/research/analyst-headless.ts +172 -0
- package/src/plugins/builtin/research/analyst-model.ts +219 -0
- package/src/plugins/builtin/research/analyst-pane.tsx +31 -221
- package/src/plugins/builtin/research/client.ts +12 -0
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -237
- package/src/plugins/builtin/research/event-model.ts +245 -0
- package/src/plugins/builtin/research/events-headless.test.ts +32 -0
- package/src/plugins/builtin/research/events-headless.ts +87 -0
- package/src/plugins/builtin/research/headless.test.ts +93 -0
- package/src/plugins/builtin/research/headless.ts +161 -0
- package/src/plugins/builtin/research/index.tsx +32 -18
- package/src/plugins/builtin/research-search/pane.tsx +4 -0
- package/src/plugins/builtin/scanner/headless.test.ts +59 -0
- package/src/plugins/builtin/scanner/headless.ts +125 -0
- package/src/plugins/builtin/scanner/index.ts +4 -0
- package/src/plugins/builtin/sec/client.ts +16 -0
- package/src/plugins/builtin/sec/headless.test.ts +72 -0
- package/src/plugins/builtin/sec/headless.ts +69 -0
- package/src/plugins/builtin/sec/index.tsx +20 -75
- package/src/plugins/builtin/sec/model.ts +89 -0
- package/src/plugins/builtin/sectors/client.ts +54 -0
- package/src/plugins/builtin/sectors/headless.test.ts +28 -0
- package/src/plugins/builtin/sectors/headless.ts +80 -0
- package/src/plugins/builtin/sectors/index.tsx +6 -51
- package/src/plugins/builtin/shared/cloud-upgrade.ts +8 -4
- package/src/plugins/builtin/short-interest/client.ts +5 -2
- package/src/plugins/builtin/short-interest/headless.test.ts +96 -0
- package/src/plugins/builtin/short-interest/headless.ts +111 -0
- package/src/plugins/builtin/short-interest/index.tsx +15 -9
- package/src/plugins/builtin/thirteenf/data.ts +13 -11
- package/src/plugins/builtin/thirteenf/headless.test.ts +112 -0
- package/src/plugins/builtin/thirteenf/headless.ts +286 -0
- package/src/plugins/builtin/thirteenf/index.tsx +4 -0
- package/src/plugins/builtin/thirteenf/model.ts +13 -0
- package/src/plugins/builtin/thirteenf/table.ts +3 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +14 -0
- package/src/plugins/builtin/treasury-auctions/headless.test.ts +34 -0
- package/src/plugins/builtin/treasury-auctions/headless.ts +95 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +4 -0
- package/src/plugins/builtin/volatility/client.ts +16 -3
- package/src/plugins/builtin/volatility/headless.test.ts +35 -0
- package/src/plugins/builtin/volatility/headless.ts +75 -0
- package/src/plugins/builtin/volatility/index.tsx +4 -0
- package/src/plugins/builtin/world-indices/client.ts +43 -0
- package/src/plugins/builtin/world-indices/headless.test.ts +32 -0
- package/src/plugins/builtin/world-indices/headless.ts +109 -0
- package/src/plugins/builtin/world-indices/index.tsx +4 -0
- package/src/plugins/builtin/yield-curve/headless.test.ts +24 -0
- package/src/plugins/builtin/yield-curve/headless.ts +67 -0
- package/src/plugins/builtin/yield-curve/index.tsx +6 -8
- package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -6
- package/src/plugins/bundle.test.ts +58 -1
- package/src/plugins/bundle.ts +4 -2
- package/src/plugins/catalog-backend.test.ts +26 -0
- package/src/plugins/catalog-backend.ts +9 -2
- package/src/plugins/catalog-browser.test.ts +0 -13
- package/src/plugins/catalog-browser.ts +4 -0
- package/src/plugins/catalog-manifest.test.ts +15 -0
- package/src/plugins/catalog-manifest.ts +12 -5
- package/src/plugins/catalog-ui.ts +0 -6
- package/src/plugins/host-contract.ts +9 -0
- package/src/plugins/host-modules.ts +12 -0
- package/src/plugins/loader.ts +63 -15
- package/src/plugins/prediction-markets/headless.test.ts +119 -0
- package/src/plugins/prediction-markets/headless.ts +255 -0
- package/src/plugins/prediction-markets/index.tsx +2 -0
- package/src/plugins/seed.ts +3 -0
- package/src/public/broker.ts +6 -0
- package/src/remote/schema.test.ts +65 -0
- package/src/remote/schema.ts +322 -39
- package/src/remote/types.ts +49 -2
- package/src/renderers/browser/app-services.ts +3 -0
- package/src/renderers/browser/config-host.ts +24 -8
- package/src/renderers/browser/deeplink-bridge.test.ts +11 -0
- package/src/renderers/browser/deeplink-bridge.ts +15 -3
- package/src/renderers/browser/main.tsx +3 -1
- package/src/renderers/browser/research-entry.test.ts +8 -0
- package/src/renderers/browser/research-entry.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +2 -2
- package/src/renderers/electrobun/bun/desktop/initialization.ts +10 -3
- package/src/renderers/electrobun/bun/external-plugins.ts +20 -4
- package/src/renderers/electrobun/view/build-assets.ts +0 -2
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +63 -10
- package/src/sources/gloomberb-cloud/news.ts +1 -0
- package/src/sync/core-contributors.test.ts +111 -0
- package/src/sync/core-contributors.ts +155 -1
- package/src/time-series/resolve.test.ts +118 -1
- package/src/time-series/resolve.ts +14 -16
- package/src/types/config.ts +2 -0
- package/src/plugins/broker-sync/broker-sync.test.ts +0 -188
- package/src/plugins/broker-sync/native-loader.ts +0 -10
- package/src/plugins/broker-sync/normalize.ts +0 -308
- package/src/plugins/broker-sync/public.ts +0 -122
- package/src/plugins/broker-sync/robinhood-native.ts +0 -367
- package/src/plugins/broker-sync/robinhood.ts +0 -114
- package/src/plugins/broker-sync/simplefin-native.ts +0 -201
- package/src/plugins/broker-sync/simplefin.ts +0 -70
- package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +0 -7
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
formatShares,
|
|
13
13
|
formatShortDate,
|
|
14
14
|
} from "./format";
|
|
15
|
+
import { positionType } from "./model";
|
|
15
16
|
import type {
|
|
16
17
|
FilingPositionColumn,
|
|
17
18
|
FilingPositionRow,
|
|
@@ -82,7 +83,7 @@ export function renderHoldingCell(
|
|
|
82
83
|
attributes: TextAttributes.BOLD,
|
|
83
84
|
};
|
|
84
85
|
case "type":
|
|
85
|
-
return { text:
|
|
86
|
+
return { text: positionType(row), color: selectedColor ?? colors.textDim };
|
|
86
87
|
case "issuer":
|
|
87
88
|
return { text: row.issuer, color: selectedColor ?? colors.text };
|
|
88
89
|
case "value":
|
|
@@ -136,7 +137,7 @@ export function renderFilingPositionCell(
|
|
|
136
137
|
attributes: TextAttributes.BOLD,
|
|
137
138
|
};
|
|
138
139
|
case "type":
|
|
139
|
-
return { text:
|
|
140
|
+
return { text: positionType(row), color: selectedColor ?? colors.textDim };
|
|
140
141
|
case "issuer":
|
|
141
142
|
return { text: row.issuer, color: selectedColor ?? colors.text };
|
|
142
143
|
case "value":
|
|
@@ -185,19 +186,6 @@ export function renderTimelineCell(
|
|
|
185
186
|
}
|
|
186
187
|
}
|
|
187
188
|
|
|
188
|
-
function formatPositionType(row: {
|
|
189
|
-
putCall: string;
|
|
190
|
-
titleOfClass: string;
|
|
191
|
-
shareType: string;
|
|
192
|
-
}): string {
|
|
193
|
-
const putCall = row.putCall.trim().toUpperCase();
|
|
194
|
-
if (putCall === "PUT" || putCall === "CALL") return putCall;
|
|
195
|
-
const title = row.titleOfClass.trim().toUpperCase();
|
|
196
|
-
if (title) return title;
|
|
197
|
-
const shareType = row.shareType.trim().toUpperCase();
|
|
198
|
-
return shareType || "--";
|
|
199
|
-
}
|
|
200
|
-
|
|
201
189
|
function actionColor(action: FundHoldingRow["action"]): string {
|
|
202
190
|
switch (action) {
|
|
203
191
|
case "new":
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Box } from "../../../ui";
|
|
2
|
+
import { getCurrentPluginTarget } from "../../current-target";
|
|
3
|
+
import { recordResearchActivity } from "../../../api-client/research-activity";
|
|
2
4
|
import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from "react";
|
|
3
5
|
import type { PaneProps, TickerResearchTabDef } from "../../../types/plugin";
|
|
4
6
|
import { t, tf } from "../../../i18n";
|
|
@@ -98,7 +100,19 @@ export function TickerResearchPane({ focused, width, height }: PaneProps) {
|
|
|
98
100
|
TICKER_RESEARCH_TAB_COMMIT_DELAY_MS,
|
|
99
101
|
{ commitPendingOnUnmount: true },
|
|
100
102
|
);
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (!focused || !ticker || getCurrentPluginTarget() !== "web") return;
|
|
105
|
+
const url = new URL(window.location.href);
|
|
106
|
+
url.searchParams.set("ticker", ticker.metadata.ticker);
|
|
107
|
+
url.searchParams.set("tab", activeTabId);
|
|
108
|
+
window.history.replaceState(window.history.state, "", url.href);
|
|
109
|
+
}, [focused, ticker?.metadata.ticker, activeTabId]);
|
|
101
110
|
const [pluginCaptured, setPluginCaptured] = useState(false);
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (focused && Number.isFinite(financials?.quote?.price) && (financials?.quote?.price ?? 0) > 0) {
|
|
113
|
+
recordResearchActivity("research_viewed", "research");
|
|
114
|
+
}
|
|
115
|
+
}, [focused, financials?.quote?.price]);
|
|
102
116
|
const [mountedTabIds, setMountedTabIds] = useState<Set<string>>(() => new Set());
|
|
103
117
|
const paneSettings = getTickerResearchPaneSettings(paneInstance?.settings);
|
|
104
118
|
const hasOptionsChain = !!resolveOptionsTarget(ticker)?.effectiveTicker;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
|
|
3
|
+
import { createTreasuryAuctionsHeadless } from "./headless";
|
|
4
|
+
import type { TreasuryAuction } from "./types";
|
|
5
|
+
|
|
6
|
+
const bill: TreasuryAuction = {
|
|
7
|
+
id: "bill", cusip: "A", secType: "Bill", securityTerm: "4-Week", auctionDate: "2026-09-03",
|
|
8
|
+
highInvestmentRate: 4.1, highYield: null, avgMedYield: null, highPrice: null, lowPrice: null,
|
|
9
|
+
avgMedPrice: null, bidToCoverRatio: 2.8, competitiveAccepted: 80, indirectAccepted: 20,
|
|
10
|
+
primaryDealerAccepted: 10, totalAccepted: 100, offeringAmount: 100,
|
|
11
|
+
};
|
|
12
|
+
const bond: TreasuryAuction = { ...bill, id: "bond", cusip: "B", secType: "Bond", securityTerm: "30-Year" };
|
|
13
|
+
|
|
14
|
+
function args(filter: string): HeadlessPaneLoadArgs {
|
|
15
|
+
return { rawArgument: "", argument: null, symbols: [], options: { historyDays: "90", filter } };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("treasury auctions headless model", () => {
|
|
19
|
+
test("applies history and security filters to derived auction rows", async () => {
|
|
20
|
+
const requested: number[] = [];
|
|
21
|
+
const headless = createTreasuryAuctionsHeadless({
|
|
22
|
+
load: async (_args, historyDays) => {
|
|
23
|
+
requested.push(historyDays);
|
|
24
|
+
return { auctions: [bill, bond], fetchedAt: 123, stale: false };
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const all = await headless.load(args("all"), {} as HeadlessPaneContext);
|
|
29
|
+
const bonds = await headless.load(args("bond"), {} as HeadlessPaneContext);
|
|
30
|
+
expect(all.rows).toHaveLength(2);
|
|
31
|
+
expect(bonds.rows).toEqual([expect.objectContaining({ id: "bond", rate: 4.1, indirectPercent: 20 })]);
|
|
32
|
+
expect(requested).toEqual([90, 90]);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HeadlessPaneDefinition,
|
|
3
|
+
HeadlessPaneLoadArgs,
|
|
4
|
+
} from "../../../types/plugin";
|
|
5
|
+
import { formatCompact } from "../../../utils/format";
|
|
6
|
+
import { fetchTreasuryAuctions } from "./client";
|
|
7
|
+
import { loadTreasuryAuctions, type TreasuryAuctionsResult } from "./cache";
|
|
8
|
+
import {
|
|
9
|
+
auctionSize,
|
|
10
|
+
DEFAULT_AUCTION_SORT,
|
|
11
|
+
indirectPct,
|
|
12
|
+
rateValue,
|
|
13
|
+
visibleAuctions,
|
|
14
|
+
type AuctionFilter,
|
|
15
|
+
} from "./model";
|
|
16
|
+
|
|
17
|
+
const COLUMNS = [
|
|
18
|
+
{ key: "auctionDate", header: "Date" },
|
|
19
|
+
{ key: "secType", header: "Type" },
|
|
20
|
+
{ key: "securityTerm", header: "Term" },
|
|
21
|
+
{ key: "rate", header: "Rate", align: "right" as const, format: (value: unknown) => value == null ? "-" : `${Number(value).toFixed(3)}%` },
|
|
22
|
+
{ key: "bidToCoverRatio", header: "B/C", align: "right" as const, format: (value: unknown) => value == null ? "-" : Number(value).toFixed(2) },
|
|
23
|
+
{ key: "indirectPercent", header: "Indirect", align: "right" as const, format: (value: unknown) => value == null ? "-" : `${Number(value).toFixed(1)}%` },
|
|
24
|
+
{ key: "size", header: "Size", align: "right" as const, format: (value: unknown) => value == null ? "-" : `$${formatCompact(Number(value))}` },
|
|
25
|
+
{ key: "cusip", header: "CUSIP" },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export interface TreasuryAuctionsHeadlessDependencies {
|
|
29
|
+
load(args: HeadlessPaneLoadArgs, historyDays: number): Promise<TreasuryAuctionsResult>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const defaultDependencies: TreasuryAuctionsHeadlessDependencies = {
|
|
33
|
+
load: (_args, historyDays) => loadTreasuryAuctions(false, fetchTreasuryAuctions, historyDays),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function createTreasuryAuctionsHeadless(
|
|
37
|
+
dependencies: TreasuryAuctionsHeadlessDependencies = defaultDependencies,
|
|
38
|
+
): HeadlessPaneDefinition<"rows"> {
|
|
39
|
+
return {
|
|
40
|
+
shape: "rows",
|
|
41
|
+
argument: {
|
|
42
|
+
kind: "free-text",
|
|
43
|
+
placeholder: "search",
|
|
44
|
+
description: "Optional security type, term, or auction date filter.",
|
|
45
|
+
optional: true,
|
|
46
|
+
},
|
|
47
|
+
options: [
|
|
48
|
+
{
|
|
49
|
+
key: "historyDays",
|
|
50
|
+
aliases: ["history"],
|
|
51
|
+
settingKey: "historyDays",
|
|
52
|
+
description: "Auction history window in days.",
|
|
53
|
+
type: "enum",
|
|
54
|
+
values: [{ value: "30" }, { value: "90" }, { value: "120" }, { value: "365" }],
|
|
55
|
+
defaultValue: "120",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
key: "filter",
|
|
59
|
+
description: "Security type group.",
|
|
60
|
+
type: "enum",
|
|
61
|
+
values: [{ value: "all" }, { value: "bill" }, { value: "note" }, { value: "bond" }],
|
|
62
|
+
defaultValue: "all",
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
columns: COLUMNS,
|
|
66
|
+
describe: (args) => `Treasury Auctions | ${String(args.options.historyDays)} days | ${String(args.options.filter)}`,
|
|
67
|
+
async load(args) {
|
|
68
|
+
const historyDays = Number(args.options.historyDays);
|
|
69
|
+
const filter = args.options.filter as AuctionFilter;
|
|
70
|
+
const result = await dependencies.load(args, historyDays);
|
|
71
|
+
const query = typeof args.argument === "string" ? args.argument : "";
|
|
72
|
+
const auctions = visibleAuctions(result.auctions, {
|
|
73
|
+
filter,
|
|
74
|
+
query,
|
|
75
|
+
sort: DEFAULT_AUCTION_SORT,
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
rows: auctions.map((auction) => ({
|
|
79
|
+
...auction,
|
|
80
|
+
rate: rateValue(auction),
|
|
81
|
+
indirectPercent: indirectPct(auction),
|
|
82
|
+
size: auctionSize(auction),
|
|
83
|
+
})),
|
|
84
|
+
metadata: {
|
|
85
|
+
fetchedAt: result.fetchedAt,
|
|
86
|
+
stale: result.stale,
|
|
87
|
+
historyDays,
|
|
88
|
+
filter,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const treasuryAuctionsHeadless = createTreasuryAuctionsHeadless();
|
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
import { TreasuryAuctionsPane } from "./pane";
|
|
9
9
|
import { TREASURY_AUCTIONS_PANE_ID } from "./types";
|
|
10
10
|
import { createPublicPaneShare } from "../shared/public-pane";
|
|
11
|
+
import { treasuryAuctionsHeadless } from "./headless";
|
|
12
|
+
|
|
13
|
+
export { treasuryAuctionsHeadless } from "./headless";
|
|
11
14
|
|
|
12
15
|
let disposeConnection: (() => void) | null = null;
|
|
13
16
|
|
|
@@ -86,6 +89,7 @@ export const treasuryAuctionsModule: PluginModule = {
|
|
|
86
89
|
"issuance",
|
|
87
90
|
],
|
|
88
91
|
shortcut: { prefix: "AUCT" },
|
|
92
|
+
headless: treasuryAuctionsHeadless,
|
|
89
93
|
createInstance: () => ({ placement: "floating" }),
|
|
90
94
|
publicShare: createPublicPaneShare("Treasury Auctions"),
|
|
91
95
|
},
|
|
@@ -21,6 +21,11 @@ export interface VolatilityLoadResult {
|
|
|
21
21
|
|
|
22
22
|
const HISTORY_LIMIT = 120;
|
|
23
23
|
|
|
24
|
+
export type VolatilitySeriesLoader = (
|
|
25
|
+
seriesId: VolatilitySeriesId,
|
|
26
|
+
options: { limit?: number; sortOrder?: "asc" | "desc" },
|
|
27
|
+
) => Promise<VolatilitySeriesInput>;
|
|
28
|
+
|
|
24
29
|
function requestFor(seriesId: VolatilitySeriesId): FredSeriesRequest {
|
|
25
30
|
return { seriesId, limit: HISTORY_LIMIT, sortOrder: "desc" };
|
|
26
31
|
}
|
|
@@ -48,11 +53,12 @@ export function getCachedVolatilityData(): VolatilityLoadResult | null {
|
|
|
48
53
|
async function loadSeries(
|
|
49
54
|
seriesId: VolatilitySeriesId,
|
|
50
55
|
force: boolean,
|
|
56
|
+
loader: VolatilitySeriesLoader,
|
|
51
57
|
): Promise<FredSeriesLoadResult> {
|
|
52
58
|
const request = requestFor(seriesId);
|
|
53
59
|
return loadCachedFredSeries(
|
|
54
60
|
request,
|
|
55
|
-
async () => toInput({ data: await
|
|
61
|
+
async () => toInput({ data: await loader(seriesId, {
|
|
56
62
|
limit: request.limit,
|
|
57
63
|
sortOrder: request.sortOrder,
|
|
58
64
|
}) }),
|
|
@@ -60,9 +66,16 @@ async function loadSeries(
|
|
|
60
66
|
);
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
|
|
69
|
+
const defaultSeriesLoader: VolatilitySeriesLoader = (seriesId, options) => (
|
|
70
|
+
apiClient.getCloudFredSeries(seriesId, options)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
export async function loadVolatilityData(
|
|
74
|
+
force = false,
|
|
75
|
+
loader: VolatilitySeriesLoader = defaultSeriesLoader,
|
|
76
|
+
): Promise<VolatilityLoadResult> {
|
|
64
77
|
const settled = await Promise.allSettled(
|
|
65
|
-
VOLATILITY_SERIES.map(({ seriesId }) => loadSeries(seriesId, force)),
|
|
78
|
+
VOLATILITY_SERIES.map(({ seriesId }) => loadSeries(seriesId, force, loader)),
|
|
66
79
|
);
|
|
67
80
|
const inputs: Partial<Record<VolatilitySeriesId, VolatilitySeriesInput>> = {};
|
|
68
81
|
const errors: string[] = [];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
|
|
3
|
+
import { createVolatilityHeadless } from "./headless";
|
|
4
|
+
|
|
5
|
+
const args: HeadlessPaneLoadArgs = { rawArgument: "", argument: null, symbols: [], options: {} };
|
|
6
|
+
|
|
7
|
+
describe("volatility headless model", () => {
|
|
8
|
+
test("projects aligned tenors and the derived curve state", async () => {
|
|
9
|
+
const headless = createVolatilityHeadless({
|
|
10
|
+
load: async () => ({
|
|
11
|
+
data: {
|
|
12
|
+
metrics: [
|
|
13
|
+
{ seriesId: "VIXCLS", label: "VIX", tenor: "30D", title: "VIX", value: 18, date: "2026-09-03", history: [] },
|
|
14
|
+
{ seriesId: "VXVCLS", label: "VIX 3M", tenor: "3M", title: "VIX 3M", value: 21, date: "2026-09-03", history: [] },
|
|
15
|
+
],
|
|
16
|
+
termPoints: [],
|
|
17
|
+
termDate: "2026-09-03",
|
|
18
|
+
ratio: 21 / 18,
|
|
19
|
+
slope: 3,
|
|
20
|
+
ratioHistory: [],
|
|
21
|
+
termState: "normal",
|
|
22
|
+
},
|
|
23
|
+
stale: false,
|
|
24
|
+
errors: [],
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
const result = await headless.load(args, {} as HeadlessPaneContext);
|
|
28
|
+
|
|
29
|
+
expect(result.sections[0]?.entries?.slice(0, 2)).toEqual([
|
|
30
|
+
{ label: "State", value: "normal" },
|
|
31
|
+
{ label: "As of", value: "2026-09-03" },
|
|
32
|
+
]);
|
|
33
|
+
expect(result.sections[1]?.rows?.[0]).toMatchObject({ label: "VIX", value: 18 });
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HeadlessBundleResult,
|
|
3
|
+
HeadlessPaneDefinition,
|
|
4
|
+
HeadlessPaneLoadArgs,
|
|
5
|
+
} from "../../../types/plugin";
|
|
6
|
+
import {
|
|
7
|
+
loadVolatilityData,
|
|
8
|
+
type VolatilityLoadResult,
|
|
9
|
+
type VolatilitySeriesLoader,
|
|
10
|
+
} from "./client";
|
|
11
|
+
import type { VolatilityData } from "./model";
|
|
12
|
+
|
|
13
|
+
function formattedValue(value: number | null, digits = 2): string {
|
|
14
|
+
return value == null ? "-" : value.toFixed(digits);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function projectVolatilityHeadless(data: VolatilityData): HeadlessBundleResult {
|
|
18
|
+
return {
|
|
19
|
+
sections: [
|
|
20
|
+
{
|
|
21
|
+
title: "Term structure",
|
|
22
|
+
entries: [
|
|
23
|
+
{ label: "State", value: data.termState },
|
|
24
|
+
{ label: "As of", value: data.termDate },
|
|
25
|
+
{ label: "3M / 30D", value: data.ratio, formatted: formattedValue(data.ratio) },
|
|
26
|
+
{ label: "3M spread", value: data.slope, formatted: data.slope == null ? "-" : `${data.slope > 0 ? "+" : ""}${data.slope.toFixed(2)} pts` },
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
title: "Volatility tenors",
|
|
31
|
+
columns: [
|
|
32
|
+
{ key: "label", header: "Index" },
|
|
33
|
+
{ key: "tenor", header: "Tenor" },
|
|
34
|
+
{ key: "value", header: "Close", align: "right", format: (value) => formattedValue(value as number | null) },
|
|
35
|
+
{ key: "date", header: "As of" },
|
|
36
|
+
{ key: "title", header: "Series" },
|
|
37
|
+
],
|
|
38
|
+
rows: data.metrics.map((metric) => ({ ...metric })),
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface VolatilityHeadlessDependencies {
|
|
45
|
+
load(args: HeadlessPaneLoadArgs, loader: VolatilitySeriesLoader): Promise<VolatilityLoadResult>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const defaultDependencies: VolatilityHeadlessDependencies = {
|
|
49
|
+
load: (_args, loader) => loadVolatilityData(false, loader),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function createVolatilityHeadless(
|
|
53
|
+
dependencies: VolatilityHeadlessDependencies = defaultDependencies,
|
|
54
|
+
): HeadlessPaneDefinition<"bundle"> {
|
|
55
|
+
return {
|
|
56
|
+
shape: "bundle",
|
|
57
|
+
argument: { kind: "none" },
|
|
58
|
+
options: [],
|
|
59
|
+
describe: "VIX 30D/3M Curve",
|
|
60
|
+
async load(args, ctx) {
|
|
61
|
+
const result = await dependencies.load(
|
|
62
|
+
args,
|
|
63
|
+
(seriesId, options) => ctx.apiClient.getCloudFredSeries(seriesId, options),
|
|
64
|
+
);
|
|
65
|
+
const projected = projectVolatilityHeadless(result.data);
|
|
66
|
+
return {
|
|
67
|
+
...projected,
|
|
68
|
+
errors: result.errors,
|
|
69
|
+
metadata: { stale: result.stale },
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const volatilityHeadless = createVolatilityHeadless();
|
|
@@ -18,6 +18,9 @@ import { colors } from "../../../theme/colors";
|
|
|
18
18
|
import type { PluginModule } from "../plugin-module";
|
|
19
19
|
import { getCachedVolatilityData, loadVolatilityData } from "./client";
|
|
20
20
|
import type { TermState, VolatilityData } from "./model";
|
|
21
|
+
import { volatilityHeadless } from "./headless";
|
|
22
|
+
|
|
23
|
+
export { volatilityHeadless } from "./headless";
|
|
21
24
|
|
|
22
25
|
function formatValue(value: number | null, suffix = ""): string {
|
|
23
26
|
return value == null || !Number.isFinite(value) ? "--" : `${value.toFixed(2)}${suffix}`;
|
|
@@ -189,5 +192,6 @@ export const volatilityModule: PluginModule = {
|
|
|
189
192
|
description: "Aligned daily 30-day and three-month VIX implied-volatility closes from FRED.",
|
|
190
193
|
keywords: ["vix", "volatility", "implied volatility", "curve", "slope", "normal", "inverted", "macro"],
|
|
191
194
|
shortcut: { prefix: "VIX" },
|
|
195
|
+
headless: volatilityHeadless,
|
|
192
196
|
}],
|
|
193
197
|
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { DataProvider } from "../../../types/data-provider";
|
|
2
|
+
import type { Quote } from "../../../types/financials";
|
|
3
|
+
import type { IndexEntry } from "./indices";
|
|
4
|
+
|
|
5
|
+
export interface WorldIndexQuoteResult {
|
|
6
|
+
quotes: Map<string, Quote | null>;
|
|
7
|
+
errors: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function errorMessage(error: unknown): string {
|
|
11
|
+
return error instanceof Error ? error.message : String(error);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function loadWorldIndexQuotes(
|
|
15
|
+
entries: readonly IndexEntry[],
|
|
16
|
+
provider: DataProvider,
|
|
17
|
+
): Promise<WorldIndexQuoteResult> {
|
|
18
|
+
const quotes = new Map<string, Quote | null>();
|
|
19
|
+
const errors: string[] = [];
|
|
20
|
+
if (provider.getQuotesBatch) {
|
|
21
|
+
const results = await provider.getQuotesBatch(
|
|
22
|
+
entries.map((entry) => ({ symbol: entry.symbol, exchange: "" })),
|
|
23
|
+
);
|
|
24
|
+
const bySymbol = new Map(results.map((result) => [result.target.symbol, result]));
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
const result = bySymbol.get(entry.symbol);
|
|
27
|
+
quotes.set(entry.symbol, result?.quote ?? null);
|
|
28
|
+
if (result?.error) errors.push(`${entry.symbol}: ${errorMessage(result.error)}`);
|
|
29
|
+
else if (!result?.quote) errors.push(`${entry.symbol}: quote unavailable`);
|
|
30
|
+
}
|
|
31
|
+
return { quotes, errors };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
await Promise.all(entries.map(async (entry) => {
|
|
35
|
+
try {
|
|
36
|
+
quotes.set(entry.symbol, await provider.getQuote(entry.symbol, ""));
|
|
37
|
+
} catch (error) {
|
|
38
|
+
quotes.set(entry.symbol, null);
|
|
39
|
+
errors.push(`${entry.symbol}: ${errorMessage(error)}`);
|
|
40
|
+
}
|
|
41
|
+
}));
|
|
42
|
+
return { quotes, errors };
|
|
43
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
|
|
3
|
+
import { createWorldIndicesHeadless } from "./headless";
|
|
4
|
+
|
|
5
|
+
const args: HeadlessPaneLoadArgs = { rawArgument: "", argument: null, symbols: [], options: {} };
|
|
6
|
+
|
|
7
|
+
describe("world indices headless model", () => {
|
|
8
|
+
test("groups quote rows by the same world regions as the pane", async () => {
|
|
9
|
+
const headless = createWorldIndicesHeadless({
|
|
10
|
+
load: async (_args, entries) => ({
|
|
11
|
+
quotes: new Map(entries.map((entry) => [entry.symbol, {
|
|
12
|
+
symbol: entry.symbol,
|
|
13
|
+
price: entry.symbol === "^GSPC" ? 6_800 : 100,
|
|
14
|
+
currency: "USD",
|
|
15
|
+
change: 10,
|
|
16
|
+
changePercent: 0.5,
|
|
17
|
+
lastUpdated: 1_700_000_000_000,
|
|
18
|
+
marketState: "REGULAR" as const,
|
|
19
|
+
}])),
|
|
20
|
+
errors: [],
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
const result = await headless.load(args, {} as HeadlessPaneContext);
|
|
24
|
+
|
|
25
|
+
expect(result.sections.map((section) => section.title)).toEqual([
|
|
26
|
+
"Americas", "Europe", "Asia-Pacific", "Other",
|
|
27
|
+
]);
|
|
28
|
+
expect(result.sections[0]?.rows?.[0]).toMatchObject({
|
|
29
|
+
symbol: "^GSPC", shortName: "SPX", price: 6_800,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { DataProvider } from "../../../types/data-provider";
|
|
2
|
+
import type {
|
|
3
|
+
HeadlessBundleResult,
|
|
4
|
+
HeadlessPaneDefinition,
|
|
5
|
+
HeadlessPaneLoadArgs,
|
|
6
|
+
} from "../../../types/plugin";
|
|
7
|
+
import { formatCurrency, formatNumber, formatPercentRaw } from "../../../utils/format";
|
|
8
|
+
import { loadWorldIndexQuotes, type WorldIndexQuoteResult } from "./client";
|
|
9
|
+
import {
|
|
10
|
+
getIndicesByRegion,
|
|
11
|
+
REGION_LABELS,
|
|
12
|
+
REGION_ORDER,
|
|
13
|
+
WORLD_INDICES,
|
|
14
|
+
type IndexEntry,
|
|
15
|
+
} from "./indices";
|
|
16
|
+
|
|
17
|
+
const COLUMNS = [
|
|
18
|
+
{ key: "shortName", header: "Index" },
|
|
19
|
+
{ key: "name", header: "Name" },
|
|
20
|
+
{
|
|
21
|
+
key: "price",
|
|
22
|
+
header: "Last",
|
|
23
|
+
align: "right" as const,
|
|
24
|
+
format: (value: unknown, row: Record<string, unknown>) => value == null
|
|
25
|
+
? "-"
|
|
26
|
+
: formatCurrency(Number(value), String(row.currency ?? "USD")),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
key: "change",
|
|
30
|
+
header: "Change",
|
|
31
|
+
align: "right" as const,
|
|
32
|
+
format: (value: unknown) => value == null ? "-" : `${Number(value) >= 0 ? "+" : ""}${formatNumber(Number(value), 2)}`,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: "changePercent",
|
|
36
|
+
header: "Change %",
|
|
37
|
+
align: "right" as const,
|
|
38
|
+
format: (value: unknown) => value == null ? "-" : formatPercentRaw(Number(value)),
|
|
39
|
+
},
|
|
40
|
+
{ key: "marketState", header: "Session" },
|
|
41
|
+
{
|
|
42
|
+
key: "lastUpdated",
|
|
43
|
+
header: "Updated",
|
|
44
|
+
format: (value: unknown) => value == null ? "-" : new Date(Number(value)).toISOString(),
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
export function projectWorldIndicesHeadless(
|
|
49
|
+
entries: readonly IndexEntry[],
|
|
50
|
+
loaded: WorldIndexQuoteResult,
|
|
51
|
+
): HeadlessBundleResult {
|
|
52
|
+
const grouped = getIndicesByRegion(entries);
|
|
53
|
+
return {
|
|
54
|
+
sections: REGION_ORDER.flatMap((region) => {
|
|
55
|
+
const regionEntries = grouped.get(region) ?? [];
|
|
56
|
+
if (regionEntries.length === 0) return [];
|
|
57
|
+
return [{
|
|
58
|
+
title: REGION_LABELS[region],
|
|
59
|
+
columns: COLUMNS,
|
|
60
|
+
rows: regionEntries.map((entry) => {
|
|
61
|
+
const quote = loaded.quotes.get(entry.symbol);
|
|
62
|
+
return {
|
|
63
|
+
...entry,
|
|
64
|
+
price: quote?.price ?? null,
|
|
65
|
+
currency: quote?.currency ?? null,
|
|
66
|
+
change: quote?.change ?? null,
|
|
67
|
+
changePercent: quote?.changePercent ?? null,
|
|
68
|
+
marketState: quote?.marketState ?? null,
|
|
69
|
+
lastUpdated: quote?.lastUpdated ?? null,
|
|
70
|
+
};
|
|
71
|
+
}),
|
|
72
|
+
}];
|
|
73
|
+
}),
|
|
74
|
+
errors: loaded.errors,
|
|
75
|
+
metadata: {
|
|
76
|
+
requested: entries.length,
|
|
77
|
+
available: [...loaded.quotes.values()].filter(Boolean).length,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface WorldIndicesHeadlessDependencies {
|
|
83
|
+
load(
|
|
84
|
+
args: HeadlessPaneLoadArgs,
|
|
85
|
+
entries: readonly IndexEntry[],
|
|
86
|
+
provider: DataProvider,
|
|
87
|
+
): Promise<WorldIndexQuoteResult>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const defaultDependencies: WorldIndicesHeadlessDependencies = {
|
|
91
|
+
load: (_args, entries, provider) => loadWorldIndexQuotes(entries, provider),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export function createWorldIndicesHeadless(
|
|
95
|
+
dependencies: WorldIndicesHeadlessDependencies = defaultDependencies,
|
|
96
|
+
): HeadlessPaneDefinition<"bundle"> {
|
|
97
|
+
return {
|
|
98
|
+
shape: "bundle",
|
|
99
|
+
argument: { kind: "none" },
|
|
100
|
+
options: [],
|
|
101
|
+
describe: "World Equity Indices",
|
|
102
|
+
async load(args, ctx) {
|
|
103
|
+
const loaded = await dependencies.load(args, WORLD_INDICES, ctx.marketData);
|
|
104
|
+
return projectWorldIndicesHeadless(WORLD_INDICES, loaded);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const worldIndicesHeadless = createWorldIndicesHeadless();
|
|
@@ -9,6 +9,7 @@ import { useQuoteBoard } from "../shared/use-quote-board";
|
|
|
9
9
|
import { WORLD_INDICES, REGION_LABELS, getIndicesByRegion, resolveIndexEntries } from "./indices";
|
|
10
10
|
import { useWorldIndicesFooter } from "./footer";
|
|
11
11
|
import { createPublicPaneShare } from "../shared/public-pane";
|
|
12
|
+
import { worldIndicesHeadless } from "./headless";
|
|
12
13
|
import {
|
|
13
14
|
buildFlatRows,
|
|
14
15
|
DEFAULT_SORT_PREFERENCE,
|
|
@@ -23,6 +24,8 @@ import {
|
|
|
23
24
|
type WorldIndexColumn,
|
|
24
25
|
} from "./table";
|
|
25
26
|
|
|
27
|
+
export { worldIndicesHeadless } from "./headless";
|
|
28
|
+
|
|
26
29
|
/** Stable identity: a fresh literal here would remount the board every render. */
|
|
27
30
|
const NO_SAVED_SYMBOLS: string[] = [];
|
|
28
31
|
|
|
@@ -160,6 +163,7 @@ export const worldIndicesModule: PluginModule = {
|
|
|
160
163
|
description: "Monitor global equity indices grouped by region.",
|
|
161
164
|
keywords: ["world", "indices", "global", "equity", "markets", "international"],
|
|
162
165
|
shortcut: { prefix: "WEI" },
|
|
166
|
+
headless: worldIndicesHeadless,
|
|
163
167
|
publicShare: createPublicPaneShare("World Equity Indices"),
|
|
164
168
|
},
|
|
165
169
|
],
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
|
|
3
|
+
import { createYieldCurveHeadless } from "./headless";
|
|
4
|
+
|
|
5
|
+
const args: HeadlessPaneLoadArgs = { rawArgument: "", argument: null, symbols: [], options: {} };
|
|
6
|
+
|
|
7
|
+
describe("yield curve headless model", () => {
|
|
8
|
+
test("returns ordered maturities and the shared 2Y to 10Y spread", async () => {
|
|
9
|
+
const headless = createYieldCurveHeadless({
|
|
10
|
+
load: async () => [
|
|
11
|
+
{ maturity: "10Y", maturityYears: 10, yield: 4.1, asOf: "2026-09-03" },
|
|
12
|
+
{ maturity: "2Y", maturityYears: 2, yield: 4.35, asOf: "2026-09-04" },
|
|
13
|
+
],
|
|
14
|
+
});
|
|
15
|
+
const result = await headless.load(args, {} as HeadlessPaneContext);
|
|
16
|
+
|
|
17
|
+
expect(result.rows.map((row) => row.maturity)).toEqual(["2Y", "10Y"]);
|
|
18
|
+
expect(result.metadata).toEqual({
|
|
19
|
+
asOf: "2026-09-04",
|
|
20
|
+
inverted: true,
|
|
21
|
+
spread2Y10YBasisPoints: -25,
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|