gloomberb 0.17.1 → 0.17.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.
Files changed (237) hide show
  1. package/package.json +1 -1
  2. package/src/{plugins/builtin/cloud → account}/auth-dialog.tsx +7 -7
  3. package/src/api-client/earnings.ts +4 -2
  4. package/src/api-client/futures-curve.ts +16 -0
  5. package/src/api-client/index.ts +18 -8
  6. package/src/api-client/types.ts +3 -1
  7. package/src/app/global-shortcuts.ts +10 -0
  8. package/src/app/keybindings/actions.ts +16 -0
  9. package/src/app/runtime/desktop-deeplink.ts +1 -1
  10. package/src/app.tsx +1 -1
  11. package/src/architecture/core-builtin-allowlist.json +4 -9
  12. package/src/cli/commands/market.ts +94 -25
  13. package/src/cli/commands/options-chain.ts +29 -33
  14. package/src/cli/commands/options-leaps.ts +462 -63
  15. package/src/cli/commands/search.ts +53 -9
  16. package/src/cli/commands/ticker.ts +100 -21
  17. package/src/cli/desktop-pane-shot.ts +34 -9
  18. package/src/cli/fx-pair.ts +17 -0
  19. package/src/cli/glossary.ts +134 -6
  20. package/src/cli/history-rows.ts +16 -4
  21. package/src/cli/index.ts +44 -12
  22. package/src/cli/known-gaps.ts +43 -2
  23. package/src/cli/listing-arg.ts +19 -4
  24. package/src/cli/listing-suggestion.ts +33 -2
  25. package/src/cli/pane-functions/catalog.ts +6 -3
  26. package/src/cli/pane-functions/dom.ts +56 -8
  27. package/src/cli/pane-functions/freshness.ts +48 -5
  28. package/src/cli/pane-functions/function-help.ts +2 -2
  29. package/src/cli/pane-functions/headless.ts +85 -19
  30. package/src/cli/pane-functions/index.ts +65 -7
  31. package/src/cli/pane-functions/market-session.ts +69 -6
  32. package/src/cli/pane-functions/report-notices.ts +15 -0
  33. package/src/cli/pane-functions/resolver.ts +4 -2
  34. package/src/cli/pane-functions/screenshot.ts +14 -1
  35. package/src/cli/scroll-truncation.ts +5 -1
  36. package/src/cli/symbol-args.ts +60 -3
  37. package/src/cli/symbol-list.ts +19 -7
  38. package/src/cli/unknown-command.ts +70 -0
  39. package/src/components/chart/native/surface/visibility.ts +37 -7
  40. package/src/components/command-bar/workflow/builtin.ts +2 -2
  41. package/src/components/command-bar/workflow/collection-actions.ts +1 -1
  42. package/src/components/feedback-dialog.tsx +1 -1
  43. package/src/components/layout/header.tsx +348 -102
  44. package/src/components/layout/shell/index.tsx +1 -1
  45. package/src/components/layout/status-bar-groups.ts +1 -1
  46. package/src/components/layout/status-bar.tsx +2 -2
  47. package/src/components/markdown-text.tsx +50 -64
  48. package/src/components/market-board/index.tsx +12 -6
  49. package/src/components/reader/article-reader.tsx +1 -1
  50. package/src/components/reader/detail-reader.tsx +37 -12
  51. package/src/components/reader/reader-text.tsx +32 -17
  52. package/src/components/ticker/badge/index.tsx +2 -0
  53. package/src/components/ui/display.tsx +6 -1
  54. package/src/components/ui/external-link.tsx +10 -2
  55. package/src/data/memory-resource-store.ts +1 -1
  56. package/src/data/resource-store.ts +13 -1
  57. package/src/docs/snapshot.json +13 -13
  58. package/src/i18n/es.ts +1 -0
  59. package/src/i18n/ja.ts +1 -0
  60. package/src/i18n/ko.ts +1 -0
  61. package/src/i18n/zh-cn.ts +1 -0
  62. package/src/i18n/zh-tw.ts +1 -0
  63. package/src/layout-marketplace/gallery.tsx +2 -2
  64. package/src/layout-marketplace/linked-sync.ts +1 -1
  65. package/src/market-data/futures-contract.ts +10 -0
  66. package/src/market-data/market/format.ts +7 -2
  67. package/src/market-data/market/freshness.ts +28 -1
  68. package/src/market-data/published-apac-sessions.ts +12 -2
  69. package/src/market-data/quotes/regular-session-valuation.ts +32 -1
  70. package/src/market-data/quotes/resolution.ts +2 -0
  71. package/src/plugins/builtin/account-management/pane.tsx +17 -13
  72. package/src/plugins/builtin/attention/headless.ts +19 -10
  73. package/src/plugins/builtin/attention/model.ts +22 -0
  74. package/src/plugins/builtin/bond-calculator/pane.tsx +2 -1
  75. package/src/plugins/builtin/changelog/index.tsx +11 -8
  76. package/src/plugins/builtin/chart-composer/editor/desktop-view.tsx +2 -0
  77. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  78. package/src/plugins/builtin/chart-composer/presets.ts +33 -1
  79. package/src/plugins/builtin/chat/attachments/desktop.tsx +2 -1
  80. package/src/plugins/builtin/chat/attachments/model.ts +5 -3
  81. package/src/plugins/builtin/chat/channel-labels.ts +2 -2
  82. package/src/plugins/builtin/chat/content/channel-navigation.ts +1 -1
  83. package/src/plugins/builtin/chat/content/footer.tsx +6 -0
  84. package/src/plugins/builtin/chat/content/index.tsx +2 -2
  85. package/src/plugins/builtin/chat/content/open-targets.ts +13 -4
  86. package/src/plugins/builtin/chat/content/transcript.tsx +2 -0
  87. package/src/plugins/builtin/chat/content/whole-message-copy.ts +70 -0
  88. package/src/plugins/builtin/chat/conversations.ts +1 -1
  89. package/src/plugins/builtin/chat/index.tsx +2 -2
  90. package/src/plugins/builtin/chat/layout.ts +91 -31
  91. package/src/plugins/builtin/chat/markdown.ts +94 -0
  92. package/src/plugins/builtin/chat/message/desktop.tsx +3 -2
  93. package/src/plugins/builtin/chat/message/inline-tokens.tsx +197 -80
  94. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  95. package/src/plugins/builtin/chat/message/terminal.tsx +4 -4
  96. package/src/plugins/builtin/chat/share/flow.tsx +1 -1
  97. package/src/plugins/builtin/chat/sidebar-rows.ts +1 -1
  98. package/src/plugins/builtin/chat/sidebar.tsx +2 -2
  99. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +2 -2
  100. package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
  101. package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
  102. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +1 -1
  103. package/src/plugins/builtin/cloud/team/acm-tab.tsx +3 -3
  104. package/src/plugins/builtin/cloud/team/capability.ts +2 -2
  105. package/src/plugins/builtin/cloud/team/command.ts +4 -4
  106. package/src/plugins/builtin/cloud/team/module.tsx +3 -3
  107. package/src/plugins/builtin/cloud/team/pane-model.ts +2 -2
  108. package/src/plugins/builtin/cloud/team/pane-sections.tsx +1 -1
  109. package/src/plugins/builtin/cloud/team/pane-ui.tsx +1 -1
  110. package/src/plugins/builtin/cloud/team/pane.tsx +3 -3
  111. package/src/plugins/builtin/cloud/team/status-widget.tsx +3 -3
  112. package/src/plugins/builtin/cloud/team/team-state-host.ts +1 -1
  113. package/src/plugins/builtin/cloud/team/views.ts +2 -2
  114. package/src/plugins/builtin/cloud/thesis/board-pane.tsx +1 -1
  115. package/src/plugins/builtin/cloud/thesis/detail.tsx +1 -1
  116. package/src/plugins/builtin/cloud/thesis/exposure.ts +18 -2
  117. package/src/plugins/builtin/cloud/thesis/ticker-tab.tsx +1 -1
  118. package/src/plugins/builtin/cloud/upgrade-command.ts +1 -1
  119. package/src/plugins/builtin/company-attention/headless.ts +8 -5
  120. package/src/plugins/builtin/company-attention/model.ts +53 -2
  121. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  122. package/src/plugins/builtin/congress-trades/detail.tsx +7 -4
  123. package/src/plugins/builtin/congress-trades/pane.tsx +3 -1
  124. package/src/plugins/builtin/correlation/relationship/pane.tsx +3 -7
  125. package/src/plugins/builtin/cot/client.ts +12 -4
  126. package/src/plugins/builtin/cot/pane.tsx +2 -2
  127. package/src/plugins/builtin/crypto-board/model.ts +7 -3
  128. package/src/plugins/builtin/custom-view/pane.tsx +2 -2
  129. package/src/plugins/builtin/debt-maturities/pane.tsx +1 -6
  130. package/src/plugins/builtin/debug/index.tsx +15 -15
  131. package/src/plugins/builtin/dividend-yield/client.ts +8 -1
  132. package/src/plugins/builtin/dividend-yield/headless.ts +11 -2
  133. package/src/plugins/builtin/dividend-yield/pane.tsx +8 -5
  134. package/src/plugins/builtin/docs/blocks.tsx +13 -2
  135. package/src/plugins/builtin/docs/index.tsx +5 -3
  136. package/src/plugins/builtin/docs/pane.tsx +24 -6
  137. package/src/plugins/builtin/docs/targets.ts +5 -3
  138. package/src/plugins/builtin/earnings/board-model.ts +18 -1
  139. package/src/plugins/builtin/earnings/board-table.ts +18 -3
  140. package/src/plugins/builtin/earnings/headless.ts +13 -2
  141. package/src/plugins/builtin/earnings/history-model.ts +1 -1
  142. package/src/plugins/builtin/earnings/history-table.ts +2 -2
  143. package/src/plugins/builtin/earnings-calls/data.ts +22 -0
  144. package/src/plugins/builtin/earnings-calls/pane.tsx +24 -14
  145. package/src/plugins/builtin/equity-screener/headless.ts +4 -3
  146. package/src/plugins/builtin/equity-screener/live.ts +27 -1
  147. package/src/plugins/builtin/equity-screener/pane.tsx +21 -2
  148. package/src/plugins/builtin/fear-greed/charts.tsx +5 -1
  149. package/src/plugins/builtin/fear-greed/pane.tsx +1 -1
  150. package/src/plugins/builtin/futures/index.tsx +32 -4
  151. package/src/plugins/builtin/futures/model.ts +65 -3
  152. package/src/plugins/builtin/futures/table.tsx +31 -11
  153. package/src/plugins/builtin/futures/use-board-terms.ts +89 -0
  154. package/src/plugins/builtin/help/feedback-tab.tsx +1 -1
  155. package/src/plugins/builtin/holders/pane.tsx +6 -1
  156. package/src/plugins/builtin/mna/client.ts +12 -2
  157. package/src/plugins/builtin/mna/detail.tsx +2 -2
  158. package/src/plugins/builtin/mna/pane.tsx +2 -2
  159. package/src/plugins/builtin/notes/index.tsx +1 -1
  160. package/src/plugins/builtin/notes/owner.tsx +7 -6
  161. package/src/plugins/builtin/notes/ticker-notes-tab.tsx +2 -3
  162. package/src/plugins/builtin/options/carry.ts +36 -0
  163. package/src/plugins/builtin/options/open-interest-as-of.ts +16 -5
  164. package/src/plugins/builtin/options/view.tsx +19 -7
  165. package/src/plugins/builtin/options-calculator/pane.tsx +58 -4
  166. package/src/plugins/builtin/options-calculator/premium-history.ts +34 -0
  167. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -5
  168. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -3
  169. package/src/plugins/builtin/portfolio-list/cli/shared.ts +9 -0
  170. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +6 -3
  171. package/src/plugins/builtin/portfolio-list/mutations.ts +1 -1
  172. package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +1 -1
  173. package/src/plugins/builtin/research/corporate-actions-pane.tsx +4 -2
  174. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +2 -9
  175. package/src/plugins/builtin/research/event-model.ts +21 -4
  176. package/src/plugins/builtin/research/headless-schema.ts +31 -1
  177. package/src/plugins/builtin/research/relative-valuation-columns.ts +289 -0
  178. package/src/plugins/builtin/research/relative-valuation-currency.ts +164 -0
  179. package/src/plugins/builtin/research/relative-valuation-headless.ts +94 -11
  180. package/src/plugins/builtin/research/relative-valuation-model.ts +4 -1
  181. package/src/plugins/builtin/research/relative-valuation-pane.tsx +13 -4
  182. package/src/plugins/builtin/research/relative-valuation-peers.ts +302 -0
  183. package/src/plugins/builtin/research-search/data.ts +11 -0
  184. package/src/plugins/builtin/research-search/pane.tsx +13 -2
  185. package/src/plugins/builtin/scanner/hilo-pane.tsx +7 -17
  186. package/src/plugins/builtin/seasonality/client.ts +23 -0
  187. package/src/plugins/builtin/seasonality/pane.tsx +4 -2
  188. package/src/plugins/builtin/sectors/index.tsx +7 -3
  189. package/src/plugins/builtin/series-inventory.ts +1 -0
  190. package/src/plugins/builtin/shared/earnings-report.ts +10 -4
  191. package/src/plugins/builtin/shared/report-freshness.ts +6 -2
  192. package/src/plugins/builtin/short-interest/headless.ts +13 -3
  193. package/src/plugins/builtin/short-interest/index.tsx +1 -1
  194. package/src/plugins/builtin/short-interest/model.ts +62 -3
  195. package/src/plugins/builtin/short-interest/pane.tsx +2 -2
  196. package/src/plugins/builtin/short-interest/schedule.ts +107 -0
  197. package/src/plugins/builtin/short-interest/watch-client.ts +54 -8
  198. package/src/plugins/builtin/short-interest/watch-headless.ts +8 -3
  199. package/src/plugins/builtin/short-interest/watch-model.ts +52 -23
  200. package/src/plugins/builtin/short-interest/watch-pane.tsx +1 -1
  201. package/src/plugins/builtin/supply-chain/flow.tsx +14 -4
  202. package/src/plugins/builtin/themes/headless.ts +1 -1
  203. package/src/plugins/builtin/thirteenf/overlap-pane.tsx +23 -27
  204. package/src/plugins/builtin/ticker-detail/index.tsx +1 -1
  205. package/src/plugins/builtin/ticker-detail/overview/model.ts +4 -3
  206. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +4 -4
  207. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +9 -4
  208. package/src/plugins/builtin/vol-surface/pane.tsx +16 -1
  209. package/src/plugins/builtin/volatility/chart-model.ts +1 -1
  210. package/src/plugins/builtin/volatility/charts.tsx +1 -1
  211. package/src/renderers/dom/styles.css +10 -2
  212. package/src/sources/gloomberb-cloud/normalizers.ts +3 -0
  213. package/src/sources/provider-router/batches.ts +2 -1
  214. package/src/sources/provider-router/cache.ts +3 -0
  215. package/src/sync/baseline.ts +19 -14
  216. package/src/sync/controller.ts +140 -40
  217. package/src/sync/core-contributors.ts +256 -32
  218. package/src/sync/types.ts +44 -0
  219. package/src/{plugins/builtin/cloud/team → team}/collections-sync.ts +6 -6
  220. package/src/{plugins/builtin/cloud/team → team}/collections.ts +3 -3
  221. package/src/{plugins/builtin/cloud/team → team}/model.ts +2 -2
  222. package/src/{plugins/builtin/cloud/team → team}/pane-request.ts +1 -1
  223. package/src/{plugins/builtin/cloud/team → team}/store.ts +3 -3
  224. package/src/time-series/resolve.ts +19 -2
  225. package/src/types/data-provider.ts +11 -0
  226. package/src/types/financials.ts +46 -4
  227. package/src/types/headless.ts +43 -0
  228. package/src/types/plugin.ts +7 -0
  229. package/src/utils/decimal-comma.ts +18 -0
  230. package/src/utils/declared-dividends.ts +39 -0
  231. package/src/utils/exchanges.ts +7 -0
  232. package/src/utils/forward-dividend.ts +62 -9
  233. package/src/utils/fundamentals.ts +13 -2
  234. package/src/utils/markdown.ts +180 -0
  235. package/src/utils/market-capitalization.ts +22 -0
  236. package/src/utils/option-expiry.ts +16 -0
  237. package/src/utils/sec.ts +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gloomberb",
