gloomberb 0.13.1 → 0.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/api-client/index.test.ts +34 -0
- package/src/api-client/request.ts +4 -1
- package/src/cli/desktop-pane-shot.ts +72 -2
- package/src/cli/pane-functions/cloud-session.test.ts +77 -0
- package/src/cli/pane-functions/cloud-session.ts +47 -0
- package/src/cli/pane-functions/index.ts +5 -1
- package/src/cli/pane-functions/screenshot.test.ts +27 -0
- package/src/cli/pane-functions/screenshot.ts +85 -20
- package/src/components/command-bar/assist/runtime.ts +38 -30
- package/src/components/command-bar/surface/assist.test.tsx +43 -0
- package/src/news/types.ts +1 -0
- 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 +3 -2
- package/src/plugins/builtin/cloud/askg/protocol.ts +16 -4
- 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/plugin.tsx +2 -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/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 +35 -9
- package/src/plugins/builtin/earnings/model.test.ts +29 -1
- 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/transcript-view.tsx +8 -9
- 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/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.test.tsx +145 -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 +27 -1
- package/src/plugins/builtin/research/event-model.ts +56 -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.test.ts +43 -0
- package/src/plugins/builtin/research/index.tsx +28 -18
- 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/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/short-interest/model.ts +15 -4
- package/src/plugins/builtin/short-interest/pane.tsx +1 -1
- 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/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/renderers/browser/config-host.test.ts +70 -0
- package/src/renderers/browser/config-host.ts +74 -10
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +180 -19
- package/src/sources/gloomberb-cloud/news.ts +1 -0
- package/src/updater.test.ts +17 -1
- package/src/updater.ts +4 -2
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HeadlessPaneColumn,
|
|
3
|
+
HeadlessPaneContext,
|
|
4
|
+
HeadlessPaneDefinition,
|
|
5
|
+
HeadlessPaneLoadArgs,
|
|
6
|
+
} from "../../../types/plugin";
|
|
7
|
+
import type { CloudCongressHousePayload } from "../../../api-client";
|
|
8
|
+
import { loadCongressHouse } from "./client";
|
|
9
|
+
import {
|
|
10
|
+
CONGRESS_FILING_LIMIT,
|
|
11
|
+
CONGRESS_TRADE_LIMIT,
|
|
12
|
+
formatAmountRange,
|
|
13
|
+
formatLag,
|
|
14
|
+
formatShortDate,
|
|
15
|
+
sortedMembers,
|
|
16
|
+
sortedTrades,
|
|
17
|
+
} from "./model";
|
|
18
|
+
|
|
19
|
+
const TRADE_COLUMNS: HeadlessPaneColumn[] = [
|
|
20
|
+
{ key: "filingDate", header: "Filed", format: (value) => formatShortDate(typeof value === "string" ? value : null) },
|
|
21
|
+
{ key: "transactionDate", header: "Tx", format: (value) => formatShortDate(typeof value === "string" ? value : null) },
|
|
22
|
+
{ key: "lagDays", header: "Lag", align: "right", format: (value) => formatLag(typeof value === "number" ? value : null) },
|
|
23
|
+
{ key: "memberName", header: "Member" },
|
|
24
|
+
{ key: "side", header: "Side" },
|
|
25
|
+
{ key: "ticker", header: "Ticker" },
|
|
26
|
+
{
|
|
27
|
+
key: "amountLow",
|
|
28
|
+
header: "Amount",
|
|
29
|
+
align: "right",
|
|
30
|
+
format: (value, row) => formatAmountRange(
|
|
31
|
+
typeof value === "number" ? value : null,
|
|
32
|
+
typeof row.amountHigh === "number" ? row.amountHigh : null,
|
|
33
|
+
typeof row.amount === "string" ? row.amount : undefined,
|
|
34
|
+
),
|
|
35
|
+
},
|
|
36
|
+
{ key: "owner", header: "Owner" },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const MEMBER_COLUMNS: HeadlessPaneColumn[] = [
|
|
40
|
+
{ key: "memberName", header: "Member" },
|
|
41
|
+
{ key: "stateDistrict", header: "Dist" },
|
|
42
|
+
{ key: "tradeCount", header: "Trades", align: "right" },
|
|
43
|
+
{ key: "buyCount", header: "Buy", align: "right" },
|
|
44
|
+
{ key: "sellCount", header: "Sell", align: "right" },
|
|
45
|
+
{
|
|
46
|
+
key: "estimatedLow",
|
|
47
|
+
header: "Est Range",
|
|
48
|
+
align: "right",
|
|
49
|
+
format: (value, row) => formatAmountRange(
|
|
50
|
+
typeof value === "number" ? value : null,
|
|
51
|
+
typeof row.estimatedHigh === "number" ? row.estimatedHigh : null,
|
|
52
|
+
),
|
|
53
|
+
},
|
|
54
|
+
{ key: "lastFilingDate", header: "Last", format: (value) => formatShortDate(typeof value === "string" ? value : null) },
|
|
55
|
+
{ key: "avgLagDays", header: "Avg", align: "right", format: (value) => formatLag(typeof value === "number" ? value : null) },
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
export interface CongressHeadlessDependencies {
|
|
59
|
+
loadHouse(
|
|
60
|
+
args: HeadlessPaneLoadArgs,
|
|
61
|
+
ctx: HeadlessPaneContext,
|
|
62
|
+
): Promise<CloudCongressHousePayload>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const defaultDependencies: CongressHeadlessDependencies = {
|
|
66
|
+
loadHouse: (args, ctx) => loadCongressHouse({
|
|
67
|
+
year: Number(args.options.year),
|
|
68
|
+
limit: CONGRESS_TRADE_LIMIT,
|
|
69
|
+
filingLimit: CONGRESS_FILING_LIMIT,
|
|
70
|
+
}, ctx.apiClient),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export function createCongressHeadless(
|
|
74
|
+
dependencies: CongressHeadlessDependencies = defaultDependencies,
|
|
75
|
+
): HeadlessPaneDefinition<"rows"> {
|
|
76
|
+
return {
|
|
77
|
+
shape: "rows",
|
|
78
|
+
argument: {
|
|
79
|
+
kind: "none",
|
|
80
|
+
description: "The House PTR feed does not require an argument.",
|
|
81
|
+
},
|
|
82
|
+
options: [
|
|
83
|
+
{
|
|
84
|
+
key: "tab",
|
|
85
|
+
description: "Congress pane tab.",
|
|
86
|
+
type: "enum",
|
|
87
|
+
values: [{ value: "trades" }, { value: "members" }],
|
|
88
|
+
defaultValue: "trades",
|
|
89
|
+
pluginState: { pluginId: "gloomberb-cloud", key: "activeTab" },
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
key: "year",
|
|
93
|
+
description: "House disclosure year.",
|
|
94
|
+
type: "integer",
|
|
95
|
+
defaultValue: new Date().getUTCFullYear(),
|
|
96
|
+
minimum: 2008,
|
|
97
|
+
maximum: 2100,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: "limit",
|
|
101
|
+
aliases: ["count", "rows"],
|
|
102
|
+
description: "Maximum rows.",
|
|
103
|
+
type: "integer",
|
|
104
|
+
defaultValue: 50,
|
|
105
|
+
minimum: 1,
|
|
106
|
+
maximum: 200,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
describe: (args) => `Congress Trades | ${args.options.tab === "members" ? "Members" : "Trades"}`,
|
|
110
|
+
async load(args, ctx) {
|
|
111
|
+
const payload = await dependencies.loadHouse(args, ctx);
|
|
112
|
+
const limit = Number(args.options.limit);
|
|
113
|
+
if (args.options.tab === "members") {
|
|
114
|
+
const rows = sortedMembers(payload.members, { columnId: "trades", direction: "desc" })
|
|
115
|
+
.slice(0, limit)
|
|
116
|
+
.map((member) => ({ ...member }));
|
|
117
|
+
return {
|
|
118
|
+
columns: MEMBER_COLUMNS,
|
|
119
|
+
rows,
|
|
120
|
+
metadata: {
|
|
121
|
+
asOf: payload.asOf,
|
|
122
|
+
chamber: payload.chamber,
|
|
123
|
+
source: payload.source,
|
|
124
|
+
year: payload.year,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const rows = sortedTrades(payload.trades, { columnId: "filed", direction: "desc" })
|
|
129
|
+
.slice(0, limit)
|
|
130
|
+
.map((trade) => ({ ...trade }));
|
|
131
|
+
return {
|
|
132
|
+
columns: TRADE_COLUMNS,
|
|
133
|
+
rows,
|
|
134
|
+
metadata: {
|
|
135
|
+
asOf: payload.asOf,
|
|
136
|
+
chamber: payload.chamber,
|
|
137
|
+
source: payload.source,
|
|
138
|
+
year: payload.year,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const congressHeadless = createCongressHeadless();
|
|
@@ -10,7 +10,6 @@ import { useDebouncedPluginPaneState, usePluginPaneState } from "../../runtime";
|
|
|
10
10
|
import { useAutoRefresh } from "../shared/auto-refresh";
|
|
11
11
|
import { useInlineTickerOpener } from "../../../state/hooks/inline-tickers";
|
|
12
12
|
import {
|
|
13
|
-
apiClient,
|
|
14
13
|
type CloudCongressHousePayload,
|
|
15
14
|
type CloudCongressMemberPayload,
|
|
16
15
|
type CloudCongressTradePayload,
|
|
@@ -46,6 +45,7 @@ import {
|
|
|
46
45
|
renderCongressMemberCell,
|
|
47
46
|
renderCongressTradeCell,
|
|
48
47
|
} from "./table";
|
|
48
|
+
import { loadCongressHouse } from "./client";
|
|
49
49
|
|
|
50
50
|
export { CONGRESS_TRADES_PANE_ID } from "./model";
|
|
51
51
|
|
|
@@ -77,7 +77,7 @@ export function CongressTradesPane({ focused, width, height }: PaneProps) {
|
|
|
77
77
|
setStatus((current) => (current === "loaded" && !refresh ? "loaded" : "loading"));
|
|
78
78
|
setError(null);
|
|
79
79
|
setLoadingMore(false);
|
|
80
|
-
|
|
80
|
+
loadCongressHouse({
|
|
81
81
|
limit: CONGRESS_TRADE_LIMIT,
|
|
82
82
|
filingLimit: CONGRESS_FILING_LIMIT,
|
|
83
83
|
refresh,
|
|
@@ -103,7 +103,7 @@ export function CongressTradesPane({ focused, width, height }: PaneProps) {
|
|
|
103
103
|
if (!nextRequest) return;
|
|
104
104
|
const gen = fetchGenRef.current;
|
|
105
105
|
setLoadingMore(true);
|
|
106
|
-
|
|
106
|
+
loadCongressHouse({
|
|
107
107
|
...nextRequest,
|
|
108
108
|
limit: CONGRESS_TRADE_LIMIT,
|
|
109
109
|
filingLimit: CONGRESS_FILING_LIMIT,
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
|
|
3
|
+
import type { DividendData } from "./client";
|
|
4
|
+
import { createDividendYieldHeadless } from "./headless";
|
|
5
|
+
|
|
6
|
+
const fixture: DividendData = {
|
|
7
|
+
price: 200,
|
|
8
|
+
metrics: {
|
|
9
|
+
trailingYield: 0.005,
|
|
10
|
+
forwardYield: 0.0052,
|
|
11
|
+
trailingRate: 1,
|
|
12
|
+
forwardRate: 1.04,
|
|
13
|
+
payoutRatio: 0.14,
|
|
14
|
+
growth1Y: 0.04,
|
|
15
|
+
growth3Y: 0.03,
|
|
16
|
+
paymentFrequency: "quarterly",
|
|
17
|
+
exDividendDate: new Date("2026-08-10T00:00:00.000Z"),
|
|
18
|
+
nextPayDate: new Date("2026-08-17T00:00:00.000Z"),
|
|
19
|
+
},
|
|
20
|
+
payments: [
|
|
21
|
+
{
|
|
22
|
+
exDate: new Date("2026-08-10T00:00:00.000Z"),
|
|
23
|
+
recordDate: null,
|
|
24
|
+
paymentDate: null,
|
|
25
|
+
declarationDate: null,
|
|
26
|
+
amount: 0.26,
|
|
27
|
+
currency: "USD",
|
|
28
|
+
type: "cash",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
exDate: new Date("2026-05-11T00:00:00.000Z"),
|
|
32
|
+
recordDate: null,
|
|
33
|
+
paymentDate: null,
|
|
34
|
+
declarationDate: null,
|
|
35
|
+
amount: 0.24,
|
|
36
|
+
currency: "USD",
|
|
37
|
+
type: "special",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function args(options: Record<string, string | number | boolean>): HeadlessPaneLoadArgs {
|
|
43
|
+
return {
|
|
44
|
+
rawArgument: "AAPL",
|
|
45
|
+
argument: "AAPL",
|
|
46
|
+
symbols: ["AAPL"],
|
|
47
|
+
options,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const context = {} as HeadlessPaneContext;
|
|
52
|
+
|
|
53
|
+
describe("dividend yield headless", () => {
|
|
54
|
+
test("maps metrics and payment rows", async () => {
|
|
55
|
+
const definition = createDividendYieldHeadless({
|
|
56
|
+
loadData: async () => fixture,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const result = await definition.load(args({ type: "all", limit: 40 }), context);
|
|
60
|
+
|
|
61
|
+
expect(result.sections[0]).toMatchObject({
|
|
62
|
+
title: "Dividend metrics",
|
|
63
|
+
entries: expect.arrayContaining([
|
|
64
|
+
{ label: "Trailing yield", value: 0.005 },
|
|
65
|
+
{ label: "Frequency", value: "quarterly" },
|
|
66
|
+
]),
|
|
67
|
+
});
|
|
68
|
+
expect(result.sections[1]).toMatchObject({
|
|
69
|
+
title: "Dividend history",
|
|
70
|
+
rows: [
|
|
71
|
+
expect.objectContaining({ exDate: "2026-08-10", amount: 0.26, type: "cash" }),
|
|
72
|
+
expect.objectContaining({ exDate: "2026-05-11", amount: 0.24, type: "special" }),
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("applies type and limit options", async () => {
|
|
78
|
+
const definition = createDividendYieldHeadless({ loadData: async () => fixture });
|
|
79
|
+
|
|
80
|
+
const result = await definition.load(args({ type: "cash", limit: 1 }), context);
|
|
81
|
+
const history = result.sections[1];
|
|
82
|
+
|
|
83
|
+
expect(history).toMatchObject({
|
|
84
|
+
rows: [expect.objectContaining({ type: "cash" })],
|
|
85
|
+
});
|
|
86
|
+
expect(result.metadata).toMatchObject({ totalPayments: 1, returnedPayments: 1 });
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HeadlessBundleResult,
|
|
3
|
+
HeadlessPaneContext,
|
|
4
|
+
HeadlessPaneDefinition,
|
|
5
|
+
HeadlessPaneLoadArgs,
|
|
6
|
+
} from "../../../types/plugin";
|
|
7
|
+
import { fetchDividendData, type DividendData } from "./client";
|
|
8
|
+
import type { DividendPayment } from "./types";
|
|
9
|
+
import { toDividendRows } from "./view";
|
|
10
|
+
|
|
11
|
+
const PAYMENT_COLUMNS = [
|
|
12
|
+
{ key: "exDate", header: "Ex-date" },
|
|
13
|
+
{ key: "amount", header: "Amount", align: "right" as const },
|
|
14
|
+
{ key: "currency", header: "CCY" },
|
|
15
|
+
{ key: "type", header: "Type" },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export interface DividendYieldHeadlessDependencies {
|
|
19
|
+
loadData(symbol: string, context: HeadlessPaneContext): Promise<DividendData>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const defaultDependencies: DividendYieldHeadlessDependencies = {
|
|
23
|
+
async loadData(symbol, context) {
|
|
24
|
+
let currentPrice: number | null = null;
|
|
25
|
+
try {
|
|
26
|
+
currentPrice = (await context.marketData.getQuote(symbol, "")).price ?? null;
|
|
27
|
+
} catch {
|
|
28
|
+
currentPrice = null;
|
|
29
|
+
}
|
|
30
|
+
return fetchDividendData(symbol, currentPrice);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function matchesType(payment: DividendPayment, type: string): boolean {
|
|
35
|
+
return type === "all" || payment.type === type;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function projectDividendYieldHeadless(
|
|
39
|
+
data: DividendData,
|
|
40
|
+
args: HeadlessPaneLoadArgs,
|
|
41
|
+
): HeadlessBundleResult {
|
|
42
|
+
const type = String(args.options.type ?? "all");
|
|
43
|
+
const limit = Number(args.options.limit ?? 40);
|
|
44
|
+
const matching = data.payments.filter((payment) => matchesType(payment, type));
|
|
45
|
+
const selectedPayments = matching.slice(0, limit);
|
|
46
|
+
const rows = toDividendRows(selectedPayments).map((row, index) => {
|
|
47
|
+
const payment = selectedPayments[index];
|
|
48
|
+
return {
|
|
49
|
+
...row,
|
|
50
|
+
recordDate: payment?.recordDate?.toISOString() ?? null,
|
|
51
|
+
paymentDate: payment?.paymentDate?.toISOString() ?? null,
|
|
52
|
+
declarationDate: payment?.declarationDate?.toISOString() ?? null,
|
|
53
|
+
type: payment?.type ?? null,
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
const metrics = data.metrics;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
sections: [
|
|
60
|
+
{
|
|
61
|
+
title: "Dividend metrics",
|
|
62
|
+
entries: [
|
|
63
|
+
{ label: "Price", value: data.price },
|
|
64
|
+
{ label: "Trailing yield", value: metrics.trailingYield },
|
|
65
|
+
{ label: "Forward yield", value: metrics.forwardYield },
|
|
66
|
+
{ label: "Trailing rate", value: metrics.trailingRate },
|
|
67
|
+
{ label: "Forward rate", value: metrics.forwardRate },
|
|
68
|
+
{ label: "Payout ratio", value: metrics.payoutRatio },
|
|
69
|
+
{ label: "1Y growth", value: metrics.growth1Y },
|
|
70
|
+
{ label: "3Y growth", value: metrics.growth3Y },
|
|
71
|
+
{ label: "Frequency", value: metrics.paymentFrequency },
|
|
72
|
+
{ label: "Ex-dividend", value: metrics.exDividendDate },
|
|
73
|
+
{ label: "Next pay", value: metrics.nextPayDate },
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
title: "Dividend history",
|
|
78
|
+
columns: PAYMENT_COLUMNS,
|
|
79
|
+
rows,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
metadata: {
|
|
83
|
+
totalPayments: matching.length,
|
|
84
|
+
returnedPayments: rows.length,
|
|
85
|
+
truncated: rows.length < matching.length,
|
|
86
|
+
type,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function createDividendYieldHeadless(
|
|
92
|
+
dependencies: DividendYieldHeadlessDependencies = defaultDependencies,
|
|
93
|
+
): HeadlessPaneDefinition<"bundle"> {
|
|
94
|
+
return {
|
|
95
|
+
shape: "bundle",
|
|
96
|
+
argument: {
|
|
97
|
+
kind: "ticker",
|
|
98
|
+
placeholder: "ticker",
|
|
99
|
+
description: "Company symbol.",
|
|
100
|
+
},
|
|
101
|
+
options: [
|
|
102
|
+
{
|
|
103
|
+
key: "type",
|
|
104
|
+
description: "Dividend payment type to include.",
|
|
105
|
+
type: "enum",
|
|
106
|
+
values: [
|
|
107
|
+
{ value: "all" },
|
|
108
|
+
{ value: "cash" },
|
|
109
|
+
{ value: "special" },
|
|
110
|
+
{ value: "stock" },
|
|
111
|
+
{ value: "unknown" },
|
|
112
|
+
],
|
|
113
|
+
defaultValue: "all",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
key: "limit",
|
|
117
|
+
description: "Maximum historical payments to return.",
|
|
118
|
+
type: "integer",
|
|
119
|
+
defaultValue: 40,
|
|
120
|
+
minimum: 1,
|
|
121
|
+
maximum: 200,
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
describe: (args) => `Dividend Yield | ${String(args.argument)}`,
|
|
125
|
+
async load(args, context) {
|
|
126
|
+
const symbol = String(args.argument);
|
|
127
|
+
return projectDividendYieldHeadless(
|
|
128
|
+
await dependencies.loadData(symbol, context),
|
|
129
|
+
args,
|
|
130
|
+
);
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const dividendYieldHeadless = createDividendYieldHeadless();
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
YAHOO_DIVIDENDS_CONNECTION_ID,
|
|
7
7
|
} from "./client";
|
|
8
8
|
import { DividendYieldPane } from "./pane";
|
|
9
|
+
import { dividendYieldHeadless } from "./headless";
|
|
9
10
|
|
|
10
11
|
let disposeConnection: (() => void) | null = null;
|
|
11
12
|
|
|
@@ -50,13 +51,16 @@ export const dividendYieldModule: PluginModule = {
|
|
|
50
51
|
],
|
|
51
52
|
|
|
52
53
|
paneTemplates: [
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
{
|
|
55
|
+
...createTickerSurfacePaneTemplate({
|
|
56
|
+
id: "dividend-yield-pane",
|
|
57
|
+
paneId: "dividend-yield",
|
|
58
|
+
label: "Dividend Yield",
|
|
59
|
+
description: "Dividend history, trailing/forward yield, growth rates, and payment schedule.",
|
|
60
|
+
keywords: ["dividend", "yield", "dvd", "income", "payout", "ex-date", "distribution"],
|
|
61
|
+
shortcut: "DVD",
|
|
62
|
+
}),
|
|
63
|
+
headless: dividendYieldHeadless,
|
|
64
|
+
},
|
|
61
65
|
],
|
|
62
66
|
};
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import type { DataTableColumn } from "../../../components";
|
|
2
2
|
import { compareSortValues, type SortDirection } from "../../../utils/sort-values";
|
|
3
|
-
import type {
|
|
3
|
+
import type { DividendRow } from "./view";
|
|
4
|
+
|
|
5
|
+
export { toDividendRows } from "./view";
|
|
6
|
+
export type { DividendRow } from "./view";
|
|
4
7
|
|
|
5
8
|
export type DividendColumnId = "exDate" | "amount" | "currency";
|
|
6
9
|
export type DividendColumn = DataTableColumn & { id: DividendColumnId };
|
|
7
10
|
|
|
8
|
-
export interface DividendRow {
|
|
9
|
-
key: string;
|
|
10
|
-
exDate: string;
|
|
11
|
-
amount: number;
|
|
12
|
-
currency: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
11
|
export interface DividendSortPreference {
|
|
16
12
|
columnId: DividendColumnId | null;
|
|
17
13
|
direction: SortDirection;
|
|
@@ -67,17 +63,3 @@ export function nextSortPreference(
|
|
|
67
63
|
}
|
|
68
64
|
return DEFAULT_SORT_PREFERENCE;
|
|
69
65
|
}
|
|
70
|
-
|
|
71
|
-
export function toDividendRows(payments: DividendPayment[]): DividendRow[] {
|
|
72
|
-
return payments.map((p, i) => ({
|
|
73
|
-
key: `${p.exDate.toISOString()}:${i}`,
|
|
74
|
-
exDate: formatDate(p.exDate),
|
|
75
|
-
amount: p.amount,
|
|
76
|
-
currency: p.currency,
|
|
77
|
-
}));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function formatDate(date: Date): string {
|
|
81
|
-
const iso = date.toISOString();
|
|
82
|
-
return iso.slice(0, 10);
|
|
83
|
-
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DividendPayment } from "./types";
|
|
2
|
+
|
|
3
|
+
export interface DividendRow {
|
|
4
|
+
key: string;
|
|
5
|
+
exDate: string;
|
|
6
|
+
amount: number;
|
|
7
|
+
currency: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function toDividendRows(payments: DividendPayment[]): DividendRow[] {
|
|
11
|
+
return payments.map((payment, index) => ({
|
|
12
|
+
key: `${payment.exDate.toISOString()}:${index}`,
|
|
13
|
+
exDate: payment.exDate.toISOString().slice(0, 10),
|
|
14
|
+
amount: payment.amount,
|
|
15
|
+
currency: payment.currency,
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { EarningsEvent } from "../../../types/data-provider";
|
|
3
|
+
import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
|
|
4
|
+
import { createEarningsCalendarHeadless } from "./headless";
|
|
5
|
+
|
|
6
|
+
const events: EarningsEvent[] = [
|
|
7
|
+
{
|
|
8
|
+
symbol: "MSFT",
|
|
9
|
+
name: "Microsoft",
|
|
10
|
+
earningsDate: new Date("2026-09-08T20:00:00.000Z"),
|
|
11
|
+
earningsCallDate: new Date("2026-09-08T21:00:00.000Z"),
|
|
12
|
+
isDateEstimate: false,
|
|
13
|
+
epsEstimate: 3.67,
|
|
14
|
+
epsLow: 3.5,
|
|
15
|
+
epsHigh: 3.8,
|
|
16
|
+
epsGrowth: 0.12,
|
|
17
|
+
epsTrend30dAgo: 3.55,
|
|
18
|
+
epsRevisionUp30d: 8,
|
|
19
|
+
epsRevisionDown30d: 2,
|
|
20
|
+
epsActual: null,
|
|
21
|
+
revenueEstimate: 74_000_000_000,
|
|
22
|
+
revenueLow: 72_000_000_000,
|
|
23
|
+
revenueHigh: 76_000_000_000,
|
|
24
|
+
revenueGrowth: 0.16,
|
|
25
|
+
revenueActual: null,
|
|
26
|
+
surprise: null,
|
|
27
|
+
timing: "AMC",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
symbol: "AAPL",
|
|
31
|
+
name: "Apple",
|
|
32
|
+
earningsDate: new Date("2026-09-07T12:00:00.000Z"),
|
|
33
|
+
epsEstimate: 1.5,
|
|
34
|
+
epsActual: null,
|
|
35
|
+
revenueEstimate: 95_000_000_000,
|
|
36
|
+
revenueActual: null,
|
|
37
|
+
surprise: null,
|
|
38
|
+
timing: "BMO",
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
function args(options: Record<string, string | number | boolean>): HeadlessPaneLoadArgs {
|
|
43
|
+
return {
|
|
44
|
+
rawArgument: "AAPL,MSFT",
|
|
45
|
+
argument: ["AAPL", "MSFT"],
|
|
46
|
+
symbols: ["AAPL", "MSFT"],
|
|
47
|
+
options,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const context = {} as HeadlessPaneContext;
|
|
52
|
+
|
|
53
|
+
describe("earnings calendar headless", () => {
|
|
54
|
+
test("maps provider events into date-ordered rows", async () => {
|
|
55
|
+
const definition = createEarningsCalendarHeadless({
|
|
56
|
+
loadCalendar: async () => ({ events, fetchedAt: 123, stale: false }),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const result = await definition.load(args({ timing: "all", limit: 50 }), context);
|
|
60
|
+
|
|
61
|
+
expect(result.rows.map((row) => row.symbol)).toEqual(["AAPL", "MSFT"]);
|
|
62
|
+
expect(result.rows[1]).toMatchObject({
|
|
63
|
+
date: "2026-09-08T20:00:00.000Z",
|
|
64
|
+
timing: "AMC",
|
|
65
|
+
dateStatus: "confirmed",
|
|
66
|
+
epsEstimate: 3.67,
|
|
67
|
+
revenueEstimate: 74_000_000_000,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("applies timing and limit options", async () => {
|
|
72
|
+
const definition = createEarningsCalendarHeadless({
|
|
73
|
+
loadCalendar: async () => ({ events, fetchedAt: 123, stale: false }),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const result = await definition.load(args({ timing: "AMC", limit: 1 }), context);
|
|
77
|
+
|
|
78
|
+
expect(result.rows).toHaveLength(1);
|
|
79
|
+
expect(result.rows[0]?.symbol).toBe("MSFT");
|
|
80
|
+
expect(result.metadata).toMatchObject({ timing: "AMC", returned: 1 });
|
|
81
|
+
});
|
|
82
|
+
});
|