gloomberb 0.17.1 → 0.17.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/package.json +1 -1
- package/src/{plugins/builtin/cloud → account}/auth-dialog.tsx +7 -7
- package/src/api-client/earnings.ts +4 -2
- package/src/app/global-shortcuts.ts +10 -0
- package/src/app/keybindings/actions.ts +16 -0
- package/src/app/runtime/desktop-deeplink.ts +1 -1
- package/src/app.tsx +1 -1
- package/src/architecture/core-builtin-allowlist.json +4 -9
- package/src/cli/commands/market.ts +34 -16
- package/src/cli/commands/options-chain.ts +29 -33
- package/src/cli/commands/options-leaps.ts +462 -63
- package/src/cli/commands/search.ts +10 -7
- package/src/cli/commands/ticker.ts +1 -1
- package/src/cli/known-gaps.ts +22 -0
- package/src/cli/pane-functions/catalog.ts +6 -3
- package/src/cli/pane-functions/dom.ts +17 -2
- package/src/cli/pane-functions/function-help.ts +1 -1
- package/src/cli/pane-functions/index.ts +5 -3
- package/src/cli/pane-functions/report-notices.ts +15 -0
- package/src/cli/symbol-args.ts +19 -0
- package/src/components/command-bar/workflow/builtin.ts +2 -2
- package/src/components/command-bar/workflow/collection-actions.ts +1 -1
- package/src/components/feedback-dialog.tsx +1 -1
- package/src/components/layout/shell/index.tsx +1 -1
- package/src/components/layout/status-bar-groups.ts +1 -1
- package/src/components/layout/status-bar.tsx +2 -2
- package/src/components/markdown-text.tsx +14 -2
- package/src/components/reader/article-reader.tsx +1 -1
- package/src/components/reader/detail-reader.tsx +10 -7
- package/src/components/reader/reader-text.tsx +32 -17
- package/src/layout-marketplace/gallery.tsx +2 -2
- package/src/layout-marketplace/linked-sync.ts +1 -1
- package/src/market-data/market/freshness.ts +3 -0
- package/src/market-data/published-apac-sessions.ts +12 -2
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/presets.ts +33 -1
- package/src/plugins/builtin/chat/channel-labels.ts +2 -2
- package/src/plugins/builtin/chat/content/channel-navigation.ts +1 -1
- package/src/plugins/builtin/chat/content/index.tsx +2 -2
- package/src/plugins/builtin/chat/conversations.ts +1 -1
- package/src/plugins/builtin/chat/index.tsx +2 -2
- package/src/plugins/builtin/chat/share/flow.tsx +1 -1
- package/src/plugins/builtin/chat/sidebar-rows.ts +1 -1
- package/src/plugins/builtin/chat/sidebar.tsx +2 -2
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +2 -2
- package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
- package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +1 -1
- package/src/plugins/builtin/cloud/team/acm-tab.tsx +3 -3
- package/src/plugins/builtin/cloud/team/capability.ts +2 -2
- package/src/plugins/builtin/cloud/team/command.ts +4 -4
- package/src/plugins/builtin/cloud/team/module.tsx +3 -3
- package/src/plugins/builtin/cloud/team/pane-model.ts +2 -2
- package/src/plugins/builtin/cloud/team/pane-sections.tsx +1 -1
- package/src/plugins/builtin/cloud/team/pane-ui.tsx +1 -1
- package/src/plugins/builtin/cloud/team/pane.tsx +3 -3
- package/src/plugins/builtin/cloud/team/status-widget.tsx +3 -3
- package/src/plugins/builtin/cloud/team/team-state-host.ts +1 -1
- package/src/plugins/builtin/cloud/team/views.ts +2 -2
- package/src/plugins/builtin/cloud/thesis/board-pane.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/detail.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/ticker-tab.tsx +1 -1
- package/src/plugins/builtin/cloud/upgrade-command.ts +1 -1
- package/src/plugins/builtin/custom-view/pane.tsx +2 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +8 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +4 -2
- package/src/plugins/builtin/docs/blocks.tsx +13 -2
- package/src/plugins/builtin/docs/pane.tsx +6 -5
- package/src/plugins/builtin/earnings/board-model.ts +18 -1
- package/src/plugins/builtin/earnings/board-table.ts +18 -3
- package/src/plugins/builtin/earnings/headless.ts +13 -2
- package/src/plugins/builtin/earnings/history-model.ts +1 -1
- package/src/plugins/builtin/earnings/history-table.ts +2 -2
- package/src/plugins/builtin/help/feedback-tab.tsx +1 -1
- package/src/plugins/builtin/notes/index.tsx +1 -1
- package/src/plugins/builtin/notes/owner.tsx +2 -2
- package/src/plugins/builtin/options/carry.ts +36 -0
- package/src/plugins/builtin/options/open-interest-as-of.ts +16 -5
- package/src/plugins/builtin/options/view.tsx +19 -7
- package/src/plugins/builtin/options-calculator/pane.tsx +56 -1
- package/src/plugins/builtin/options-calculator/premium-history.ts +34 -0
- package/src/plugins/builtin/portfolio-list/mutations.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +4 -2
- package/src/plugins/builtin/research/event-model.ts +7 -4
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/earnings-report.ts +10 -4
- package/src/renderers/dom/styles.css +4 -2
- package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
- package/src/{plugins/builtin/cloud/team → team}/collections-sync.ts +6 -6
- package/src/{plugins/builtin/cloud/team → team}/collections.ts +3 -3
- package/src/{plugins/builtin/cloud/team → team}/model.ts +2 -2
- package/src/{plugins/builtin/cloud/team → team}/pane-request.ts +1 -1
- package/src/{plugins/builtin/cloud/team → team}/store.ts +3 -3
- package/src/types/data-provider.ts +3 -0
- package/src/types/financials.ts +6 -0
- package/src/utils/decimal-comma.ts +18 -0
- package/src/utils/exchanges.ts +7 -0
- package/src/utils/forward-dividend.ts +39 -9
- package/src/utils/option-expiry.ts +16 -0
- package/src/utils/sec.ts +2 -1
package/package.json
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
* hosted terminal's sign-in gate reuses.
|
|
7
7
|
*/
|
|
8
8
|
import { useEffect, useRef, useState } from "react";
|
|
9
|
-
import type { AuthUser } from "
|
|
10
|
-
import { DialogFrame } from "
|
|
11
|
-
import { useAppLanguage } from "
|
|
12
|
-
import { useDialog, type PromptContext } from "
|
|
13
|
-
import { AuthForm, authFormTitle } from "
|
|
14
|
-
import type { AccountMode } from "
|
|
15
|
-
import { CloudVerificationPanel } from "
|
|
9
|
+
import type { AuthUser } from "../api-client";
|
|
10
|
+
import { DialogFrame } from "../components/ui/frame";
|
|
11
|
+
import { useAppLanguage } from "../i18n/react";
|
|
12
|
+
import { useDialog, type PromptContext } from "../ui/dialog";
|
|
13
|
+
import { AuthForm, authFormTitle } from "./auth-form";
|
|
14
|
+
import type { AccountMode } from "./auth-model";
|
|
15
|
+
import { CloudVerificationPanel } from "./verification-panel";
|
|
16
16
|
|
|
17
17
|
function AuthDialog({
|
|
18
18
|
initialMode,
|
|
@@ -38,7 +38,9 @@ export interface EarningsReport {
|
|
|
38
38
|
reportedAt: string | null;
|
|
39
39
|
/** Fiscal quarter end month, YYYY-MM. */
|
|
40
40
|
fiscalPeriod: string | null;
|
|
41
|
+
/** The company's cap as RV reads it: its latest snapshot at the regular session's price. */
|
|
41
42
|
marketCap: number | null;
|
|
43
|
+
/** For an upcoming report, the consensus every earnings view shows, with `epsAnalysts` behind it. */
|
|
42
44
|
epsEstimate: number | null;
|
|
43
45
|
epsActual: number | null;
|
|
44
46
|
epsAnalysts: number | null;
|
|
@@ -48,6 +50,8 @@ export interface EarningsReport {
|
|
|
48
50
|
realized: EarningsRealizedMove | null;
|
|
49
51
|
/** Whether the date is out, announced or projected; absent from older servers. */
|
|
50
52
|
dateStatus?: EarningsDateStatus;
|
|
53
|
+
/** The reported figure's basis, null when it cannot be determined; absent from older servers. */
|
|
54
|
+
epsBasis?: EarningsEpsBasis | null;
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
export interface EarningsCalendarReport extends EarningsReport {
|
|
@@ -65,8 +69,6 @@ export interface EarningsCalendarPayload {
|
|
|
65
69
|
|
|
66
70
|
interface EarningsHistoryReport extends EarningsReport {
|
|
67
71
|
revenueActual: number | null;
|
|
68
|
-
/** Null when the basis cannot be determined; absent from older servers. */
|
|
69
|
-
epsBasis?: EarningsEpsBasis | null;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
export interface EarningsHistoryPayload {
|
|
@@ -212,6 +212,16 @@ export function useAppGlobalShortcuts({
|
|
|
212
212
|
return;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
// Text fields keep Cmd+Z. Everywhere else it restores the previous layout.
|
|
216
|
+
// A detached window has no layout history of its own.
|
|
217
|
+
if (action === "layout-undo" || action === "layout-redo") {
|
|
218
|
+
if (isDetachedWindow || dialogOpen || state.commandBarOpen || state.inputCaptured || event.targetEditable === true) return;
|
|
219
|
+
event.preventDefault();
|
|
220
|
+
event.stopPropagation();
|
|
221
|
+
dispatch({ type: action === "layout-undo" ? "UNDO_LAYOUT" : "REDO_LAYOUT" });
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
215
225
|
if (dialogOpen) return;
|
|
216
226
|
|
|
217
227
|
if (!isDetachedWindow && action === "command-bar") {
|
|
@@ -30,6 +30,8 @@ export type CoreKeybindingActionId =
|
|
|
30
30
|
| "pane-share"
|
|
31
31
|
| "pane-ask"
|
|
32
32
|
| "layout-gallery"
|
|
33
|
+
| "layout-undo"
|
|
34
|
+
| "layout-redo"
|
|
33
35
|
| "tidy-windows"
|
|
34
36
|
| "window-move-mode"
|
|
35
37
|
| "window-resize-mode";
|
|
@@ -62,6 +64,8 @@ export const PANE_ACTION_IDS = new Set<CoreKeybindingActionId>([
|
|
|
62
64
|
"pane-share",
|
|
63
65
|
"pane-ask",
|
|
64
66
|
"layout-gallery",
|
|
67
|
+
"layout-undo",
|
|
68
|
+
"layout-redo",
|
|
65
69
|
"tidy-windows",
|
|
66
70
|
"window-move-mode",
|
|
67
71
|
"window-resize-mode",
|
|
@@ -245,6 +249,18 @@ export const KEYBINDING_ACTIONS: readonly KeybindingActionDef[] = [
|
|
|
245
249
|
description: "Open layout actions.",
|
|
246
250
|
defaults: ["CmdOrCtrl+Shift+L"],
|
|
247
251
|
},
|
|
252
|
+
{
|
|
253
|
+
id: "layout-undo",
|
|
254
|
+
category: "Pane Management",
|
|
255
|
+
description: "Undo the last layout change.",
|
|
256
|
+
defaults: ["CmdOrCtrl+Z"],
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: "layout-redo",
|
|
260
|
+
category: "Pane Management",
|
|
261
|
+
description: "Redo the last undone layout change.",
|
|
262
|
+
defaults: ["CmdOrCtrl+Shift+Z"],
|
|
263
|
+
},
|
|
248
264
|
{
|
|
249
265
|
id: "tidy-windows",
|
|
250
266
|
category: "Pane Management",
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import type { DesktopDeepLinkBridge } from "../../types/desktop-deeplink";
|
|
12
12
|
import type { DesktopWindowBridge } from "../../types/desktop-window";
|
|
13
13
|
import { requestAccountManagementTab } from "../../plugins/builtin/account-management/navigation";
|
|
14
|
-
import { openTeamPane } from "../../
|
|
14
|
+
import { openTeamPane } from "../../team/pane-request";
|
|
15
15
|
import { chatController } from "../../plugins/builtin/chat/controller";
|
|
16
16
|
import { getShare, type ShareRecord } from "../../shares/api";
|
|
17
17
|
import { openShareLink, registerShareOpener } from "../../shares/open";
|
package/src/app.tsx
CHANGED
|
@@ -14,7 +14,7 @@ import { Header } from "./components/layout/header";
|
|
|
14
14
|
import { StatusBar } from "./components/layout/status-bar";
|
|
15
15
|
import { useAppChromeShown } from "./components/layout/presentation";
|
|
16
16
|
import { useLinkedLayoutSync } from "./layout-marketplace/linked-sync";
|
|
17
|
-
import { useTeamCollectionsSync } from "./
|
|
17
|
+
import { useTeamCollectionsSync } from "./team/collections-sync";
|
|
18
18
|
import { Shell } from "./components/layout/shell";
|
|
19
19
|
import { DetachedPaneShell } from "./components/layout/detached-pane-shell";
|
|
20
20
|
import { TransientLayoutProvider } from "./components/layout/transient-layout";
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
"src/app/global-shortcuts.ts": ["help"],
|
|
5
5
|
"src/app/pane-runtime/plugin-bindings.ts": ["broker-manager","layout-manager"],
|
|
6
6
|
"src/app/runtime/broker-import.ts": ["portfolio-list"],
|
|
7
|
-
"src/app/runtime/desktop-deeplink.ts": ["account-management","chat"
|
|
7
|
+
"src/app/runtime/desktop-deeplink.ts": ["account-management","chat"],
|
|
8
8
|
"src/app/runtime/startup.ts": ["chat"],
|
|
9
9
|
"src/cli/commands/market.ts": ["holders","insider","research","sec"],
|
|
10
10
|
"src/cli/commands/options-chain.ts": ["options","options-calculator"],
|
|
11
|
-
"src/cli/commands/options-leaps.ts": ["options","vol-surface"],
|
|
11
|
+
"src/cli/commands/options-leaps.ts": ["iv-history","options","vol-surface"],
|
|
12
12
|
"src/cli/commands/overview.ts": ["correlation","econ","market-movers","sectors","world-indices"],
|
|
13
13
|
"src/cli/commands/ticker.ts": ["notes"],
|
|
14
14
|
"src/cli/desktop-pane-shot.ts": ["market-valuation","time-sales"],
|
|
@@ -24,21 +24,16 @@
|
|
|
24
24
|
"src/components/command-bar/routes/root/command-items.ts": ["portfolio-list"],
|
|
25
25
|
"src/components/command-bar/routes/root/help-items.ts": ["help"],
|
|
26
26
|
"src/components/command-bar/routes/root/shortcut-items.ts": ["portfolio-list"],
|
|
27
|
-
"src/components/command-bar/workflow/builtin.ts": ["
|
|
28
|
-
"src/components/command-bar/workflow/collection-actions.ts": ["
|
|
27
|
+
"src/components/command-bar/workflow/builtin.ts": ["portfolio-list"],
|
|
28
|
+
"src/components/command-bar/workflow/collection-actions.ts": ["portfolio-list"],
|
|
29
29
|
"src/components/command-bar/workflow/ops.ts": ["correlation","portfolio-list","ticker-detail"],
|
|
30
30
|
"src/components/command-bar/workflow/tickers.ts": ["portfolio-list"],
|
|
31
|
-
"src/components/feedback-dialog.tsx": ["cloud"],
|
|
32
31
|
"src/components/form-modal/routes.ts": ["portfolio-list"],
|
|
33
32
|
"src/components/index.ts": ["portfolio-list"],
|
|
34
33
|
"src/components/layout/shell/index.tsx": ["cloud"],
|
|
35
|
-
"src/components/layout/status-bar-groups.ts": ["cloud"],
|
|
36
|
-
"src/components/layout/status-bar.tsx": ["cloud"],
|
|
37
34
|
"src/components/onboarding/use-onboarding-upgrade.ts": ["chat"],
|
|
38
35
|
"src/components/onboarding/wizard-model.ts": ["portfolio-list"],
|
|
39
36
|
"src/components/onboarding/wizard-positions.ts": ["portfolio-list"],
|
|
40
|
-
"src/layout-marketplace/gallery.tsx": ["cloud"],
|
|
41
|
-
"src/layout-marketplace/linked-sync.ts": ["cloud"],
|
|
42
37
|
"src/layout/desks.ts": ["chart-composer"],
|
|
43
38
|
"src/plugins/catalog-browser.ts": ["alerts","analytics","attention","awards","backtest","bond-calculator","catalysts","cds","central-bank-rates","changelog","chart-composer","clinical-trials","cloud","comment-letters","commodities-energy","commodity-balances","company-attention","company-kpis","connections","correlation","cot","cpi","credit-boards","credit-conditions","credit-documents","crypto-board","debt-maturities","dividend-yield","docs","doe","earnings-calls","earnings-ripple","econ","econ-statistics","equity-screener","estimate-revisions","executives","exposure","fear-greed","filing-events","fixed-income","futures","futures-curve","fx-matrix","gpu","help","insider","ipo-calendar","iv-history","jobs","jodi","kelly-sizer","layout-manager","macro-day","market-halts","market-heatmap","market-valuation","members","mna","money-markets","news","openfda","options","options-calculator","options-positioning","options-scenario","pe-band","perps","portfolio-list","power","primary-dealers","rate-path","realized-vol","relative-rotation","research","research-search","revenue-breakdown","reverse-dcf","risk-factors","scanner","seasonality","sec","sectors","short-interest","short-volume","social-mentions","supply-chain","ticker-detail","time-sales","trace-bonds","treasury-auctions","treasury-daily","vol-surface","volatility","world-indices","world-venue-map","yield-curve"],
|
|
44
39
|
"src/plugins/catalog-ui.ts": ["alerts","clinical-trials","cloud","comment-letters","custom-view","fear-greed","ipo-calendar","market-halts","market-heatmap","members","news","notes","openfda","research-search"],
|
|
@@ -55,18 +55,19 @@ import {
|
|
|
55
55
|
formatOptionDeltaCell,
|
|
56
56
|
formatOptionIvCell,
|
|
57
57
|
formatOptionQuoteCell,
|
|
58
|
-
missingExpiryMessage,
|
|
59
58
|
OPTION_MODEL_RATE,
|
|
60
59
|
OPTIONS_USAGE,
|
|
61
60
|
optionRows,
|
|
62
61
|
optionsBasisFields,
|
|
63
62
|
optionsBasisFootnote,
|
|
64
63
|
parseOptionExpiration,
|
|
65
|
-
requestListedOpenInterest,
|
|
66
64
|
valueChainAtModelSpot,
|
|
67
65
|
type OptionsChainBasis,
|
|
68
66
|
} from "./options-chain";
|
|
69
|
-
import {
|
|
67
|
+
import { missingExpiryMessage } from "../../utils/option-expiry";
|
|
68
|
+
import {
|
|
69
|
+
DEFAULT_LEAPS_CRITERIA, LEAPS_EXTRA_COLUMNS, LEAPS_USAGE, parseLeapsCriteria, runLeapsScreen, takeLeapsOptions,
|
|
70
|
+
} from "./options-leaps";
|
|
70
71
|
import type { CliResultColumn } from "../result";
|
|
71
72
|
import type { PriceHistoryResult } from "../../types/price-history";
|
|
72
73
|
import {
|
|
@@ -143,7 +144,7 @@ import { formatBasisPoints, toBasisPoints } from "../../utils/basis-points";
|
|
|
143
144
|
import { notATickerSymbol, providerMissReason } from "../../sources/provider-errors";
|
|
144
145
|
import { isNotATickerMessage, withListingSuggestion } from "../not-a-ticker";
|
|
145
146
|
import { describeSuggestion, suggestListing } from "../listing-suggestion";
|
|
146
|
-
import { splitSymbolArgs } from "../symbol-args";
|
|
147
|
+
import { splitSymbolArgs, uniqueSecuritySymbols } from "../symbol-args";
|
|
147
148
|
import { cliPerpetualContract, perpetualContractStats, type PerpetualContractPayload } from "../perpetual-contract";
|
|
148
149
|
import { subMinuteDataMessage } from "../known-gaps";
|
|
149
150
|
import { secRegistrantMismatchMessage, SecRegistrantMismatchError, areDifferentCompanies } from "../../sources/sec-registrant";
|
|
@@ -224,9 +225,13 @@ function parseNewsFeed(value: string | undefined): NewsQuery["feed"] | undefined
|
|
|
224
225
|
return value && VALID_NEWS_FEEDS.has(value as NewsFeed) ? value as NewsQuery["feed"] : undefined;
|
|
225
226
|
}
|
|
226
227
|
|
|
227
|
-
/**
|
|
228
|
+
/**
|
|
229
|
+
* The symbols of a command's arguments, one each however they were separated
|
|
230
|
+
* (spaces, commas or pasted lines), and each security once however its case or
|
|
231
|
+
* share class was written: `AAPL aapl BRK.B BRK-B` is AAPL and BRK.B.
|
|
232
|
+
*/
|
|
228
233
|
function normalizeSymbols(args: string[]): string[] {
|
|
229
|
-
return splitSymbolArgs(args).map((symbol) => symbol.toUpperCase());
|
|
234
|
+
return uniqueSecuritySymbols(splitSymbolArgs(args).map((symbol) => symbol.toUpperCase()));
|
|
230
235
|
}
|
|
231
236
|
|
|
232
237
|
/** What JSON metadata says about the listing a command resolved. */
|
|
@@ -1472,10 +1477,9 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1472
1477
|
const expiration = takeOption(args, "--expiration");
|
|
1473
1478
|
const exchangeOption = takeOption(args, "--exchange");
|
|
1474
1479
|
if (leapsOptions.leaps) {
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
}
|
|
1478
|
-
const criteria = parseLeapsCriteria(leapsOptions.raw);
|
|
1480
|
+
// With --leaps, --expiration is a window of one expiry.
|
|
1481
|
+
const expirationGiven = expiration != null || rawArgs.some((arg) => arg === "--expiration" || arg.startsWith("--expiration="));
|
|
1482
|
+
const criteria = parseLeapsCriteria({ ...leapsOptions.raw, ...expirationGiven ? { "--expiration": expiration ?? "" } : {} });
|
|
1479
1483
|
if ("error" in criteria) ctx.fail(criteria.error, `Usage: gloomberb ${LEAPS_USAGE}`);
|
|
1480
1484
|
const symbols = normalizeSymbols(args);
|
|
1481
1485
|
if (symbols.length === 0) ctx.fail("--leaps needs at least one symbol.", `Usage: gloomberb ${LEAPS_USAGE}`);
|
|
@@ -1505,8 +1509,6 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1505
1509
|
const quotePromise = loadListingQuote(market.dataProvider, listing);
|
|
1506
1510
|
const fate = listingFateLoader(listing, market, quotePromise);
|
|
1507
1511
|
const inputsPromise = loadOptionModelInputs(market.dataProvider, listing, quotePromise, refresh);
|
|
1508
|
-
// OPX's listing dates the chain's open interest; asked for alongside the chain when the expiry is known.
|
|
1509
|
-
const listedOpenInterest = expirationDate != null ? requestListedOpenInterest(symbol, expirationDate) : null;
|
|
1510
1512
|
const { result, chain: loaded } = await loadForListing(listing, market, ctx, async () => {
|
|
1511
1513
|
const cached = await market.dataProvider.getCachedQuery?.("getOptionsChain", [symbol, exchange, expirationDate, undefined])
|
|
1512
1514
|
.load({ force: refresh });
|
|
@@ -1539,7 +1541,7 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1539
1541
|
// IV and delta are valued at one spot and instant, named under the table and on every exported row.
|
|
1540
1542
|
const { chain, spot: modelSpot } = valueChainAtModelSpot(loaded, inputs, Date.now(), identity.exchange);
|
|
1541
1543
|
const basis: OptionsChainBasis = { spot: modelSpot, dividendYield: inputs.dividendYield ?? null,
|
|
1542
|
-
openInterest: await loadOptionsChainOpenInterest(symbol, chain
|
|
1544
|
+
openInterest: await loadOptionsChainOpenInterest(symbol, chain) };
|
|
1543
1545
|
const basisFields = optionsBasisFields(basis);
|
|
1544
1546
|
const currency = [...chain.calls, ...chain.puts].find((contract) => contract.currency?.trim())?.currency.trim().toUpperCase()
|
|
1545
1547
|
?? quote?.currency ?? null;
|
|
@@ -1815,10 +1817,10 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1815
1817
|
options: [
|
|
1816
1818
|
{
|
|
1817
1819
|
flags: "--expiration <YYYY-MM-DD|unix>",
|
|
1818
|
-
description: "Expiration as a date (2028-01-21) or Unix seconds; defaults to the nearest one, and lists the others",
|
|
1820
|
+
description: "Expiration as a date (2028-01-21) or Unix seconds; defaults to the nearest one, and lists the others. With --leaps: rank that expiry alone",
|
|
1819
1821
|
},
|
|
1820
1822
|
EXCHANGE_OPTION,
|
|
1821
|
-
{ flags: "--leaps", description: "Rank contracts
|
|
1823
|
+
{ flags: "--leaps", description: "Rank long-dated contracts across the symbols given, as a stock replacement; more than a year out unless a window says otherwise" },
|
|
1822
1824
|
{ flags: "--side <calls|puts>", description: `With --leaps: the side to rank (default ${DEFAULT_LEAPS_CRITERIA.side}s)` },
|
|
1823
1825
|
{
|
|
1824
1826
|
flags: "--delta <low-high>",
|
|
@@ -1829,6 +1831,17 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1829
1831
|
description: `With --leaps: widest bid/ask spread, in percent of the midpoint (default ${DEFAULT_LEAPS_CRITERIA.maxSpreadPercent})`,
|
|
1830
1832
|
},
|
|
1831
1833
|
{ flags: "--min-oi <contracts>", description: `With --leaps: least open interest (default ${DEFAULT_LEAPS_CRITERIA.minOpenInterest})` },
|
|
1834
|
+
{
|
|
1835
|
+
flags: "--min-days <days>",
|
|
1836
|
+
description: `With --leaps: fewest calendar days to expiry (default ${DEFAULT_LEAPS_CRITERIA.minDays}); 180 reaches six months out`,
|
|
1837
|
+
},
|
|
1838
|
+
{ flags: "--max-days <days>", description: "With --leaps: most calendar days to expiry; --min-days 365 --max-days 730 is 12 to 24 months" },
|
|
1839
|
+
{ flags: "--expiry-from <YYYY-MM-DD>", description: "With --leaps: first expiry date kept; replaces the one-year floor" },
|
|
1840
|
+
{ flags: "--expiry-to <YYYY-MM-DD>", description: "With --leaps: last expiry date kept" },
|
|
1841
|
+
{
|
|
1842
|
+
flags: "--columns <list>",
|
|
1843
|
+
description: `With --leaps: add figures, comma-separated: ${LEAPS_EXTRA_COLUMNS.join(", ")}`,
|
|
1844
|
+
},
|
|
1832
1845
|
{
|
|
1833
1846
|
flags: "--sort <order>",
|
|
1834
1847
|
description: "With --leaps: carry (extrinsic per year, lowest first; the default), spread, oi, delta, expiry or symbol",
|
|
@@ -1837,7 +1850,10 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1837
1850
|
sections: [{
|
|
1838
1851
|
title: "LEAPS",
|
|
1839
1852
|
lines: [
|
|
1840
|
-
"--leaps reads
|
|
1853
|
+
"--leaps reads each symbol's expiries in the window and keeps the liquid contracts in the delta band, ranked by extrinsic per year: time value (midpoint less intrinsic) as a percent of spot, per year to expiry.",
|
|
1854
|
+
"--min-days, --max-days, --expiry-from and --expiry-to set the window together; without a lower bound it starts a year out. Each symbol reads at most 8 expiries, the ones nearest the window's start, and a warning names any it left.",
|
|
1855
|
+
"--columns adds iv (the contract's implied vol), ivr (the underlying's IV rank), cost (midpoint x 100 per contract), breakeven (at the midpoint, and its distance from spot), next-earnings, ex-dividend and carry-net "
|
|
1856
|
+
+ `(extrinsic per year net of the ${Number((OPTION_MODEL_RATE * 100).toFixed(2))}% rate on the strike and the dividend yield). A figure a symbol lacks is blank, with a warning.`,
|
|
1841
1857
|
"A symbol without LEAPS, a quote or a chain is named in a warning and the others still rank. docs/research-data.md defines each figure.",
|
|
1842
1858
|
],
|
|
1843
1859
|
}],
|
|
@@ -1847,6 +1863,8 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1847
1863
|
"options AAPL:NASDAQ --json",
|
|
1848
1864
|
"options AAPL MSFT NVDA GOOGL AMZN META --leaps",
|
|
1849
1865
|
"options SPY QQQ --leaps --side puts --delta 0.20-0.40 --csv",
|
|
1866
|
+
"options MSFT --leaps --min-days 365 --max-days 730",
|
|
1867
|
+
"options AAPL MSFT NVDA --leaps --min-days 180 --columns iv,ivr,cost,breakeven,carry-net",
|
|
1850
1868
|
],
|
|
1851
1869
|
},
|
|
1852
1870
|
execute: runOptions,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OptionContract, OptionsChain, Quote } from "../../types/financials";
|
|
1
|
+
import type { OptionContract, OptionsChain, Quote, TickerFinancials } from "../../types/financials";
|
|
2
2
|
import type { DataProvider } from "../../types/data-provider";
|
|
3
3
|
import type { CliListing } from "../listing-arg";
|
|
4
4
|
import { cliStyles, cliTerminalWidth, wrapText } from "../../utils/cli-output";
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
formatModelSpotPrice, modelSpotLabel, optionModelSpot, type OptionModelSpot,
|
|
10
10
|
} from "../../plugins/builtin/options/model-spot";
|
|
11
11
|
import {
|
|
12
|
-
chainOpenInterestAsOf, loadListedOpenInterest, openInterestAsOfLabel, type OpenInterestAsOf,
|
|
12
|
+
chainOpenInterestAsOf, loadListedOpenInterest, openInterestAsOfLabel, type OpenInterestAsOf, statedOpenInterestAsOf,
|
|
13
13
|
} from "../../plugins/builtin/options/open-interest-as-of";
|
|
14
14
|
import { DEFAULT_OPTION_CALC_DRAFT } from "../../plugins/builtin/options-calculator/model";
|
|
15
|
-
import { expiryIsoDate,
|
|
15
|
+
import { expiryIsoDate, parseOptionExpiration } from "../../utils/option-expiry";
|
|
16
16
|
|
|
17
17
|
export const OPTIONS_USAGE = "options <symbol> [--expiration <YYYY-MM-DD|unix>]";
|
|
18
18
|
|
|
@@ -29,22 +29,6 @@ function listedDates(expirations: readonly number[]): string[] {
|
|
|
29
29
|
return [...new Set(expirations.map((expiration) => expiryIsoDate(expiration)).filter(Boolean))].sort();
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
* Why a chain request for one expiry came back without contracts, and which
|
|
34
|
-
* expiries it could have been: the few nearest the requested date, so a typo or
|
|
35
|
-
* an unlisted LEAPS date points at the neighbours to pick.
|
|
36
|
-
*/
|
|
37
|
-
export function missingExpiryMessage(
|
|
38
|
-
symbol: string,
|
|
39
|
-
requested: number,
|
|
40
|
-
expirations: readonly number[],
|
|
41
|
-
): { message: string; details?: string } {
|
|
42
|
-
return {
|
|
43
|
-
message: missingExpiryText(requested, expirations, symbol),
|
|
44
|
-
details: `Run gloomberb options ${symbol} to list every expiry (--json carries them too).`,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
32
|
const EXPIRY_LIST_LIMIT = 40;
|
|
49
33
|
const EXPIRY_LIST_WIDTH = 100;
|
|
50
34
|
const EXPIRY_GAP = 2;
|
|
@@ -113,16 +97,30 @@ function optionModelFigures(chain: OptionsChain, inputs: OptionModelInputs): Map
|
|
|
113
97
|
return figures;
|
|
114
98
|
}
|
|
115
99
|
|
|
116
|
-
/**
|
|
100
|
+
/** The underlying's financials, as DES reads them: its dividend yield, ex-dividend date and next report. Null when they cannot be read. */
|
|
101
|
+
export function loadUnderlyingFinancials(
|
|
102
|
+
dataProvider: Pick<DataProvider, "getTickerFinancials">,
|
|
103
|
+
listing: CliListing,
|
|
104
|
+
refresh: boolean,
|
|
105
|
+
): Promise<TickerFinancials | null> {
|
|
106
|
+
return Promise.resolve().then(() => dataProvider.getTickerFinancials(
|
|
107
|
+
listing.request.symbol, listing.request.exchange, { cacheMode: refresh ? "refresh" : "default" },
|
|
108
|
+
)).catch(() => null);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Spot and dividend yield for IV and delta, read from the underlying the way the
|
|
113
|
+
* options pane does; missing ones stay missing. `financials` shares one read of
|
|
114
|
+
* them with a caller that needs more of them.
|
|
115
|
+
*/
|
|
117
116
|
export async function loadOptionModelInputs(
|
|
118
117
|
dataProvider: Pick<DataProvider, "getTickerFinancials">,
|
|
119
118
|
listing: CliListing,
|
|
120
119
|
quote: Promise<Quote | null>,
|
|
121
120
|
refresh: boolean,
|
|
121
|
+
underlying: Promise<TickerFinancials | null> = loadUnderlyingFinancials(dataProvider, listing, refresh),
|
|
122
122
|
): Promise<OptionModelInputs> {
|
|
123
|
-
const financials = await
|
|
124
|
-
listing.request.symbol, listing.request.exchange, { cacheMode: refresh ? "refresh" : "default" },
|
|
125
|
-
)).catch(() => null);
|
|
123
|
+
const financials = await underlying;
|
|
126
124
|
const current = [await quote, financials?.quote]
|
|
127
125
|
.find((candidate) => candidate && candidate.stale !== true && isFiniteNumber(candidate.price) && candidate.price > 0);
|
|
128
126
|
const dividendYield = financials?.fundamentals?.dividendYield;
|
|
@@ -161,21 +159,19 @@ export function valueChainAtModelSpot(
|
|
|
161
159
|
};
|
|
162
160
|
}
|
|
163
161
|
|
|
164
|
-
/**
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
162
|
+
/**
|
|
163
|
+
* The open-interest date of the chain's expiry: the chain's own, else (a chain
|
|
164
|
+
* that does not say) from OPX's listing of it; null when the listing cannot be read.
|
|
165
|
+
*/
|
|
166
|
+
export async function loadOptionsChainOpenInterest(symbol: string, chain: OptionsChain): Promise<OpenInterestAsOf | null> {
|
|
167
|
+
const stated = statedOpenInterestAsOf(chain);
|
|
168
|
+
if (stated) return stated;
|
|
168
169
|
const expiration = [...chain.calls, ...chain.puts][0]?.expiration;
|
|
169
170
|
if (expiration == null) return null;
|
|
170
|
-
const payload = await
|
|
171
|
+
const payload = await loadListedOpenInterest(symbol, expiryIsoDate(expiration));
|
|
171
172
|
return payload ? chainOpenInterestAsOf(chain, payload) : null;
|
|
172
173
|
}
|
|
173
174
|
|
|
174
|
-
/** OPX's listing of an expiry, requested while the chain loads. */
|
|
175
|
-
export function requestListedOpenInterest(symbol: string, expiration: number) {
|
|
176
|
-
return loadListedOpenInterest(symbol, expiryIsoDate(expiration));
|
|
177
|
-
}
|
|
178
|
-
|
|
179
175
|
/**
|
|
180
176
|
* The line under the chain: `Greeks at spot 89.79 (close Mon 12 Oct 20:00 UTC), dividend yield 2.34%, rate 4% · OI as of Fri 9 Oct`.
|
|
181
177
|
* An open interest whose date cannot be established says why.
|