3
- "version": "0.17.1",
3
+ "version": "0.17.3",
4
4
  "packageManager": "bun@1.3.11",
5
5
  "description": "Market research and portfolio tracker for the terminal",
6
6
  "module": "src/public/index.ts",
@@ -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 "../../../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 "../../../account/auth-form";
14
- import type { AccountMode } from "../../../account/auth-model";
15
- import { CloudVerificationPanel } from "../../../account/verification-panel";
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 {
@@ -30,6 +30,22 @@ export interface FuturesContractTermsPayload {
30
30
  inDelivery: boolean
31
31
  /** The newest exchange settlement confirmed for the contract, with its session. */
32
32
  settlement: { price: number; date: string } | null
33
+ /**
34
+ * For a continuous symbol (CL=F) or a front generic: the nearest contract
35
+ * still trading when the symbol has rolled past it to the most active one,
36
+ * with its last trade; null when it prices the nearest itself. Absent from
37
+ * servers that do not send it.
38
+ */
39
+ nearest?: FuturesNearestContract | null
40
+ }
41
+
42
+ /** The nearest contract still trading, which a continuous symbol has rolled past (CLX26 under CL=F on Dec). */
43
+ interface FuturesNearestContract {
44
+ contract: string
45
+ contractSymbol: string
46
+ /** YYYY-MM */
47
+ deliveryMonth: string
48
+ lastTrade: string | null
33
49
  }
34
50
 
35
51
  /** Gloom Cloud futures prices retain provider denomination and explicit quote units. */
@@ -12,7 +12,7 @@ import {
12
12
  parseCloudLayoutEntry,
13
13
  parseCloudLayoutList,
14
14
  } from "../layout-marketplace/cloud";
15
- import type { SyncSnapshot } from "../sync/types";
15
+ import { SyncRevisionConflictError, type SyncSnapshot } from "../sync/types";
16
16
  import { withDeadline } from "../utils/async-deadline";
17
17
  import { CloudASKGApi } from "./askg";
18
18
  import { CloudAuthApi } from "./auth";
@@ -450,14 +450,24 @@ class GloomApiClient {
450
450
  return this.request<CloudSyncSnapshotResponse>("/sync/snapshot", { method: "GET" });
451
451
  }
452
452
 
453
+ /**
454
+ * `baseRevision` goes out as If-Match: the server ignores the body field and
455
+ * would replace a snapshot another device wrote since this one pulled. With
456
+ * the header it refuses that write (409), raised as SyncRevisionConflictError
457
+ * so the controller pulls again instead.
458
+ */
453
459
  async putSyncSnapshot(snapshot: SyncSnapshot, options?: { baseRevision?: number | null }): Promise<CloudSyncPushResponse> {
454
- return this.request<CloudSyncPushResponse>("/sync/snapshot", {
455
- method: "PUT",
456
- body: JSON.stringify({
457
- snapshot,
458
- baseRevision: options?.baseRevision ?? null,
459
- }),
460
- });
460
+ const baseRevision = options?.baseRevision ?? null;
461
+ try {
462
+ return await this.request<CloudSyncPushResponse>("/sync/snapshot", {
463
+ method: "PUT",
464
+ ...(baseRevision !== null ? { headers: { "If-Match": String(baseRevision) } } : {}),
465
+ body: JSON.stringify({ snapshot, baseRevision }),
466
+ });
467
+ } catch (error) {
468
+ if (error instanceof ApiRequestError && error.status === 409) throw new SyncRevisionConflictError(error.message);
469
+ throw error;
470
+ }
461
471
  }
462
472
 
463
473
  async getMarketplaceLayout(
@@ -459,10 +459,12 @@ export interface CloudPricePointPayload {
459
459
  low?: number;
460
460
  close: number;
461
461
  volume?: number;
462
- /** A coin's daily bar for the UTC day still forming when served; it has no volume yet. */
462
+ /** A daily bar still forming when served: a coin's UTC day, or the trade date a future is trading. */
463
463
  partial?: boolean;
464
464
  /** A coin's daily bar whose prices were rebuilt from hourly bars; a day the source left empty has no volume. */
465
465
  filled?: boolean;
466
+ /** A future's closed session whose settlement is not confirmed yet: it closes on the last trade. */
467
+ unsettled?: boolean;
466
468
  }
467
469
 
468
470
  type CloudEconImpact = "high" | "medium" | "low";
@@ -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 "../../plugins/builtin/cloud/team/pane-request";
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 "./plugins/builtin/cloud/team/collections-sync";
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","cloud"],
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": ["cloud","portfolio-list"],
28
- "src/components/command-bar/workflow/collection-actions.ts": ["cloud","portfolio-list"],
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"],
@@ -24,6 +24,7 @@ import { withCliServices, withMarketData } from "../context";
24
24
  import { isoDate, parsePositiveInt, requireOneArg, takeFlag, takeOption } from "./command-utils";
25
25
  import type { BuiltinCliCommandDef } from "../command-options";
26
26
  import { CLI_COMMAND_GROUPS, TABLE_SECTION_OPTION } from "../help";
27
+ import { CLI_EXIT_CODES } from "../exit-codes";
27
28
  import {
28
29
  formatChangePercentCell,
29
30
  formatCountCell,
@@ -44,7 +45,9 @@ import {
44
45
  import { getPublishedUsEquityCalendarYears, getPublishedUsEquitySession } from "../../market-data/published-us-sessions";
45
46
  import { isQuoteStaleForCurrentSession } from "../../market-data/quotes/freshness";
46
47
  import { isOptionsUnavailableError, listedOptionsAlternative, optionsUnavailableTitle } from "../../market-data/options-alternatives";
47
- import { cliFuturesContract, futuresContractMetrics, fundamentalsReportTables, renderFundamentalsReport } from "./ticker";
48
+ import {
49
+ cliFuturesContract, futuresContractMetrics, fundamentalsReportTables, loadForwardYieldDeclarations, renderFundamentalsReport,
50
+ } from "./ticker";
48
51
  import type { FuturesContractTermsPayload } from "../../api-client/futures-curve";
49
52
  import { futuresSettleText, isFuturesInstrument } from "../../market-data/futures-contract";
50
53
  import {
@@ -55,18 +58,20 @@ import {
55
58
  formatOptionDeltaCell,
56
59
  formatOptionIvCell,
57
60
  formatOptionQuoteCell,
58
- missingExpiryMessage,
59
61
  OPTION_MODEL_RATE,
60
62
  OPTIONS_USAGE,
61
63
  optionRows,
62
64
  optionsBasisFields,
63
65
  optionsBasisFootnote,
64
66
  parseOptionExpiration,
65
- requestListedOpenInterest,
66
67
  valueChainAtModelSpot,
67
68
  type OptionsChainBasis,
68
69
  } from "./options-chain";
69
- import { DEFAULT_LEAPS_CRITERIA, LEAPS_USAGE, parseLeapsCriteria, runLeapsScreen, takeLeapsOptions } from "./options-leaps";
70
+ import { missingExpiryMessage } from "../../utils/option-expiry";
71
+ import { declaredDividends } from "../../utils/declared-dividends";
72
+ import {
73
+ DEFAULT_LEAPS_CRITERIA, LEAPS_EXTRA_COLUMNS, LEAPS_USAGE, parseLeapsCriteria, runLeapsScreen, takeLeapsOptions,
74
+ } from "./options-leaps";
70
75
  import type { CliResultColumn } from "../result";
71
76
  import type { PriceHistoryResult } from "../../types/price-history";
72
77
  import {
@@ -142,14 +147,14 @@ import { earningsCalendarFailure, earningsChoiceNote, earningsDateLabel, isListi
142
147
  import { formatBasisPoints, toBasisPoints } from "../../utils/basis-points";
143
148
  import { notATickerSymbol, providerMissReason } from "../../sources/provider-errors";
144
149
  import { isNotATickerMessage, withListingSuggestion } from "../not-a-ticker";
145
- import { describeSuggestion, suggestListing } from "../listing-suggestion";
146
- import { splitSymbolArgs } from "../symbol-args";
150
+ import { describeSuggestion, suggestListing, suggestUsTicker, type ListingSuggestion } from "../listing-suggestion";
151
+ import { splitSymbolArgs, uniqueSecuritySymbols } from "../symbol-args";
147
152
  import { cliPerpetualContract, perpetualContractStats, type PerpetualContractPayload } from "../perpetual-contract";
148
153
  import { subMinuteDataMessage } from "../known-gaps";
149
154
  import { secRegistrantMismatchMessage, SecRegistrantMismatchError, areDifferentCompanies } from "../../sources/sec-registrant";
150
155
  import { isCryptoPairSymbol } from "../../utils/crypto-pair";
151
156
  import { looksLikeCurrencyPair } from "../../utils/currency-pair";
152
- import { isUsListingExchange } from "../../utils/exchanges";
157
+ import { canonicalExchange, isUsListingExchange } from "../../utils/exchanges";
153
158
  import { describeTickerChange, startTickerChangeLookup, tickerChangeLine, type TickerChange } from "../../tickers/ticker-changes";
154
159
  import { noSecReportsNotice, nonUsSecListingVenue } from "../../utils/sec";
155
160
  import type { MarketContext } from "../types";
@@ -207,6 +212,8 @@ type QuoteCliRecord = Omit<QuoteBatchResult, "error"> & {
207
212
  perpetualContract?: PerpetualContractPayload;
208
213
  /** The symbol changed: its issuer moved to another symbol, or its holder had another before. */
209
214
  tickerChange?: TickerChange;
215
+ /** A bare symbol read on a listing abroad that is one slip from a US ticker: the US ticker it was most likely typed for. */
216
+ usSuggestion?: ListingSuggestion;
210
217
  };
211
218
  type FinancialsCliData = TickerFinancials & {
212
219
  symbol: string;
@@ -224,9 +231,13 @@ function parseNewsFeed(value: string | undefined): NewsQuery["feed"] | undefined
224
231
  return value && VALID_NEWS_FEEDS.has(value as NewsFeed) ? value as NewsQuery["feed"] : undefined;
225
232
  }
226
233
 
227
- /** The symbols of a command's arguments, one each however they were separated: spaces, commas or pasted lines. */
234
+ /**
235
+ * The symbols of a command's arguments, one each however they were separated
236
+ * (spaces, commas or pasted lines), and each security once however its case or
237
+ * share class was written: `AAPL aapl BRK.B BRK-B` is AAPL and BRK.B.
238
+ */
228
239
  function normalizeSymbols(args: string[]): string[] {
229
- return splitSymbolArgs(args).map((symbol) => symbol.toUpperCase());
240
+ return uniqueSecuritySymbols(splitSymbolArgs(args).map((symbol) => symbol.toUpperCase()));
230
241
  }
231
242
 
232
243
  /** What JSON metadata says about the listing a command resolved. */
@@ -499,6 +510,13 @@ function corporateActionRows(data: CorporateActionsData) {
499
510
  date: event.exDate,
500
511
  detail: currency ? formatDistributionAmount(event.amount, currency) : cleanDecimal(event.amount),
501
512
  })),
513
+ // Declared and unpaid, at its ex-date beside the paid rows, never one of them.
514
+ ...declaredDividends(data).map((event) => ({
515
+ type: "dividend",
516
+ date: event.exDate,
517
+ detail: `declared ${currency ? formatDistributionAmount(event.amount, currency) : cleanDecimal(event.amount)}`
518
+ + (event.payDate ? `, payable ${event.payDate}` : ""),
519
+ })),
502
520
  ...data.splits.map((event) => ({
503
521
  type: "split",
504
522
  date: event.date,
@@ -563,7 +581,9 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
563
581
  // So is whether a symbol changed (FB is now META), for the same lists.
564
582
  const changes = new Map(listings.length > MAX_CHOICE_LOOKUPS ? []
565
583
  : listings.map((listing) => [listing, startTickerChangeLookup(market.dataProvider, listing.symbol, listing.exchange)] as const));
566
- const results = await market.dataProvider.getQuotesBatch(targets, { forceRefresh: ctx.cliOptions.refresh });
584
+ // The service's quote of the moment, not each form's local copy from whenever it was last asked for:
585
+ // BRK.B saved on NYSE, BRK-B and BRK.B:NYSE print one quote (GLO-735). A local copy answers only offline.
586
+ const results = await market.dataProvider.getQuotesBatch(targets, { forceRefresh: ctx.cliOptions.refresh, revalidate: true });
567
587
  const listingOf = (result: QuoteBatchResult, index: number) => listings[targets.indexOf(result.target)] ?? listings[index]!;
568
588
  const asked = results.map(listingOf);
569
589
  const request = { command: commandName, noun: "quote" };
@@ -590,6 +610,11 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
590
610
  const perpetualContracts = await Promise.all(answers.map(({ result }) => commandName === "quote" && result.quote
591
611
  ? cliPerpetualContract(result.quote)
592
612
  : null));
613
+ // A bare symbol quoted on a listing abroad (APPL, Aptus Pharma on BSE) that is one slip from a US ticker (AAPL) says so.
614
+ const usSuggestions = await Promise.all(answers.map(({ result, listing, notes }) => {
615
+ const venue = result.quote && !listing.saved ? bareListingNote(listing, result.quote) : null;
616
+ return venue && notes.includes(venue) ? suggestUsTicker(listing.symbol, market) : null;
617
+ }));
593
618
  // Each row names its listing by key (SAN:EPA) and company, so the table needs no line above it.
594
619
  const data = answers.map(({ result, listing, failure }, index) => ({
595
620
  target: result.target,
@@ -599,6 +624,7 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
599
624
  quote: result.quote,
600
625
  ...contracts[index] ? { futuresContract: contracts[index] } : {},
601
626
  ...perpetualContracts[index] ? { perpetualContract: perpetualContracts[index] } : {},
627
+ ...usSuggestions[index] ? { usSuggestion: usSuggestions[index] } : {},
602
628
  error: failure?.oneLine ?? errorMessage(result.error),
603
629
  ...yieldIndexFields(result),
604
630
  ...tickerChanges[index] ? { tickerChange: tickerChanges[index] } : {},
@@ -625,15 +651,38 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
625
651
  freshness: quotesFreshness(data.map((row) => row.quote)),
626
652
  }, {
627
653
  rows: quoteRows,
628
- details: quoteFuturesContractText,
654
+ details: quoteDetailsText,
629
655
  columns,
630
656
  ...(yieldsOnly ? { textColumns: columns.map((column) => column.key === "changePercent" ? { ...column, header: "Chg" } : column) } : {}),
631
657
  });
658
+ // The table names each symbol it found nothing for; with none found, the exit status says so too.
659
+ if (data.every((row) => !row.quote)) process.exitCode = CLI_EXIT_CODES.error;
632
660
  });
633
661
  }
634
662
 
635
663
  /**
636
- * Under the table, each futures row's Settle line and Contract block, worded
664
+ * Under the table: each futures row's Settle line and Contract block, then the
665
+ * line for a symbol that is not a US ticker though it is one slip from one.
666
+ */
667
+ function quoteDetailsText(data: QuoteCliRecord[]): string {
668
+ return [quoteFuturesContractText(data), ...data.flatMap((row) => notUsTickerLine(row) ?? [])].filter(Boolean).join("\n\n");
669
+ }
670
+
671
+ /**
672
+ * "APPL is not a US ticker: it is Aptus Pharma Limited on BSE (INR). Did you
673
+ * mean AAPL (Apple Inc.)?", for a symbol read on a listing abroad that is one
674
+ * slip from a US ticker, worded as the not-a-ticker answers word it.
675
+ */
676
+ function notUsTickerLine({ target, quote, listing, usSuggestion }: QuoteCliRecord): string | null {
677
+ if (!quote || !usSuggestion) return null;
678
+ const venue = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
679
+ const name = quote.name || listing?.name;
680
+ const what = `${name ? `${name} on ` : ""}${venue}${quote.currency ? ` (${quote.currency})` : ""}`;
681
+ return cliStyles.warning(`${target.symbol} is not a US ticker: it is ${what}. Did you mean ${describeSuggestion(usSuggestion)}?`);
682
+ }
683
+
684
+ /**
685
+ * Each futures row's Settle line and Contract block, worded
637
686
  * as `ticker` words them. Several symbols name theirs in the block's title.
638
687
  */
639
688
  function quoteFuturesContractText(data: QuoteCliRecord[]): string {
@@ -837,16 +886,18 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
837
886
  const flagColumn: CliResultColumn = {
838
887
  key: "flag", header: "Flag", format: (value) => typeof value === "string" ? cliStyles.warning(value) : "",
839
888
  };
840
- // Text also marks a bar still forming or rebuilt from hourly bars there; exports keep them as their own fields.
889
+ // Text also marks a bar still forming, rebuilt from hourly bars or closing before its settlement; exports keep them as fields.
841
890
  const flagTextColumn: CliResultColumn = {
842
891
  ...flagColumn,
843
892
  format: (value, row) => [
844
893
  typeof value === "string" ? cliStyles.warning(value) : "",
845
894
  row.partial ? cliStyles.muted("partial") : "",
846
895
  row.filled ? cliStyles.muted("filled") : "",
896
+ row.unsettled ? cliStyles.muted("unsettled") : "",
847
897
  ].filter(Boolean).join(" "),
848
898
  };
849
899
  const marked = data.some((row) => row.partial || row.filled);
900
+ const unsettled = data.some((row) => row.unsettled);
850
901
  ctx.printResult({
851
902
  data,
852
903
  metadata: {
@@ -875,6 +926,7 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
875
926
  ...priceColumns,
876
927
  flagColumn,
877
928
  ...marked ? [{ key: "partial", header: "Partial" }, { key: "filled", header: "Filled" }] : [],
929
+ ...unsettled ? [{ key: "unsettled", header: "Unsettled" }] : [],
878
930
  ...volume ? [{ key: "volumeUnit", header: "Volume unit" }, { key: "volumeNote", header: "Volume note" }] : [],
879
931
  ],
880
932
  // Every exported row says what it is a bar of, the currency a price is in and its bar size, so it survives head and concatenation.
@@ -932,6 +984,10 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
932
984
  // Market cap, the multiples and the yield at the regular session price, as the text gives them; the quote stays as sent.
933
985
  const priced = atRegularSessionPrice(financials);
934
986
  const capitalization = selectMarketCapitalization(priced.quote, priced.fundamentals);
987
+ // A forward yield dated ahead names the declared distribution it annualizes, in the text and tables.
988
+ const report = ctx.cliOptions.format === "json" ? data : {
989
+ ...data, declaredDividends: await loadForwardYieldDeclarations(market.dataProvider, listing.request, financials.fundamentals),
990
+ };
935
991
  ctx.printResult({
936
992
  // JSON reads a multiple over a loss as null with its reason, never as a number.
937
993
  data: {
@@ -946,8 +1002,8 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
946
1002
  freshness,
947
1003
  ...(choice.length > 0 ? { warnings: choice } : {}),
948
1004
  }, {
949
- text: () => renderFundamentalsReport(data, view),
950
- ...(tabular ? { tables: selectTables(fundamentalsReportTables(data, view, freshness)) } : {}),
1005
+ text: () => renderFundamentalsReport(report, view),
1006
+ ...(tabular ? { tables: selectTables(fundamentalsReportTables(report, view, freshness)) } : {}),
951
1007
  exportIdentity: { symbolOrSeries: listing.key },
952
1008
  });
953
1009
  return;
@@ -1472,10 +1528,9 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
1472
1528
  const expiration = takeOption(args, "--expiration");
1473
1529
  const exchangeOption = takeOption(args, "--exchange");
1474
1530
  if (leapsOptions.leaps) {
1475
- if (expiration != null || rawArgs.includes("--expiration")) {
1476
- ctx.fail("--leaps reads every expiry more than a year out; drop --expiration.", `Usage: gloomberb ${LEAPS_USAGE}`);
1477
- }
1478
- const criteria = parseLeapsCriteria(leapsOptions.raw);
1531
+ // With --leaps, --expiration is a window of one expiry.
1532
+ const expirationGiven = expiration != null || rawArgs.some((arg) => arg === "--expiration" || arg.startsWith("--expiration="));
1533
+ const criteria = parseLeapsCriteria({ ...leapsOptions.raw, ...expirationGiven ? { "--expiration": expiration ?? "" } : {} });
1479
1534
  if ("error" in criteria) ctx.fail(criteria.error, `Usage: gloomberb ${LEAPS_USAGE}`);
1480
1535
  const symbols = normalizeSymbols(args);
1481
1536
  if (symbols.length === 0) ctx.fail("--leaps needs at least one symbol.", `Usage: gloomberb ${LEAPS_USAGE}`);
@@ -1505,8 +1560,6 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
1505
1560
  const quotePromise = loadListingQuote(market.dataProvider, listing);
1506
1561
  const fate = listingFateLoader(listing, market, quotePromise);
1507
1562
  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
1563
  const { result, chain: loaded } = await loadForListing(listing, market, ctx, async () => {
1511
1564
  const cached = await market.dataProvider.getCachedQuery?.("getOptionsChain", [symbol, exchange, expirationDate, undefined])
1512
1565
  .load({ force: refresh });
@@ -1539,7 +1592,7 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
1539
1592
  // IV and delta are valued at one spot and instant, named under the table and on every exported row.
1540
1593
  const { chain, spot: modelSpot } = valueChainAtModelSpot(loaded, inputs, Date.now(), identity.exchange);
1541
1594
  const basis: OptionsChainBasis = { spot: modelSpot, dividendYield: inputs.dividendYield ?? null,
1542
- openInterest: await loadOptionsChainOpenInterest(symbol, chain, listedOpenInterest) };
1595
+ openInterest: await loadOptionsChainOpenInterest(symbol, chain) };
1543
1596
  const basisFields = optionsBasisFields(basis);
1544
1597
  const currency = [...chain.calls, ...chain.puts].find((contract) => contract.currency?.trim())?.currency.trim().toUpperCase()
1545
1598
  ?? quote?.currency ?? null;
@@ -1815,10 +1868,10 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
1815
1868
  options: [
1816
1869
  {
1817
1870
  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",
1871
+ 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
1872
  },
1820
1873
  EXCHANGE_OPTION,
1821
- { flags: "--leaps", description: "Rank contracts more than a year out across the symbols given, as a stock replacement" },
1874
+ { 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
1875
  { flags: "--side <calls|puts>", description: `With --leaps: the side to rank (default ${DEFAULT_LEAPS_CRITERIA.side}s)` },
1823
1876
  {
1824
1877
  flags: "--delta <low-high>",
@@ -1829,6 +1882,17 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
1829
1882
  description: `With --leaps: widest bid/ask spread, in percent of the midpoint (default ${DEFAULT_LEAPS_CRITERIA.maxSpreadPercent})`,
1830
1883
  },
1831
1884
  { flags: "--min-oi <contracts>", description: `With --leaps: least open interest (default ${DEFAULT_LEAPS_CRITERIA.minOpenInterest})` },
1885
+ {
1886
+ flags: "--min-days <days>",
1887
+ description: `With --leaps: fewest calendar days to expiry (default ${DEFAULT_LEAPS_CRITERIA.minDays}); 180 reaches six months out`,
1888
+ },
1889
+ { flags: "--max-days <days>", description: "With --leaps: most calendar days to expiry; --min-days 365 --max-days 730 is 12 to 24 months" },
1890
+ { flags: "--expiry-from <YYYY-MM-DD>", description: "With --leaps: first expiry date kept; replaces the one-year floor" },
1891
+ { flags: "--expiry-to <YYYY-MM-DD>", description: "With --leaps: last expiry date kept" },
1892
+ {
1893
+ flags: "--columns <list>",
1894
+ description: `With --leaps: add figures, comma-separated: ${LEAPS_EXTRA_COLUMNS.join(", ")}`,
1895
+ },
1832
1896
  {
1833
1897
  flags: "--sort <order>",
1834
1898
  description: "With --leaps: carry (extrinsic per year, lowest first; the default), spread, oi, delta, expiry or symbol",
@@ -1837,7 +1901,10 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
1837
1901
  sections: [{
1838
1902
  title: "LEAPS",
1839
1903
  lines: [
1840
- "--leaps reads every expiry more than a year out for each symbol 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.",
1904
+ "--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.",
1905
+ "--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.",
1906
+ "--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 "
1907
+ + `(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
1908
  "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
1909
  ],
1843
1910
  }],
@@ -1847,6 +1914,8 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
1847
1914
  "options AAPL:NASDAQ --json",
1848
1915
  "options AAPL MSFT NVDA GOOGL AMZN META --leaps",
1849
1916
  "options SPY QQQ --leaps --side puts --delta 0.20-0.40 --csv",
1917
+ "options MSFT --leaps --min-days 365 --max-days 730",
1918
+ "options AAPL MSFT NVDA --leaps --min-days 180 --columns iv,ivr,cost,breakeven,carry-net",
1850
1919
  ],
1851
1920
  },
1852
1921
  execute: runOptions,