gloomberb 0.18.4 → 0.18.5
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +32 -1
- package/src/account/auth-model.ts +7 -3
- package/src/api-client/auth.ts +9 -4
- package/src/api-client/catalysts.ts +2 -1
- package/src/api-client/data.ts +14 -0
- package/src/api-client/index.ts +160 -11
- package/src/api-client/live-scan.ts +91 -0
- package/src/api-client/members.ts +12 -0
- package/src/api-client/request.ts +222 -41
- package/src/api-client/research-activity.ts +3 -10
- package/src/api-client/types.ts +6 -0
- package/src/app/keybindings/index.ts +3 -0
- package/src/app/keybindings/labels.ts +18 -1
- package/src/app/keybindings/resolve.ts +14 -0
- package/src/app/runtime/broker-import.ts +43 -26
- package/src/architecture/core-builtin-allowlist.json +1 -1
- package/src/cli/commands/fred-series.ts +5 -0
- package/src/cli/commands/keybindings.ts +23 -0
- package/src/cli/commands/market.ts +68 -22
- package/src/cli/commands/options-chain.ts +59 -3
- package/src/cli/commands/overview.ts +119 -31
- package/src/cli/commands/search.ts +18 -2
- package/src/cli/commands/ticker.ts +2 -1
- package/src/cli/errors.ts +2 -2
- package/src/cli/export-source.ts +29 -1
- package/src/cli/glossary.ts +49 -0
- package/src/cli/history-rows.ts +30 -7
- package/src/cli/known-gaps.ts +5 -2
- package/src/cli/listing-arg.ts +16 -5
- package/src/cli/pane-functions/freshness.ts +25 -13
- package/src/cli/pane-functions/function-help.ts +10 -3
- package/src/cli/pane-functions/headless.ts +39 -5
- package/src/cli/pane-functions/index.ts +10 -3
- package/src/cli/pane-functions/market-session.ts +15 -5
- package/src/cli/pane-functions/report.ts +5 -1
- package/src/cli/pane-functions/resolver.ts +18 -2
- package/src/cli/quote-answer.ts +10 -4
- package/src/cli/report-tables.ts +18 -7
- package/src/cli/restore-plugins.ts +13 -1
- package/src/cli/result.ts +57 -5
- package/src/components/chart/composite/composite-chart.tsx +22 -4
- package/src/components/chart/composite/format.ts +27 -0
- package/src/components/chart/composite/levels.ts +2 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +9 -2
- package/src/components/chart/composite/range-statistics.ts +62 -0
- package/src/components/chart/composite/scene.ts +7 -3
- package/src/components/chart/composite/series-axis-tags.tsx +16 -9
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/command-bar/panel/prompt-binding.ts +0 -8
- package/src/components/command-bar/workflow/alert.ts +1 -1
- package/src/components/layout/header.tsx +26 -40
- package/src/components/layout/pane/footer/index.tsx +1 -0
- package/src/components/layout/pane/footer/model.ts +27 -0
- package/src/components/onboarding/onboarding-frame.tsx +1 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +2 -2
- package/src/components/ui/display.tsx +5 -1
- package/src/core/state/app/layout.ts +2 -0
- package/src/core/state/app/state.ts +9 -0
- package/src/core/state/app/types.ts +3 -0
- package/src/data/saved-copies.ts +55 -16
- package/src/docs/snapshot.json +361 -84
- package/src/i18n/es.ts +1 -1
- package/src/i18n/ja.ts +1 -1
- package/src/i18n/ko.ts +1 -1
- package/src/i18n/zh-cn.ts +1 -1
- package/src/i18n/zh-tw.ts +1 -1
- package/src/market-data/coordinator/financials.ts +10 -2
- package/src/market-data/coordinator/index.ts +3 -1
- package/src/market-data/coordinator/quotes.ts +4 -2
- package/src/market-data/market/change-basis.ts +33 -5
- package/src/market-data/market/dated-previous-close.ts +203 -0
- package/src/market-data/market/futures-week.ts +68 -0
- package/src/market-data/market/status.ts +16 -4
- package/src/market-data/options-alternatives.ts +60 -0
- package/src/market-data/quotes/contributions.ts +20 -2
- package/src/market-data/quotes/resolution.ts +3 -0
- package/src/market-data/selectors.ts +3 -0
- package/src/notifications/notification-log.ts +6 -4
- package/src/plugins/builtin/alerts/alert-engine.ts +5 -1
- package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
- package/src/plugins/builtin/alerts/cli.ts +59 -20
- package/src/plugins/builtin/alerts/command.ts +7 -0
- package/src/plugins/builtin/alerts/constants.ts +1 -1
- package/src/plugins/builtin/alerts/fired-log.ts +45 -4
- package/src/plugins/builtin/alerts/format.ts +8 -2
- package/src/plugins/builtin/alerts/headless.ts +10 -5
- package/src/plugins/builtin/alerts/history-rows.ts +5 -2
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +66 -16
- package/src/plugins/builtin/alerts/live.ts +3 -3
- package/src/plugins/builtin/alerts/metric-command.ts +61 -0
- package/src/plugins/builtin/alerts/metric-data.ts +70 -0
- package/src/plugins/builtin/alerts/metric-model.ts +175 -0
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
- package/src/plugins/builtin/alerts/pane.tsx +42 -29
- package/src/plugins/builtin/alerts/storage.ts +33 -10
- package/src/plugins/builtin/alerts/types.ts +3 -0
- package/src/plugins/builtin/backtest/headless.ts +10 -5
- package/src/plugins/builtin/backtest/pane.tsx +9 -4
- package/src/plugins/builtin/backtest/presets.ts +29 -3
- package/src/plugins/builtin/broker-manager/index.tsx +3 -2
- package/src/plugins/builtin/broker-manager/model.ts +6 -1
- package/src/plugins/builtin/broker-trading/controller.ts +22 -12
- package/src/plugins/builtin/broker-trading/index.tsx +13 -1
- package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
- package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
- package/src/plugins/builtin/builtin-plugin-meta.ts +1 -1
- package/src/plugins/builtin/catalysts/model.ts +2 -1
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -1
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
- package/src/plugins/builtin/chart-composer/cli-options.ts +5 -5
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +5 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
- package/src/plugins/builtin/chart-composer/formula.ts +71 -18
- package/src/plugins/builtin/chart-composer/headless-schema.ts +14 -3
- package/src/plugins/builtin/chart-composer/headless.ts +13 -2
- package/src/plugins/builtin/chart-composer/pane.tsx +17 -2
- package/src/plugins/builtin/chart-composer/presets.ts +50 -15
- package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
- package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
- package/src/plugins/builtin/chat/content/command-suggestions.ts +92 -0
- package/src/plugins/builtin/chat/content/composer.tsx +13 -6
- package/src/plugins/builtin/chat/content/footer.tsx +6 -3
- package/src/plugins/builtin/chat/content/index.tsx +50 -21
- package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
- package/src/plugins/builtin/chat/index.tsx +23 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
- package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
- package/src/plugins/builtin/chat/pane-links/commands.ts +265 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
- package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
- package/src/plugins/builtin/cloud/plugin.tsx +1 -1
- package/src/plugins/builtin/econ/calendar-model.ts +14 -9
- package/src/plugins/builtin/econ/figures.ts +72 -0
- package/src/plugins/builtin/econ/index.tsx +33 -3
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +79 -31
- package/src/plugins/builtin/econ-statistics/stats.ts +1 -0
- package/src/plugins/builtin/econ-statistics/view.ts +3 -0
- package/src/plugins/builtin/equity-screener/pane.tsx +2 -2
- package/src/plugins/builtin/exposure/charts.tsx +2 -2
- package/src/plugins/builtin/exposure/model.ts +13 -1
- package/src/plugins/builtin/exposure/pane.tsx +4 -4
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/futures/model.ts +2 -1
- package/src/plugins/builtin/futures/table.tsx +2 -2
- package/src/plugins/builtin/fx-matrix/client.ts +6 -1
- package/src/plugins/builtin/fx-matrix/headless.ts +5 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +13 -3
- package/src/plugins/builtin/fx-matrix/live-legs.ts +12 -0
- package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
- package/src/plugins/builtin/iv-history/headless.ts +3 -0
- package/src/plugins/builtin/macro-day/pane.tsx +7 -5
- package/src/plugins/builtin/market-movers/footer.ts +2 -2
- package/src/plugins/builtin/members/breadth-pane.tsx +81 -0
- package/src/plugins/builtin/members/client.ts +19 -4
- package/src/plugins/builtin/members/headless.ts +20 -7
- package/src/plugins/builtin/members/index.tsx +9 -9
- package/src/plugins/builtin/members/model.ts +23 -4
- package/src/plugins/builtin/members/pane.tsx +25 -15
- package/src/plugins/builtin/options/analytics.ts +26 -2
- package/src/plugins/builtin/options/model-spot.ts +5 -2
- package/src/plugins/builtin/options/strike-window.ts +2 -1
- package/src/plugins/builtin/options/table.ts +1 -7
- package/src/plugins/builtin/options/view.tsx +25 -8
- package/src/plugins/builtin/options-positioning/client.ts +19 -0
- package/src/plugins/builtin/options-positioning/headless.ts +54 -14
- package/src/plugins/builtin/options-positioning/index.tsx +3 -3
- package/src/plugins/builtin/options-positioning/model.ts +47 -8
- package/src/plugins/builtin/options-positioning/pane.tsx +68 -38
- package/src/plugins/builtin/perps/freshness.ts +12 -10
- package/src/plugins/builtin/perps/live-rows.ts +10 -3
- package/src/plugins/builtin/perps/pane.tsx +15 -11
- package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -2
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
- package/src/plugins/builtin/rate-path/pane.tsx +2 -2
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +4 -4
- package/src/plugins/builtin/scanner/feed.ts +2 -2
- package/src/plugins/builtin/scanner/index.ts +24 -1
- package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
- package/src/plugins/builtin/scanner/live-feed.ts +168 -0
- package/src/plugins/builtin/scanner/live-headless.ts +114 -0
- package/src/plugins/builtin/scanner/live-model.ts +351 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
- package/src/plugins/builtin/scanner/live-track.ts +186 -0
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/macro-releases.ts +65 -0
- package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +8 -2
- package/src/plugins/builtin/shared/volatility/pricing.ts +7 -1
- package/src/plugins/builtin/themes/headless.ts +6 -4
- package/src/plugins/builtin/themes/model.ts +1 -1
- package/src/plugins/builtin/themes/pane.tsx +3 -3
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
- package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
- package/src/plugins/builtin/vol-surface/client.ts +6 -4
- package/src/plugins/builtin/vol-surface/headless.ts +62 -13
- package/src/plugins/builtin/vol-surface/index.tsx +2 -2
- package/src/plugins/builtin/vol-surface/model.ts +22 -7
- package/src/plugins/builtin/vol-surface/pane.tsx +44 -7
- package/src/plugins/builtin/vol-surface/tables.ts +31 -3
- package/src/plugins/builtin/volatility/dvol.ts +46 -0
- package/src/plugins/builtin/volatility/pane.tsx +2 -2
- package/src/plugins/builtin/volatility/series.ts +26 -0
- package/src/plugins/builtin/world-indices/footer.ts +1 -1
- package/src/plugins/catalog-browser.ts +13 -8
- package/src/portfolio/column-values.ts +5 -5
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/renderers/browser/app-services.ts +1 -7
- package/src/renderers/browser/config-host.ts +2 -55
- package/src/renderers/browser/main.tsx +7 -57
- package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +17 -7
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +70 -3
- package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
- package/src/renderers/electrobun/bun/index.ts +25 -1
- package/src/renderers/electrobun/shared/app-name.ts +17 -0
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
- package/src/renderers/opentui/toast-host.tsx +71 -43
- package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
- package/src/sources/provider-router/batches.ts +10 -2
- package/src/sources/provider-router/financial-routes.ts +3 -3
- package/src/sources/provider-router/history.ts +28 -7
- package/src/sources/provider-router/index.ts +146 -10
- package/src/sources/provider-router/search.ts +13 -2
- package/src/state/app/context/index.tsx +3 -0
- package/src/sync/auth-transition.ts +19 -9
- package/src/sync/baseline.ts +10 -4
- package/src/sync/controller.ts +66 -16
- package/src/sync/react.ts +4 -1
- package/src/sync/types.ts +3 -0
- package/src/telemetry/usage-counts.ts +0 -18
- package/src/theme/colors.ts +8 -0
- package/src/tickers/company-name.ts +59 -0
- package/src/tickers/listing-venues.ts +23 -0
- package/src/tickers/search/bare-symbol.ts +36 -0
- package/src/tickers/search/index.ts +36 -7
- package/src/tickers/search/ranking.ts +10 -1
- package/src/tickers/search/types.ts +5 -0
- package/src/time-series/base-series.ts +13 -5
- package/src/time-series/daily-valuation.ts +62 -0
- package/src/time-series/field-catalog.ts +1 -3
- package/src/time-series/forward-valuation.ts +32 -0
- package/src/time-series/fundamentals.ts +155 -7
- package/src/time-series/hooks.ts +1 -0
- package/src/time-series/price-units.ts +72 -2
- package/src/time-series/resolve-context.ts +5 -1
- package/src/time-series/resolve.ts +61 -12
- package/src/time-series/series-presentation.ts +40 -6
- package/src/time-series/spec.ts +2 -1
- package/src/time-series/studies.ts +270 -72
- package/src/time-series/transforms.ts +1 -0
- package/src/time-series/types.ts +5 -0
- package/src/time-series/valuation-releases.ts +39 -0
- package/src/types/data-provider.ts +9 -1
- package/src/types/desktop-window.ts +2 -0
- package/src/types/financials.ts +41 -1
- package/src/types/headless.ts +42 -4
- package/src/types/instrument.ts +3 -0
- package/src/types/plugin.ts +1 -1
- package/src/updater/command-names.ts +12 -7
- package/src/utils/financial-statements.ts +6 -1
- package/src/utils/network-failure.ts +9 -6
- package/src/utils/option-expiry.ts +48 -1
- package/src/utils/options.ts +10 -0
- package/src/utils/plugin-proxy-hosts.json +1 -0
- package/src/utils/price-history.ts +5 -0
- package/src/renderers/browser/first-screen.ts +0 -193
|
@@ -1494,7 +1494,8 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
|
|
|
1494
1494
|
|
|
1495
1495
|
let financials: TickerFinancials | null = null;
|
|
1496
1496
|
try {
|
|
1497
|
-
|
|
1497
|
+
// The Change line says what the move is from.
|
|
1498
|
+
financials = await dataProvider.getTickerFinancials(requestSymbol, exchange, { datesChangeBasis: true });
|
|
1498
1499
|
} catch (error) {
|
|
1499
1500
|
// A known exchange the symbol is not listed on: say where it is.
|
|
1500
1501
|
await failIfNotTraded(listing, { store, dataProvider }, { fail: failCommand });
|
package/src/cli/errors.ts
CHANGED
|
@@ -9,9 +9,9 @@ import { DATA_SERVICE_UNREACHABLE_MESSAGE, isNetworkFailure } from "../utils/net
|
|
|
9
9
|
|
|
10
10
|
const USAGE_PREFIX = "Usage: ";
|
|
11
11
|
/** The code of every error a command fails with: bad input, nothing found, an option it does not take. */
|
|
12
|
-
const CLI_ERROR_CODE = "cli_error";
|
|
12
|
+
export const CLI_ERROR_CODE = "cli_error";
|
|
13
13
|
/** The code of a command whose requests never reached the data service. */
|
|
14
|
-
const SERVICE_UNREACHABLE_CODE = "service_unreachable";
|
|
14
|
+
export const SERVICE_UNREACHABLE_CODE = "service_unreachable";
|
|
15
15
|
|
|
16
16
|
/** What to check when nothing answered: the connection, and the proxy when one is set. */
|
|
17
17
|
function unreachableHint(): string {
|
package/src/cli/export-source.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* writes CSV or JSON, so an export can go in an audit file as it is: CSV ends
|
|
4
4
|
* with `#` lines and JSON carries a `source` object with the same fields.
|
|
5
5
|
* CSV and NDJSON rows also end with five fixed columns naming what each row
|
|
6
|
-
* is (`EXPORT_IDENTITY_COLUMNS`), less any the table already has by name
|
|
6
|
+
* is (`EXPORT_IDENTITY_COLUMNS`), less any the table already has by name; a
|
|
7
|
+
* dated export adds `period`, `released` and `retrieved` after them.
|
|
7
8
|
*/
|
|
8
9
|
import { formatFreshnessLine, type ReportFreshness } from "./pane-functions/freshness";
|
|
9
10
|
import { parseReportTime } from "../utils/utc-time";
|
|
@@ -54,6 +55,14 @@ export function exportSource(freshness: ReportFreshness): ExportSource {
|
|
|
54
55
|
*/
|
|
55
56
|
export const EXPORT_IDENTITY_COLUMNS = ["symbol_or_series", "currency_or_unit", "interval", "source", "as_of"] as const;
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* The columns a dated export (`econ`, `fred`, `fn ECST`) ends with after the
|
|
60
|
+
* five: the period a figure covers, when it was released, and when this export
|
|
61
|
+
* retrieved it. `as_of` is then the release, and these three say the rest, so a
|
|
62
|
+
* row never has to be read against the closing lines to be dated.
|
|
63
|
+
*/
|
|
64
|
+
export const EXPORT_DATING_COLUMNS = ["period", "released", "retrieved"] as const;
|
|
65
|
+
|
|
57
66
|
/** What a command knows about the rows it exports; source and as-of come from its freshness. */
|
|
58
67
|
export interface ExportIdentity {
|
|
59
68
|
/** The symbol or series ID asked for: `USDZAR=X`, `SAN:EPA`, `DEXSFUS`. */
|
|
@@ -68,6 +77,10 @@ export interface ExportIdentity {
|
|
|
68
77
|
* blank; absent, it is the closing lines' observation date.
|
|
69
78
|
*/
|
|
70
79
|
asOf?: string | null;
|
|
80
|
+
/** In a dated export: the period the figure covers, as its publisher names it (`Sep 2026`, `2026-09-01`). */
|
|
81
|
+
period?: string | null;
|
|
82
|
+
/** In a dated export: when the figure was released, an ISO date or instant; null when the data does not say. */
|
|
83
|
+
released?: string | null;
|
|
71
84
|
}
|
|
72
85
|
|
|
73
86
|
/** A row's own as-of in the closing lines' form: a date stays a date, an instant is ISO in UTC. */
|
|
@@ -94,6 +107,21 @@ export function exportIdentityCells(freshness: ReportFreshness, identity: Export
|
|
|
94
107
|
];
|
|
95
108
|
}
|
|
96
109
|
|
|
110
|
+
/**
|
|
111
|
+
* The cells of `EXPORT_DATING_COLUMNS` for a row: its period and release as
|
|
112
|
+
* the data states them, blank when it does not, and the time this export
|
|
113
|
+
* retrieved the data.
|
|
114
|
+
*/
|
|
115
|
+
export function exportDatingCells(freshness: ReportFreshness, identity: ExportIdentity = {}): string[] {
|
|
116
|
+
return [identity.period?.trim() ?? "", rowAsOf(identity.released ?? null), exportSource(freshness).retrievalTime];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The three dating fields of an NDJSON row, null where blank. */
|
|
120
|
+
export function exportDatingFields(freshness: ReportFreshness, identity: ExportIdentity = {}): Record<string, string | null> {
|
|
121
|
+
const cells = exportDatingCells(freshness, identity);
|
|
122
|
+
return Object.fromEntries(EXPORT_DATING_COLUMNS.map((column, index) => [column, cells[index] || null]));
|
|
123
|
+
}
|
|
124
|
+
|
|
97
125
|
/** The same five as fields of an NDJSON row, null where blank. */
|
|
98
126
|
export function exportIdentityFields(freshness: ReportFreshness, identity: ExportIdentity = {}): Record<string, string | null> {
|
|
99
127
|
const cells = exportIdentityCells(freshness, identity);
|
package/src/cli/glossary.ts
CHANGED
|
@@ -778,6 +778,42 @@ export const GLOSSARY: readonly GlossaryEntry[] = [
|
|
|
778
778
|
definition: "Cboe's index of the 30-day volatility that VIX options price: the VIX of the VIX. A high reading means traders are paying up "
|
|
779
779
|
+ "to protect against sudden jumps in VIX itself.",
|
|
780
780
|
},
|
|
781
|
+
// Options positioning (OPX, GEX) and the volatility surface (OVDV).
|
|
782
|
+
{
|
|
783
|
+
term: "Max pain", aliases: ["max-pain", "maximum pain", "max pain strike", "pain strike"], functions: ["OPX", "GEX"],
|
|
784
|
+
definition: "The settlement price at which one expiry's options would pay their holders the least, summed over every call's and put's open interest: "
|
|
785
|
+
+ "the strike where Payout $ is lowest. It says where option holders collect least at expiry, not where the price will go. "
|
|
786
|
+
+ "Deribit's BTC and ETH options settle at 08:00 UTC on the day named, against its index averaged over the 30 minutes before.",
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
term: "Payout $", aliases: ["payout", "expiry payout", "option payout", "payout at expiry"], functions: ["OPX", "GEX"], shown: ["Payout $"],
|
|
790
|
+
definition: "What an expiry's options would pay their holders, in dollars, if the underlying settled at that strike: each call's open interest times "
|
|
791
|
+
+ "how far the strike is above the call's, plus each put's times how far it is below, times the contract size (100 shares for a US listing, "
|
|
792
|
+
+ "1 BTC or 1 ETH on Deribit). Max pain is the strike where it is lowest.",
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
term: "Call wall", aliases: ["put wall", "walls", "gamma wall", "gamma walls"], functions: ["OPX", "GEX"], shown: ["Call wall", "Put wall"],
|
|
796
|
+
definition: "The strike with the most dealer gamma from calls (call wall) or from puts (put wall), across every expiry or within one. "
|
|
797
|
+
+ "Hedging flows are largest there, so it is often read as resistance above spot and support below it.",
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
term: "Risk reversal", short: "RR", aliases: ["25d risk reversal", "25-delta risk reversal", "25 delta risk reversal", "rr25", "risk reversals"],
|
|
801
|
+
functions: ["OVDV"], shown: ["RR"],
|
|
802
|
+
definition: "The 25-delta call's implied volatility minus the 25-delta put's, in vol points, for one expiry. Negative means puts cost more than calls "
|
|
803
|
+
+ "as far from the forward (downside protection is bid), positive that upside calls are. OVDV signs it call minus put.",
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
term: "Butterfly", short: "BF", aliases: ["25d butterfly", "25-delta butterfly", "25 delta butterfly", "bf25", "smile curvature", "fly"],
|
|
807
|
+
functions: ["OVDV"], shown: ["BF"],
|
|
808
|
+
definition: "The average of the 25-delta call and put implied volatilities minus the implied volatility at the forward, in vol points, for one expiry: "
|
|
809
|
+
+ "how much the wings cost over the middle of the smile. A higher butterfly prices larger moves in either direction.",
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
term: "DVOL", aliases: ["deribit volatility index", "bitcoin volatility index", "ether volatility index", "btc dvol", "eth dvol", "crypto vix"],
|
|
813
|
+
functions: ["OVDV", "G"], shown: ["DVOL"],
|
|
814
|
+
definition: "Deribit's volatility index for BTC or ETH: the 30-day implied volatility its options price, in vol points, computed from the whole "
|
|
815
|
+
+ "strip of strikes as VIX is. OVDV shows it beside the coin's surface; chart it as CAP:volatility:dvol-btc or CAP:volatility:dvol-eth.",
|
|
816
|
+
},
|
|
781
817
|
// The economic calendar (ECO).
|
|
782
818
|
{
|
|
783
819
|
term: "Actual", aliases: ["actual release", "actual value"], functions: ["ECO"], shown: ["ACTUAL", "Actual"],
|
|
@@ -795,6 +831,19 @@ export const GLOSSARY: readonly GlossaryEntry[] = [
|
|
|
795
831
|
term: "Impact", short: "IMP", aliases: ["importance", "event impact", "high impact"], functions: ["ECO"],
|
|
796
832
|
definition: "How much a release usually moves markets: HIGH for the big ones (CPI, jobs, rate decisions), MED and LOW for speeches and smaller data.",
|
|
797
833
|
},
|
|
834
|
+
{
|
|
835
|
+
term: "Purchasing managers' index", short: "PMI", aliases: ["pmis", "purchasing managers", "purchasing managers index", "ism", "ism pmi",
|
|
836
|
+
"flash pmi", "caixin pmi", "ratingdog pmi"], functions: ["ECO"],
|
|
837
|
+
definition: "A monthly survey of companies' purchasing managers on orders, output, jobs and prices: 50 is no change from the month before, "
|
|
838
|
+
+ "above it growth, below it contraction. A flash PMI is the month's early read, the final a week or so later. ECO lists each as a release: "
|
|
839
|
+
+ "ISM for the US, S&P Global's elsewhere, NBS and RatingDog (formerly Caixin) for China, most at medium or low impact.",
|
|
840
|
+
},
|
|
841
|
+
// Economic statistics (ECST).
|
|
842
|
+
{
|
|
843
|
+
term: "Nowcast", aliases: ["nowcasts", "nowcasting", "gdpnow", "gdp nowcast"], functions: ["ECST", "ECO"], shown: ["GDPNow"],
|
|
844
|
+
definition: "An estimate of this quarter's GDP growth from the data already out, revised as each release lands until the official figure. "
|
|
845
|
+
+ "ECST shows the Atlanta Fed's GDPNow, annualized, against its own history; ECO lists each update as a release.",
|
|
846
|
+
},
|
|
798
847
|
];
|
|
799
848
|
|
|
800
849
|
const ANSI = /\x1b\[[0-9;]*m/g;
|
package/src/cli/history-rows.ts
CHANGED
|
@@ -19,6 +19,8 @@ import { formatUtcTime } from "../utils/utc-time";
|
|
|
19
19
|
import { historyCoverageNotice } from "../sources/history-coverage";
|
|
20
20
|
import { barLastSession, barPeriodToDate, type BarPeriodToDate } from "../market-data/market/bar-period";
|
|
21
21
|
import { barSizeFromGap, medianBarGap } from "../time-series/bar-spacing";
|
|
22
|
+
import { isRegularSessionTime, sessionCalendarTimeZone } from "../market-data/market/freshness";
|
|
23
|
+
import { zonedDateKey } from "../utils/zoned-date-time";
|
|
22
24
|
|
|
23
25
|
const PRICE_KEYS = ["open", "high", "low", "close"] as const;
|
|
24
26
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
@@ -332,18 +334,35 @@ function listDates(dates: readonly string[]): string {
|
|
|
332
334
|
return items.length > 1 ? `${items.slice(0, -1).join(", ")} and ${items.at(-1)}` : items[0] ?? "";
|
|
333
335
|
}
|
|
334
336
|
|
|
337
|
+
/** The venue's day in session at `now`: null when it is not trading, undefined when its hours are not known. */
|
|
338
|
+
export function venueTradingDay(venue: string | undefined, now: number): string | null | undefined {
|
|
339
|
+
const zone = venue ? sessionCalendarTimeZone(venue) : null;
|
|
340
|
+
const trading = zone ? isRegularSessionTime(venue, now) : null;
|
|
341
|
+
if (!zone || trading === null) return undefined;
|
|
342
|
+
return trading ? zonedDateKey(now, zone) : null;
|
|
343
|
+
}
|
|
344
|
+
|
|
335
345
|
/**
|
|
336
346
|
* What the partial, filled and unsettled bars are: "2026-10-12 is still
|
|
337
|
-
* forming (partial), with no volume until the day closes",
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
347
|
+
* forming (partial), with no volume until the day closes", a day whose
|
|
348
|
+
* session has closed that "closes on the session's last price until the
|
|
349
|
+
* day's bar is final (partial)", the bars rebuilt from hourly bars, a
|
|
350
|
+
* future's closed session that "closes on its last trade until its
|
|
351
|
+
* settlement is confirmed (unsettled)", and the latest trade an intraday
|
|
352
|
+
* history ends with when it has no volume (latestTradeIndex). Null when no
|
|
353
|
+
* bar is any of them. `tradingDay`: the venue's day in session now, null
|
|
354
|
+
* when none is; left out when the venue's hours are not known, every
|
|
355
|
+
* partial bar is then still forming.
|
|
342
356
|
*/
|
|
343
357
|
export function historyBarStatusNote(
|
|
344
|
-
rows: readonly Pick<HistoryRow, "date" | "partial" | "filled" | "unsettled" | "volume">[],
|
|
358
|
+
rows: readonly Pick<HistoryRow, "date" | "partial" | "filled" | "unsettled" | "volume" | "interval">[],
|
|
359
|
+
options: { tradingDay?: string | null } = {},
|
|
345
360
|
): string | null {
|
|
346
|
-
|
|
361
|
+
// A day's bar the data service still marks partial once its session has closed: its own daily bar is not in yet.
|
|
362
|
+
const closed = (row: (typeof rows)[number]) =>
|
|
363
|
+
options.tradingDay !== undefined && row.interval === "1d" && row.date.slice(0, 10) !== options.tradingDay;
|
|
364
|
+
const partial = rows.filter((row) => row.partial && !closed(row));
|
|
365
|
+
const settling = rows.filter((row) => row.partial && closed(row));
|
|
347
366
|
const latest = rows[latestTradeIndex(rows.map((row) => Date.parse(row.date)))];
|
|
348
367
|
const filled = rows.filter((row) => row.filled);
|
|
349
368
|
const unsettled = rows.filter((row) => row.unsettled);
|
|
@@ -352,6 +371,10 @@ export function historyBarStatusNote(
|
|
|
352
371
|
? `${listDates(partial.map((row) => row.date))} ${partial.length === 1 ? "is" : "are"} still forming (partial)`
|
|
353
372
|
+ (partial.every((row) => row.volume == null) ? ", with no volume until the day closes" : "")
|
|
354
373
|
: null,
|
|
374
|
+
settling.length > 0
|
|
375
|
+
? `${listDates(settling.map((row) => row.date))} ${settling.length === 1 ? "closes" : "close"} on the session's last price`
|
|
376
|
+
+ ` until the day's bar is final (partial)` + (settling.every((row) => row.volume == null) ? ", with no volume until then" : "")
|
|
377
|
+
: null,
|
|
355
378
|
filled.length > 0
|
|
356
379
|
? `${listDates(filled.map((row) => row.date))} ${filled.length === 1 ? "was" : "were"} rebuilt from hourly bars (filled)`
|
|
357
380
|
+ (filled.every((row) => row.volume == null) ? ", without volume" : "")
|
package/src/cli/known-gaps.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { listingSuffixExchange } from "../sources/listing-symbols";
|
|
2
2
|
import { kindFromListingSyntax } from "../tickers/instrument-kind";
|
|
3
|
+
import { macroReleaseTopic } from "../plugins/builtin/shared/macro-releases";
|
|
3
4
|
import { parsePublicTickerKey } from "../utils/exchanges";
|
|
4
5
|
import { listingAbroadVenue } from "../utils/sec";
|
|
5
6
|
import { nimCommand } from "./bank-section";
|
|
@@ -387,7 +388,8 @@ export function knownGapForTicker(symbol: string): string | null {
|
|
|
387
388
|
|
|
388
389
|
/**
|
|
389
390
|
* The line for a query that names a known subject as a whole ("carbon allowances", "EUA"), a Treasury yield
|
|
390
|
-
* typed as a ticker (US10Y),
|
|
391
|
+
* typed as a ticker (US10Y), a topic with no function ("defense budget"), or a macro release no series carries
|
|
392
|
+
* ("ISM manufacturing", "GDPNow"), else null.
|
|
391
393
|
*/
|
|
392
394
|
export function knownGapMessage(query: string): string | null {
|
|
393
395
|
const key = lookupKey(query);
|
|
@@ -395,5 +397,6 @@ export function knownGapMessage(query: string): string | null {
|
|
|
395
397
|
const tenor = treasuryYieldTenor(query);
|
|
396
398
|
return KNOWN_GAPS.find((gap) => gap.terms.some((term) => lookupKey(term) === key))?.message
|
|
397
399
|
?? (tenor ? treasuryYieldLine(tenor) : null)
|
|
398
|
-
?? topicPointer(query)?.message
|
|
400
|
+
?? topicPointer(query)?.message
|
|
401
|
+
?? macroReleaseTopic(query)?.line ?? null;
|
|
399
402
|
}
|
package/src/cli/listing-arg.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
quotedOnVenue,
|
|
10
10
|
readAmbiguousCurrencyPair,
|
|
11
11
|
readSharedCode,
|
|
12
|
+
savedRowOfBareSymbol,
|
|
12
13
|
type ListingDeps,
|
|
13
14
|
type ListingStore,
|
|
14
15
|
type ListingVenue,
|
|
@@ -55,6 +56,7 @@ import {
|
|
|
55
56
|
type TerminalListingCode,
|
|
56
57
|
} from "../tickers/terminal-codes";
|
|
57
58
|
import { listingAbroadVenue } from "../utils/sec";
|
|
59
|
+
import { savedAbroadForBareSymbol, savedUsListingOf } from "../tickers/search/bare-symbol";
|
|
58
60
|
import { listingSuffixCandidate, listingSuffixVenue, tickerHasListingSuffix } from "../sources/listing-symbols";
|
|
59
61
|
import { notTheCoinNote } from "./commands/crypto-hints";
|
|
60
62
|
|
|
@@ -621,15 +623,18 @@ async function quoteListings(
|
|
|
621
623
|
dataProvider: DataProvider,
|
|
622
624
|
listings: readonly CliListing[],
|
|
623
625
|
refresh: boolean,
|
|
626
|
+
datesChangeBasis = false,
|
|
624
627
|
): Promise<Array<Quote | null>> {
|
|
625
628
|
if (listings.length === 0) return [];
|
|
626
629
|
const targets = listings.map((listing) => listing.request);
|
|
627
630
|
if (dataProvider.getQuotesBatch) {
|
|
628
|
-
const results = await dataProvider.getQuotesBatch(targets, { forceRefresh: refresh
|
|
631
|
+
const results = await dataProvider.getQuotesBatch(targets, { forceRefresh: refresh, ...(datesChangeBasis ? { datesChangeBasis } : {}) })
|
|
632
|
+
.catch(logFailure("cli.listing-arg", "Read listing quote batch failed", []));
|
|
629
633
|
return targets.map((target, index) => (results.find((result) => result.target === target) ?? results[index])?.quote ?? null);
|
|
630
634
|
}
|
|
631
635
|
return Promise.all(targets.map((target) => dataProvider.getQuote(target.symbol, target.exchange, {
|
|
632
636
|
cacheMode: refresh ? "refresh" : "default",
|
|
637
|
+
...(datesChangeBasis ? { datesChangeBasis } : {}),
|
|
633
638
|
}).catch(logFailure("cli.listing-arg", "Read listing quote failed", null))));
|
|
634
639
|
}
|
|
635
640
|
|
|
@@ -661,7 +666,7 @@ export async function quoteBareListing(
|
|
|
661
666
|
listing: CliListing,
|
|
662
667
|
deps: ListingDeps,
|
|
663
668
|
request: ListingDataRequest,
|
|
664
|
-
{ refresh = false }: { refresh?: boolean } = {},
|
|
669
|
+
{ refresh = false, datesChangeBasis = false }: { refresh?: boolean; datesChangeBasis?: boolean } = {},
|
|
665
670
|
): Promise<BareListingQuote | null> {
|
|
666
671
|
const venues = await bareListingVenues(listing, deps);
|
|
667
672
|
if (!venues) return null;
|
|
@@ -672,10 +677,10 @@ export async function quoteBareListing(
|
|
|
672
677
|
const first = [namedListing(listing, venues.exchange)];
|
|
673
678
|
const rest = venues.others.map((venue) => namedListing(listing, venue.exchange));
|
|
674
679
|
let tried = first;
|
|
675
|
-
let quotes = await quoteListings(deps.dataProvider, first, refresh);
|
|
680
|
+
let quotes = await quoteListings(deps.dataProvider, first, refresh, datesChangeBasis);
|
|
676
681
|
if (!quotes[0] && rest.length > 0) {
|
|
677
682
|
tried = [...first, ...rest];
|
|
678
|
-
quotes = [...quotes, ...await quoteListings(deps.dataProvider, rest, refresh)];
|
|
683
|
+
quotes = [...quotes, ...await quoteListings(deps.dataProvider, rest, refresh, datesChangeBasis)];
|
|
679
684
|
}
|
|
680
685
|
const found = quotes.findIndex(Boolean);
|
|
681
686
|
if (found < 0) {
|
|
@@ -948,6 +953,10 @@ export async function loadSavedListingOf(
|
|
|
948
953
|
for (const listing of listings) {
|
|
949
954
|
if (found.some((entry) => entry.listing.key === listing.key)) continue;
|
|
950
955
|
const ticker = await loadSavedListing(store, listing);
|
|
956
|
+
// A bare symbol's US listing comes before a row abroad saved under its bare key (SHEL, saved from SHEL.L).
|
|
957
|
+
if (ticker && !listing.exchange && savedAbroadForBareSymbol(listing.symbol, ticker)) {
|
|
958
|
+
add(listing, savedUsListingOf(listing.symbol, await store.loadAllTickers()));
|
|
959
|
+
}
|
|
951
960
|
add(listing, ticker);
|
|
952
961
|
// A suffixed key as typed may be saved with a venue (7203.T:JPX, GARAN.IS:XNYS): any row of that symbol.
|
|
953
962
|
if (!ticker && !listing.exchange && tickerHasListingSuffix(listing.symbol)) {
|
|
@@ -1115,7 +1124,9 @@ export async function resolveCliListing(
|
|
|
1115
1124
|
parsed = market;
|
|
1116
1125
|
}
|
|
1117
1126
|
let listing = await settleSharedCode(parsed, deps);
|
|
1118
|
-
const
|
|
1127
|
+
const found = await loadSavedListing(deps.store, listing) ?? (typedKey ? await loadSuffixedRow(deps.store, typedKey, listing.exchange) : null);
|
|
1128
|
+
// A bare symbol is its US listing when it has one, whatever row abroad holds the bare key (SHEL saved from SHEL.L).
|
|
1129
|
+
const saved = listing.exchange ? found : await savedRowOfBareSymbol(listing.symbol, found, deps);
|
|
1119
1130
|
// A code nothing could read stays the venue it is saved on.
|
|
1120
1131
|
const savedExchange = canonicalExchange(saved?.metadata.exchange);
|
|
1121
1132
|
if (listing.sharedCode && savedExchange) listing = { symbol: listing.symbol, exchange: savedExchange, key: `${listing.symbol}:${savedExchange}` };
|
|
@@ -100,8 +100,8 @@ export interface ReportFreshness {
|
|
|
100
100
|
*/
|
|
101
101
|
sources?: ReportSourceAsOf[];
|
|
102
102
|
/**
|
|
103
|
-
* Whole minutes from the oldest of `sources` (or, declared `oldestAge
|
|
104
|
-
* oldest observation) to when this report was built: `oldest 9 min`.
|
|
103
|
+
* Whole minutes from the oldest of `sources` (or, declared `oldestAge` or
|
|
104
|
+
* `ageOnly`, the oldest observation) to when this report was built: `oldest 9 min`.
|
|
105
105
|
*/
|
|
106
106
|
oldestAgeMinutes?: number;
|
|
107
107
|
/** The data states no feed (`status` is `unreported`) and its line says only its time and age (`ageOnly`). */
|
|
@@ -125,16 +125,21 @@ export interface ReportFreshness {
|
|
|
125
125
|
/**
|
|
126
126
|
* Refreshing the data failed and a copy saved earlier answered: when that
|
|
127
127
|
* copy was saved, an ISO instant in UTC. The status line then says
|
|
128
|
-
* `couldn't refresh; saved 21:49 UTC` in place of how current the data is.
|
|
128
|
+
* `couldn't refresh; saved copy from 21:49 UTC` in place of how current the data is.
|
|
129
129
|
*/
|
|
130
130
|
savedAt?: string;
|
|
131
|
+
/**
|
|
132
|
+
* Nothing was served: the data service could not be reached and no saved
|
|
133
|
+
* copy answered. The line then names no time, since nothing was retrieved.
|
|
134
|
+
*/
|
|
135
|
+
unreached?: true;
|
|
131
136
|
}
|
|
132
137
|
|
|
133
138
|
/**
|
|
134
139
|
* The freshness a command prints, saying when what it shows (`shown`: its
|
|
135
140
|
* rows, or the data its line is drawn from) came from a copy saved earlier
|
|
136
141
|
* because refreshing it failed (`collectSavedCopies`): its status line then
|
|
137
|
-
* reads `couldn't refresh; saved 21:49 UTC` instead of live or delayed.
|
|
142
|
+
* reads `couldn't refresh; saved copy from 21:49 UTC` instead of live or delayed.
|
|
138
143
|
*/
|
|
139
144
|
export function withSavedCopy<F extends ReportFreshness | undefined>(freshness: F, shown: unknown): F {
|
|
140
145
|
if (!freshness || freshness.savedAt) return freshness;
|
|
@@ -463,9 +468,11 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
|
|
|
463
468
|
// A bar still forming covers its period through a later day than the one it is dated by.
|
|
464
469
|
const periodToDate = readPeriodToDate(declared.periodToDate, now);
|
|
465
470
|
const asOf = declaredAsOf ?? periodToDate?.through ?? newest([...observations.units, ...observations.extra]);
|
|
466
|
-
// The oldest source, not the newest, says how far behind the view can be.
|
|
467
|
-
|
|
468
|
-
|
|
471
|
+
// The oldest source, not the newest, says how far behind the view can be. A line that is dated by its age says how
|
|
472
|
+
// old its oldest observation is, even beside each source's newest: a source can stream some rows and not others.
|
|
473
|
+
const oldestSource = oldest(sources.map(({ time }) => time));
|
|
474
|
+
const agedFrom = declared.ageOnly ? oldest(observations.units) ?? oldestSource ?? asOf
|
|
475
|
+
: oldestSource ?? (declared.oldestAge ? oldest(observations.units) ?? asOf : null);
|
|
469
476
|
const oldestAgeMinutes = agedFrom && !agedFrom.dateOnly ? Math.max(0, Math.floor((now - agedFrom.time) / 60_000)) : null;
|
|
470
477
|
// A loader that dates its data itself also says how old the oldest part is, or nothing;
|
|
471
478
|
// `oldest: null` says the rows are a history, whose first entry is not stale data.
|
|
@@ -819,7 +826,7 @@ function onRetrievalDay(iso: string, retrievedAt: string): boolean {
|
|
|
819
826
|
|
|
820
827
|
/**
|
|
821
828
|
* What a report answered from a saved copy says in place of how current its
|
|
822
|
-
* data is: `couldn't refresh; saved 21:49 UTC`, with the day when the copy was
|
|
829
|
+
* data is: `couldn't refresh; saved copy from 21:49 UTC`, with the day when the copy was
|
|
823
830
|
* saved on another day than the report was built.
|
|
824
831
|
*/
|
|
825
832
|
function savedCopyText(savedAt: string, retrievedAt: string, local: boolean): string {
|
|
@@ -827,7 +834,7 @@ function savedCopyText(savedAt: string, retrievedAt: string, local: boolean): st
|
|
|
827
834
|
if (!Number.isFinite(at)) return "couldn't refresh";
|
|
828
835
|
const iso = new Date(at).toISOString();
|
|
829
836
|
const time = onRetrievalDay(iso, retrievedAt) ? `${iso.slice(11, 16)} UTC${local ? localTimeSuffix(at) : ""}` : timeText(iso, local);
|
|
830
|
-
return `couldn't refresh; saved ${time}`;
|
|
837
|
+
return `couldn't refresh; saved copy from ${time}`;
|
|
831
838
|
}
|
|
832
839
|
|
|
833
840
|
/** `US and XETRA`, `US, XETRA and EPA`. */
|
|
@@ -913,10 +920,11 @@ function reopenText(market: ReportMarket, now: number, local: boolean): { text:
|
|
|
913
920
|
|
|
914
921
|
/**
|
|
915
922
|
* `markets closed until Mon`; `short` is the compact form a narrow capture
|
|
916
|
-
* falls back to (`reopens Mon`). A time is
|
|
917
|
-
*
|
|
923
|
+
* falls back to (`reopens Mon`). A time is the regular session's open, and
|
|
924
|
+
* says so (`pre-market open · regular session opens 13:30 UTC`), so it is
|
|
918
925
|
* never read as when the pre-market opens, nor a US market as closed until
|
|
919
|
-
* then while its pre-market trades first.
|
|
926
|
+
* then while its pre-market trades first. A futures venue's Sunday open is no
|
|
927
|
+
* regular session's, so it reads `markets closed · reopens 22:00 UTC`.
|
|
920
928
|
*/
|
|
921
929
|
function marketText(market: ReportMarket, now: number, short: boolean, local: boolean): string {
|
|
922
930
|
const reopen = reopenText(market, now, local);
|
|
@@ -930,6 +938,8 @@ function marketText(market: ReportMarket, now: number, short: boolean, local: bo
|
|
|
930
938
|
return short ? `pre-market · regular opens ${reopen.text}` : `pre-market open · regular session opens ${reopen.text}`;
|
|
931
939
|
case "closed":
|
|
932
940
|
if (!reopen) return short ? "closed" : "markets closed";
|
|
941
|
+
// A futures venue's Sunday open is its first trade of the week, which no regular session is.
|
|
942
|
+
if (reopen.time && market.firstTrade) return short ? `reopens ${reopen.text}` : `markets closed · reopens ${reopen.text}`;
|
|
933
943
|
if (reopen.time) return short ? `regular opens ${reopen.text}` : `markets closed · regular session opens ${reopen.text}`;
|
|
934
944
|
return short ? `reopens ${reopen.text}` : `markets closed until ${reopen.text}`;
|
|
935
945
|
default: {
|
|
@@ -970,6 +980,7 @@ function statusLineParts(freshness: ReportFreshness, detail: StatusLineDetail =
|
|
|
970
980
|
const undated = detail.date === "none";
|
|
971
981
|
return [
|
|
972
982
|
...(freshness.sources?.length ? sourcesAsOfParts(freshness, freshness.sources, undated)
|
|
983
|
+
: freshness.unreached && !freshness.asOf ? []
|
|
973
984
|
: [asOfText(freshness, detail.asOf === "short", local, undated)]),
|
|
974
985
|
...(freshness.schedule && detail.schedule !== "none"
|
|
975
986
|
? [typeof detail.schedule === "number" ? freshness.scheduleVariants?.[detail.schedule] ?? freshness.schedule : freshness.schedule] : []),
|
|
@@ -1174,7 +1185,8 @@ export function captureStatusLine({ report: loaded, selfDated, quoted, rendered
|
|
|
1174
1185
|
}
|
|
1175
1186
|
if (report && loaded?.declaresData) return sourced(report);
|
|
1176
1187
|
if (quoted) return plain(quoted, "quotes");
|
|
1177
|
-
|
|
1188
|
+
// A report that names no feed but dates its data by a session's close says so too: a board's figures outside the session.
|
|
1189
|
+
if (report && (report.status !== "unreported" || report.ageOnly || report.asOfClose)) return sourced(report);
|
|
1178
1190
|
return plain(rendered(), null);
|
|
1179
1191
|
}
|
|
1180
1192
|
|
|
@@ -734,10 +734,10 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
734
734
|
bloomberg: ["IMAP"],
|
|
735
735
|
},
|
|
736
736
|
MEMB: {
|
|
737
|
-
summary: "ETF holdings with weights, shares, member returns, daily contributions and index changes. SPX and
|
|
738
|
-
usage: ["MEMB", "MEMB
|
|
737
|
+
summary: "ETF holdings with weights, shares, member returns, daily contributions, breadth and index changes. SPX and ^SPX use IVV holdings.",
|
|
738
|
+
usage: ["MEMB", "MEMB SPX", "MEMB ^RUT", "gloom fn MEMB SPX --tab breadth"],
|
|
739
739
|
keys: [TABS, OPEN, MEMBERS_MENU],
|
|
740
|
-
data: same("Dated
|
|
740
|
+
data: same("Dated holdings and member returns. Breadth history uses current members (survivorship bias). Nasdaq-100 is not covered."),
|
|
741
741
|
bloomberg: ["MEMB", "MRR", "IMOV"],
|
|
742
742
|
},
|
|
743
743
|
THEM: {
|
|
@@ -894,6 +894,13 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
894
894
|
data: US_SCANNER,
|
|
895
895
|
bloomberg: [],
|
|
896
896
|
},
|
|
897
|
+
SCAN: {
|
|
898
|
+
summary: "Your own intraday scan of US stocks over all listings, an index, a sector or a watchlist: moves over the last minutes, volume spikes against the average pace, new session highs and lows, VWAP crosses and gaps, above price, market cap and volume floors. Matches refresh through the session, and new ones can alert.",
|
|
899
|
+
usage: ["SCAN"],
|
|
900
|
+
keys: [TABS, OPEN, key("a", "dd condition"), key("s", "ave scan")],
|
|
901
|
+
data: US_SCANNER,
|
|
902
|
+
bloomberg: [],
|
|
903
|
+
},
|
|
897
904
|
FLOW: {
|
|
898
905
|
summary: "Sweeps, blocks and large premium prints as they hit the tape, with ticker, strike, expiry and premium.",
|
|
899
906
|
usage: ["FLOW"],
|
|
@@ -43,6 +43,7 @@ import { isRecord } from "../../utils/guards";
|
|
|
43
43
|
import { splitSymbolWords } from "../symbol-args";
|
|
44
44
|
import { formatUtcTime, localTimeSuffix } from "../../utils/utc-time";
|
|
45
45
|
import { deriveHeadlessFreshness, formatFreshnessLine, staleRowsText, withSavedCopy, type ReportFreshness } from "./freshness";
|
|
46
|
+
import { DATA_SERVICE_UNREACHABLE_MESSAGE } from "../../utils/network-failure";
|
|
46
47
|
import { accessGateStatus, errorsAccessGate, isProLock, reportGateText } from "./access-gate";
|
|
47
48
|
import { noArgumentMessage } from "./capabilities";
|
|
48
49
|
import { lockedHeadlessReport } from "./lock-note";
|
|
@@ -780,13 +781,24 @@ export function headlessReportTables(
|
|
|
780
781
|
// A section of one name or one date says so for every line it exports.
|
|
781
782
|
const named = {
|
|
782
783
|
...(section.symbol?.trim() ? { symbolOrSeries: section.symbol.trim() } : {}),
|
|
783
|
-
...(section.asOf?.trim() ? { asOf: section.asOf.trim() } : {}),
|
|
784
|
+
...(section.asOf === null ? { asOf: null } : section.asOf?.trim() ? { asOf: section.asOf.trim() } : {}),
|
|
785
|
+
...(section.period?.trim() ? { period: section.period.trim() } : {}),
|
|
786
|
+
...(section.released?.trim() ? { released: section.released.trim() } : {}),
|
|
784
787
|
};
|
|
785
788
|
if (!("rows" in section && section.rows)) {
|
|
786
789
|
const entries = exportEntriesTable(section.title, section.entries);
|
|
787
790
|
// A figure that declares its unit names it in the unit column of its own line too.
|
|
788
|
-
|
|
789
|
-
|
|
791
|
+
// A line of its own period or release (a previous reading) names them rather than the section's.
|
|
792
|
+
const own = (entry: HeadlessPaneEntry): ExportIdentity | undefined => {
|
|
793
|
+
const identity = {
|
|
794
|
+
...(entry.unit?.trim() ? { currencyOrUnit: entry.unit.trim() } : {}),
|
|
795
|
+
...(entry.period !== undefined ? { period: entry.period } : {}),
|
|
796
|
+
...(entry.released !== undefined ? { released: entry.released } : {}),
|
|
797
|
+
...(entry.asOf !== undefined ? { asOf: entry.asOf } : {}),
|
|
798
|
+
};
|
|
799
|
+
return Object.keys(identity).length > 0 ? identity : undefined;
|
|
800
|
+
};
|
|
801
|
+
const rowIdentities = section.entries.some((entry) => own(entry)) ? section.entries.map(own) : undefined;
|
|
790
802
|
return { ...entries, ...(Object.keys(named).length > 0 ? { identity: named } : {}), ...(rowIdentities ? { rowIdentities } : {}) };
|
|
791
803
|
}
|
|
792
804
|
const table = exportRowsTable(section.title, displayedColumns(columnsFor(section.rows, section.columns, definition.columns)), section.rows, { status: true });
|
|
@@ -819,6 +831,7 @@ export function headlessReportTables(
|
|
|
819
831
|
const gate = reportGate(definition, result);
|
|
820
832
|
return {
|
|
821
833
|
tables,
|
|
834
|
+
...(definition.exportDated ? { dated: true } : {}),
|
|
822
835
|
identity: reportIdentity(definition, result, args),
|
|
823
836
|
footer: reportFooterLines({
|
|
824
837
|
freshness,
|
|
@@ -961,6 +974,22 @@ export function reportGaps(loaded: LoadedHeadlessPaneModel, freshness: ReportFre
|
|
|
961
974
|
.filter(Boolean).join(", ") || null;
|
|
962
975
|
}
|
|
963
976
|
|
|
977
|
+
/**
|
|
978
|
+
* The freshness of a report that served nothing because the data service
|
|
979
|
+
* could not be reached (offline, a hung connection) and no saved copy
|
|
980
|
+
* answered: its line names no retrieval time, as nothing was retrieved.
|
|
981
|
+
*/
|
|
982
|
+
function withUnreached(
|
|
983
|
+
freshness: ReportFreshness,
|
|
984
|
+
result: HeadlessPaneResult,
|
|
985
|
+
{ rowCount, unavailableSymbols, symbols }: { rowCount: number; unavailableSymbols: readonly string[]; symbols: readonly string[] },
|
|
986
|
+
): ReportFreshness {
|
|
987
|
+
if (freshness.savedAt || freshness.asOf) return freshness;
|
|
988
|
+
const nothingServed = rowCount === 0 || (symbols.length > 0 && symbols.every((symbol) => unavailableSymbols.includes(symbol)));
|
|
989
|
+
const unreached = (result.errors ?? []).some((error) => error.includes(DATA_SERVICE_UNREACHABLE_MESSAGE));
|
|
990
|
+
return nothingServed && unreached ? { ...freshness, unreached: true } : freshness;
|
|
991
|
+
}
|
|
992
|
+
|
|
964
993
|
export async function buildHeadlessFunctionReport(
|
|
965
994
|
resolved: ResolvedPaneFunction,
|
|
966
995
|
context: MarketContext,
|
|
@@ -979,8 +1008,13 @@ export async function buildHeadlessFunctionReport(
|
|
|
979
1008
|
}
|
|
980
1009
|
const { complete, unavailableSymbols, rowCount, symbols } = reportCoverage(loaded);
|
|
981
1010
|
const serialized = serializeHeadlessPaneResult(loaded.definition, loaded.result);
|
|
982
|
-
// What the report shows from a saved copy, its refresh having failed, says so
|
|
983
|
-
|
|
1011
|
+
// What the report shows from a saved copy, its refresh having failed, says so; one that could not
|
|
1012
|
+
// reach the data service for anything names no time it was retrieved.
|
|
1013
|
+
const freshness = withUnreached(
|
|
1014
|
+
withSavedCopy(deriveHeadlessFreshness(loaded.definition, loaded.result), loaded.result),
|
|
1015
|
+
loaded.result,
|
|
1016
|
+
{ rowCount, unavailableSymbols, symbols },
|
|
1017
|
+
);
|
|
984
1018
|
return {
|
|
985
1019
|
data: {
|
|
986
1020
|
kind: loaded.definition.shape,
|
|
@@ -36,7 +36,8 @@ import {
|
|
|
36
36
|
ListingArgError, listingFateLoader, listingReadNotes, loadForListing, loadListingQuoteAnswer, quotedListingChoiceNotes, refuseMovedListing,
|
|
37
37
|
} from "../listing-arg";
|
|
38
38
|
import { isNotATickerMessage } from "../not-a-ticker";
|
|
39
|
-
import { isCliFailure } from "../errors";
|
|
39
|
+
import { isCliFailure, SERVICE_UNREACHABLE_CODE } from "../errors";
|
|
40
|
+
import { DATA_SERVICE_UNREACHABLE_MESSAGE } from "../../utils/network-failure";
|
|
40
41
|
import { isTickerLinkPeer } from "../../layout/ticker-navigation";
|
|
41
42
|
import { knownGapMessage, commandPointer, commandPointerLeads, topicPointer } from "../known-gaps";
|
|
42
43
|
import { selectReportTables, type CliReportTables } from "../report-tables";
|
|
@@ -247,7 +248,9 @@ function tablesHoldValues(tables: CliReportTables): boolean {
|
|
|
247
248
|
* is a list (`failsWithoutData`) with no name that has any. A report with data
|
|
248
249
|
* for some of what was asked, warnings or errors beside it, is a result: a
|
|
249
250
|
* loss-maker's P/E band without a P/E, or IV history with only realized
|
|
250
|
-
* volatility, exits 0.
|
|
251
|
+
* volatility, exits 0. Not one missing a part because the data service could
|
|
252
|
+
* not be reached: that is no answer rather than no data, and fails as
|
|
253
|
+
* retryable `service_unreachable`, whatever else it shows.
|
|
251
254
|
*/
|
|
252
255
|
export function reportFailure(
|
|
253
256
|
data: { empty: boolean; rowCount: number; symbols: readonly string[]; unavailableSymbols: readonly string[]; errors?: readonly string[]; reason?: unknown },
|
|
@@ -257,8 +260,12 @@ export function reportFailure(
|
|
|
257
260
|
const missing = missingListings(data.symbols, data.unavailableSymbols);
|
|
258
261
|
const noListing = data.symbols.length > 0 && data.symbols.every((symbol) => missing.has(listingKey(symbol)));
|
|
259
262
|
const noValues = tables ? !tablesHoldValues(tables) : false;
|
|
260
|
-
if (!data.empty && !(noListing && noValues) && !(failsWithoutData && foundNothing(data))) return null;
|
|
261
263
|
const reasons = [...data.errors ?? [], ...typeof data.reason === "string" ? [data.reason] : []];
|
|
264
|
+
const unreached = reasons.filter((reason) => reason.includes(DATA_SERVICE_UNREACHABLE_MESSAGE));
|
|
265
|
+
if (unreached.length > 0 && (data.empty || data.unavailableSymbols.length > 0)) {
|
|
266
|
+
return { code: SERVICE_UNREACHABLE_CODE, message: `${asked}: ${unreached.join("; ")}.`.replace(/\.\.$/, "."), retryable: true };
|
|
267
|
+
}
|
|
268
|
+
if (!data.empty && !(noListing && noValues) && !(failsWithoutData && foundNothing(data))) return null;
|
|
262
269
|
const unavailable = data.unavailableSymbols.length > 0 ? `no data for ${data.unavailableSymbols.join(", ")}` : "no data";
|
|
263
270
|
return { code: "no_data", message: `${asked}: ${reasons.length > 0 ? reasons.join("; ") : unavailable}.`.replace(/\.\.$/, ".") };
|
|
264
271
|
}
|