gloomberb 0.17.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api-client/data.ts +5 -1
- package/src/api-client/doe.ts +19 -0
- package/src/api-client/index.ts +1 -0
- package/src/app/runtime/update.ts +16 -0
- package/src/architecture/dom-cell-metrics-allowlist.json +100 -26
- package/src/cli/commands/market.ts +57 -5
- package/src/cli/commands/options-chain.ts +75 -1
- package/src/cli/commands/ticker.ts +18 -9
- package/src/cli/glossary.ts +1 -1
- package/src/cli/index.ts +14 -6
- package/src/cli/pane-functions/catalog.ts +39 -7
- package/src/cli/pane-functions/dom.ts +19 -8
- package/src/cli/pane-functions/function-help.ts +3 -1
- package/src/cli/pane-functions/headless.ts +12 -1
- package/src/cli/pane-functions/index.ts +39 -0
- package/src/cli/pane-functions/lock-note.ts +56 -0
- package/src/components/command-bar/list/view.tsx +2 -1
- package/src/components/index.ts +3 -2
- package/src/components/layout/header.tsx +58 -14
- package/src/components/layout/shell/chrome.ts +10 -4
- package/src/components/ui/column-grid.tsx +206 -0
- package/src/components/ui/display.tsx +40 -3
- package/src/components/ui/fill.ts +8 -0
- package/src/components/ui/index.ts +2 -0
- package/src/market-data/quotes/screener-live-quotes.ts +31 -7
- package/src/plugins/builtin/account-management/form-components.tsx +4 -7
- package/src/plugins/builtin/account-management/password-dialog.tsx +3 -2
- package/src/plugins/builtin/backtest/pane.tsx +9 -3
- package/src/plugins/builtin/buildout/detail/company.tsx +4 -3
- package/src/plugins/builtin/buildout/detail/ui.tsx +9 -12
- package/src/plugins/builtin/cloud/askg/pane.tsx +2 -1
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +3 -2
- package/src/plugins/builtin/cloud/thesis/detail.tsx +11 -9
- package/src/plugins/builtin/congress-trades/detail.tsx +8 -4
- package/src/plugins/builtin/connections/pane.tsx +16 -5
- package/src/plugins/builtin/cpi/pane.tsx +5 -4
- package/src/plugins/builtin/doe/index.tsx +11 -4
- package/src/plugins/builtin/doe/pane.tsx +5 -4
- package/src/plugins/builtin/doe/prices.ts +84 -0
- package/src/plugins/builtin/doe/series.ts +4 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -21
- package/src/plugins/builtin/earnings-calls/index.tsx +2 -0
- package/src/plugins/builtin/earnings-calls/lock-note.ts +17 -0
- package/src/plugins/builtin/equity-screener/headless.ts +59 -1
- package/src/plugins/builtin/equity-screener/live.ts +82 -31
- package/src/plugins/builtin/equity-screener/pane.tsx +60 -23
- package/src/plugins/builtin/estimate-revisions/client.ts +1 -1
- package/src/plugins/builtin/estimate-revisions/headless.ts +9 -2
- package/src/plugins/builtin/estimate-revisions/index.tsx +3 -0
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +23 -0
- package/src/plugins/builtin/executives/index.tsx +18 -0
- package/src/plugins/builtin/exposure/charts.tsx +4 -3
- package/src/plugins/builtin/filing-events/index.tsx +8 -10
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/gpu/equities.tsx +9 -6
- package/src/plugins/builtin/iv-history/format.ts +3 -0
- package/src/plugins/builtin/iv-history/headless.ts +3 -1
- package/src/plugins/builtin/iv-history/index.tsx +3 -1
- package/src/plugins/builtin/iv-history/model.ts +3 -1
- package/src/plugins/builtin/iv-history/screen-pane.tsx +3 -1
- package/src/plugins/builtin/iv-history/universe.ts +1 -1
- package/src/plugins/builtin/jobs/headless.ts +6 -20
- package/src/plugins/builtin/jobs/index.tsx +2 -0
- package/src/plugins/builtin/jobs/lock-note.ts +16 -0
- package/src/plugins/builtin/options/index.tsx +2 -1
- package/src/plugins/builtin/options/model-spot.ts +94 -0
- package/src/plugins/builtin/options/open-interest-as-of.ts +63 -0
- package/src/plugins/builtin/options/view.tsx +55 -9
- package/src/plugins/builtin/options-scenario/client.ts +33 -9
- package/src/plugins/builtin/options-scenario/csp.ts +168 -0
- package/src/plugins/builtin/options-scenario/evidence.ts +3 -0
- package/src/plugins/builtin/options-scenario/headless.ts +24 -8
- package/src/plugins/builtin/options-scenario/index.tsx +3 -1
- package/src/plugins/builtin/options-scenario/pane.tsx +32 -6
- package/src/plugins/builtin/risk-factors/index.tsx +18 -0
- package/src/plugins/builtin/risk-factors/pane.tsx +2 -1
- package/src/plugins/builtin/scanner/headless.ts +9 -2
- package/src/plugins/builtin/scanner/index.ts +3 -0
- package/src/plugins/builtin/scanner/lock-note.ts +17 -0
- package/src/plugins/builtin/series-inventory.ts +1 -1
- package/src/plugins/builtin/shared/cloud-denied.ts +39 -8
- package/src/plugins/builtin/shared/pro-lock-note.ts +58 -0
- package/src/plugins/builtin/shared/series-list-detail.tsx +10 -5
- package/src/plugins/builtin/social-mentions/client.ts +3 -3
- package/src/plugins/builtin/social-mentions/headless.ts +6 -2
- package/src/plugins/builtin/social-mentions/index.tsx +2 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +15 -0
- package/src/plugins/builtin/supply-chain/flow.tsx +15 -13
- package/src/plugins/builtin/supply-chain/graph.tsx +6 -4
- package/src/plugins/builtin/supply-chain/pane.tsx +8 -5
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +14 -1
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +9 -7
- package/src/plugins/builtin/ticker-detail/headless.ts +4 -1
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +105 -104
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -2
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +34 -8
- package/src/plugins/builtin/vol-surface/pane.tsx +6 -3
- package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
- package/src/renderers/dom/data-table/index.tsx +48 -15
- package/src/renderers/dom/data-table/row.tsx +1 -1
- package/src/renderers/dom/desktop/popover.tsx +6 -15
- package/src/renderers/dom/host/input.tsx +27 -3
- package/src/renderers/dom/host/surface-3d.tsx +6 -4
- package/src/renderers/dom/styles.css +22 -4
- package/src/renderers/electrobun/view/app-services.ts +2 -22
- package/src/renderers/electrobun/view/news-source.ts +42 -0
- package/src/series/validate.ts +5 -0
- package/src/shares/dialog.tsx +2 -1
- package/src/sources/provider-router/financials.ts +5 -0
- package/src/types/financials.ts +22 -0
- package/src/types/plugin.ts +17 -4
- package/src/utils/financial-statements.ts +25 -0
- package/src/utils/fundamentals.ts +41 -1
package/package.json
CHANGED
package/src/api-client/data.ts
CHANGED
|
@@ -38,7 +38,7 @@ import type { BeneficialOwnersPayload, CloudBeneficialOwnersParams } from "./ben
|
|
|
38
38
|
import type { SocialMentionDayPosts, SocialMentionsPayload, SocialMentionsRange } from "./social-mentions";
|
|
39
39
|
import type { FuturesContractTermsPayload, FuturesCurveAsOfPayload, FuturesCurvePayload } from "./futures-curve";
|
|
40
40
|
import type { CotBoardPayload, CotContractPayload, CotFamily, CotClass } from "./cot";
|
|
41
|
-
import type { DoeBoardPayload } from "./doe";
|
|
41
|
+
import type { DoeBoardPayload, DoePricesPayload } from "./doe";
|
|
42
42
|
import type { CarbonBoard, CarbonHistory, EuropePowerBoard, EuropePowerHistory, GasStorageBoard, GasStorageHistory, GasStorageSeasonal } from "./europe-energy";
|
|
43
43
|
import type { GpuBoardPayload, GpuEventsPayload, GpuHistoryPayload, GpuHistoryQuery } from "./gpu";
|
|
44
44
|
import type { CpiBoardPayload } from "./cpi";
|
|
@@ -586,6 +586,10 @@ export class CloudDataApi {
|
|
|
586
586
|
return this.request<DoeBoardPayload>("/cloud/doe/board");
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
+
async getCloudDoePrices(): Promise<DoePricesPayload> {
|
|
590
|
+
return this.request<DoePricesPayload>("/cloud/doe/prices");
|
|
591
|
+
}
|
|
592
|
+
|
|
589
593
|
async getCloudAttention(window: AttentionWindow = "now", symbol?: string): Promise<AttentionPayload> {
|
|
590
594
|
const path = symbol ? `/cloud/attention/${encodeURIComponent(symbol)}` : "/cloud/attention";
|
|
591
595
|
return this.request<AttentionPayload>(`${path}?window=${window}`);
|
package/src/api-client/doe.ts
CHANGED
|
@@ -73,3 +73,22 @@ export interface DoeBoardPayload {
|
|
|
73
73
|
reports: DoeReportStatus[];
|
|
74
74
|
series: DoeSeriesRow[];
|
|
75
75
|
}
|
|
76
|
+
|
|
77
|
+
/** One EIA weekly US retail price series, as GET /cloud/doe/prices serves it. */
|
|
78
|
+
interface DoePriceSeries {
|
|
79
|
+
id: string;
|
|
80
|
+
/** The EIA source key, which a report cites. */
|
|
81
|
+
key: string;
|
|
82
|
+
label: string;
|
|
83
|
+
unit: "usd-per-gallon";
|
|
84
|
+
/** [Monday the EIA surveyed, dollars per gallon], oldest first, the last five years. */
|
|
85
|
+
points: Array<[string, number]>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface DoePricesPayload {
|
|
89
|
+
source: "EIA";
|
|
90
|
+
generatedAt: string;
|
|
91
|
+
status: "available" | "partial" | "unavailable";
|
|
92
|
+
gaps: string[];
|
|
93
|
+
series: DoePriceSeries[];
|
|
94
|
+
}
|
package/src/api-client/index.ts
CHANGED
|
@@ -812,6 +812,7 @@ class GloomApiClient {
|
|
|
812
812
|
getCloudAwards = this.data.getCloudAwards.bind(this.data);
|
|
813
813
|
getCloudAward = this.data.getCloudAward.bind(this.data);
|
|
814
814
|
getCloudDoeBoard = this.data.getCloudDoeBoard.bind(this.data);
|
|
815
|
+
getCloudDoePrices = this.data.getCloudDoePrices.bind(this.data);
|
|
815
816
|
getCloudAttention = this.data.getCloudAttention.bind(this.data);
|
|
816
817
|
getCloudPowerBoard = this.data.getCloudPowerBoard.bind(this.data);
|
|
817
818
|
getCloudPowerHistory = this.data.getCloudPowerHistory.bind(this.data);
|
|
@@ -10,13 +10,25 @@ import {
|
|
|
10
10
|
performUpdate,
|
|
11
11
|
type ReleaseInfo,
|
|
12
12
|
type UpdateCheckResult,
|
|
13
|
+
type UpdateProgress,
|
|
13
14
|
} from "../../updater";
|
|
14
15
|
import { VERSION } from "../../version";
|
|
16
|
+
import { debugLog } from "../../utils/debug-log";
|
|
15
17
|
import { reportCrash } from "../../telemetry/crash-reports";
|
|
16
18
|
import { runAutomated } from "../../telemetry/usage-counts";
|
|
17
19
|
|
|
18
20
|
const UPDATE_CHECK_INTERVAL_MS = 60 * 60_000; // hourly
|
|
19
21
|
|
|
22
|
+
const updateLog = debugLog.createLogger("update");
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The header and the command bar show a failed update's message, and the header
|
|
26
|
+
* cuts it to its room: the log keeps what the updater actually said.
|
|
27
|
+
*/
|
|
28
|
+
function logUpdateFailure(progress: UpdateProgress): void {
|
|
29
|
+
if (progress.phase === "error") updateLog.error("update failed", { error: progress.error });
|
|
30
|
+
}
|
|
31
|
+
|
|
20
32
|
/** An update is being downloaded, applied, or is downloaded and waiting for a restart. */
|
|
21
33
|
function updateInFlight(progress: AppState["updateProgress"]): boolean {
|
|
22
34
|
return progress?.phase === "downloading" || progress?.phase === "replacing" || progress?.phase === "ready";
|
|
@@ -53,6 +65,7 @@ export function useAppUpdateRuntime({
|
|
|
53
65
|
const startUpdate = useCallback((release: ReleaseInfo) => {
|
|
54
66
|
dispatch({ type: "SET_UPDATE_PROGRESS", progress: { phase: "downloading", percent: 0 } });
|
|
55
67
|
void performUpdate(release, (progress) => {
|
|
68
|
+
logUpdateFailure(progress);
|
|
56
69
|
dispatch({ type: "SET_UPDATE_PROGRESS", progress });
|
|
57
70
|
});
|
|
58
71
|
}, [dispatch]);
|
|
@@ -66,6 +79,7 @@ export function useAppUpdateRuntime({
|
|
|
66
79
|
dispatch({ type: "SET_UPDATE_PROGRESS", progress: { phase: "replacing" } });
|
|
67
80
|
void applyUpdate((progress) => {
|
|
68
81
|
if (progress.phase === "error") restartingRef.current = false;
|
|
82
|
+
logUpdateFailure(progress);
|
|
69
83
|
dispatch({ type: "SET_UPDATE_PROGRESS", progress });
|
|
70
84
|
});
|
|
71
85
|
}, [dispatch, enabled, isDetachedWindow, stateRef]);
|
|
@@ -91,6 +105,8 @@ export function useAppUpdateRuntime({
|
|
|
91
105
|
return { kind: "error", error: message || "Update check failed" };
|
|
92
106
|
});
|
|
93
107
|
|
|
108
|
+
if (result.kind === "error") updateLog.warn("update check failed", { error: result.error });
|
|
109
|
+
|
|
94
110
|
if (!manual) {
|
|
95
111
|
if (result.kind === "available") {
|
|
96
112
|
dispatch({ type: "SET_UPDATE_AVAILABLE", release: result.release });
|
|
@@ -1,28 +1,102 @@
|
|
|
1
1
|
{
|
|
2
|
-
"src/components/layout/pane/header.tsx":
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"src/components/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"src/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"src/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"src/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"src/renderers/dom/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"src/renderers/dom/
|
|
27
|
-
|
|
2
|
+
"src/components/layout/pane/header.tsx": {
|
|
3
|
+
"uses": 2,
|
|
4
|
+
"why": "The title bar's inset for the window controls comes from the shell's header geometry, which the terminal shares, in cells."
|
|
5
|
+
},
|
|
6
|
+
"src/components/layout/pane/sizing.ts": {
|
|
7
|
+
"uses": 3,
|
|
8
|
+
"why": "Panes receive their size in cells (PaneProps); this gives the desktop's taller chrome row in those cells."
|
|
9
|
+
},
|
|
10
|
+
"src/components/layout/shell/drag/slide.ts": {
|
|
11
|
+
"uses": 4,
|
|
12
|
+
"why": "A dragged pane moves by offsets of the saved layout, which is kept in cells."
|
|
13
|
+
},
|
|
14
|
+
"src/components/layout/shell/pane/layers.tsx": {
|
|
15
|
+
"uses": 6,
|
|
16
|
+
"why": "Pane rectangles in the saved layout are in cells; this is where they become pixels."
|
|
17
|
+
},
|
|
18
|
+
"src/components/ui/select-button.tsx": {
|
|
19
|
+
"uses": 2,
|
|
20
|
+
"why": "A select's width is given in cells by the pane, as its terminal button's is."
|
|
21
|
+
},
|
|
22
|
+
"src/renderers/dom/data-table/dom.ts": {
|
|
23
|
+
"uses": 26,
|
|
24
|
+
"why": "The table's scroll and pointer API speaks rows and columns to callers shared with the terminal (j/k, paging, visible range); a row of text is one line tall."
|
|
25
|
+
},
|
|
26
|
+
"src/renderers/dom/data-table/index.tsx": {
|
|
27
|
+
"uses": 19,
|
|
28
|
+
"why": "Every pane gives its columns in cells (DataTableColumn.width) and rows are one line tall: the horizontal extent, the overflow decision, virtualization and paging follow from them."
|
|
29
|
+
},
|
|
30
|
+
"src/renderers/dom/data-table/row.tsx": {
|
|
31
|
+
"uses": 10,
|
|
32
|
+
"why": "Column widths and gaps are cells in the column model every pane uses; a row is one line tall."
|
|
33
|
+
},
|
|
34
|
+
"src/renderers/dom/desktop/controls.tsx": {
|
|
35
|
+
"uses": 6,
|
|
36
|
+
"why": "Callers give a button's size in cells; a framed field keeps its text one cell off its frame, as the terminal's does."
|
|
37
|
+
},
|
|
38
|
+
"src/renderers/dom/desktop/list-view.tsx": {
|
|
39
|
+
"uses": 2,
|
|
40
|
+
"why": "The caller gives a list's height in rows."
|
|
41
|
+
},
|
|
42
|
+
"src/renderers/dom/desktop/query-bar.tsx": {
|
|
43
|
+
"uses": 1,
|
|
44
|
+
"why": "The pane gives its search field's width in cells."
|
|
45
|
+
},
|
|
46
|
+
"src/renderers/dom/desktop/select-field.tsx": {
|
|
47
|
+
"uses": 2,
|
|
48
|
+
"why": "An inline select is one row tall, the row it sits in."
|
|
49
|
+
},
|
|
50
|
+
"src/renderers/dom/dom-ui-host.tsx": {
|
|
51
|
+
"uses": 6,
|
|
52
|
+
"why": "Exposes the cell size to components that still size in cells (chart bitmaps, the company logo)."
|
|
53
|
+
},
|
|
54
|
+
"src/renderers/dom/host/input.tsx": {
|
|
55
|
+
"uses": 3,
|
|
56
|
+
"why": "Callers give a field's width and height in cells; a one-line field is one row tall."
|
|
57
|
+
},
|
|
58
|
+
"src/renderers/dom/host/live-frame.ts": {
|
|
59
|
+
"uses": 8,
|
|
60
|
+
"why": "A resize's live frame is in cells, the pane's unit, until the pane renders at the new size."
|
|
61
|
+
},
|
|
62
|
+
"src/renderers/dom/host/mouse.ts": {
|
|
63
|
+
"uses": 10,
|
|
64
|
+
"why": "Pointer handlers receive cell coordinates, the event API every renderer shares."
|
|
65
|
+
},
|
|
66
|
+
"src/renderers/dom/host/scroll-box.tsx": {
|
|
67
|
+
"uses": 22,
|
|
68
|
+
"why": "The scroller's API (scrollTop, scrollBy, viewport) is in rows and columns for callers shared with the terminal; content insets are given in cells."
|
|
69
|
+
},
|
|
70
|
+
"src/renderers/dom/host/size-observer.ts": {
|
|
71
|
+
"uses": 6,
|
|
72
|
+
"why": "Observed sizes are reported in cells, as callers expect."
|
|
73
|
+
},
|
|
74
|
+
"src/renderers/dom/host/style.ts": {
|
|
75
|
+
"uses": 27,
|
|
76
|
+
"why": "Maps the shared Box and Text props that components give in cells (width, height, padding, margin, gap, position) to pixels."
|
|
77
|
+
},
|
|
78
|
+
"src/renderers/dom/host/surface-fit.ts": {
|
|
79
|
+
"uses": 4,
|
|
80
|
+
"why": "A chart takes its box when its cells come within a cell of it, because panes still count their space in cells."
|
|
81
|
+
},
|
|
82
|
+
"src/renderers/dom/host/tabs.tsx": {
|
|
83
|
+
"uses": 2,
|
|
84
|
+
"why": "A tab strip is one row tall, the row every caller reserves for it."
|
|
85
|
+
},
|
|
86
|
+
"src/renderers/dom/host/text.tsx": {
|
|
87
|
+
"uses": 2,
|
|
88
|
+
"why": "The ASCII wordmark draws one glyph per cell."
|
|
89
|
+
},
|
|
90
|
+
"src/renderers/dom/input-host.tsx": {
|
|
91
|
+
"uses": 4,
|
|
92
|
+
"why": "The viewport is reported in cells (useViewport), the unit every renderer shares."
|
|
93
|
+
},
|
|
94
|
+
"src/renderers/dom/native-renderer.ts": {
|
|
95
|
+
"uses": 4,
|
|
96
|
+
"why": "The renderer's width and height are reported in cells, the unit every renderer shares."
|
|
97
|
+
},
|
|
98
|
+
"src/renderers/dom/styles.css": {
|
|
99
|
+
"uses": 14,
|
|
100
|
+
"why": "Defines the cell size and the base line height; status, footer and hint lines are one row; a few insets are one cell, as the terminal's (the floating pane's two-cell grip, a query bar segment)."
|
|
101
|
+
}
|
|
28
102
|
}
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
formatPriceRange,
|
|
32
32
|
} from "../helpers";
|
|
33
33
|
import { cliStyles, colorBySign, renderSection, renderStats } from "../../utils/cli-output";
|
|
34
|
+
import { statementHistoryNotes } from "../../utils/financial-statements";
|
|
34
35
|
import { formatPerShareNumber } from "../../utils/reported-money";
|
|
35
36
|
import {
|
|
36
37
|
analystTargetCurrency,
|
|
@@ -48,9 +49,9 @@ import type { FuturesContractTermsPayload } from "../../api-client/futures-curve
|
|
|
48
49
|
import { futuresSettleText, isFuturesInstrument } from "../../market-data/futures-contract";
|
|
49
50
|
import {
|
|
50
51
|
chainHasExpiry,
|
|
51
|
-
chainWithModelFigures,
|
|
52
52
|
formatExpiryList,
|
|
53
53
|
loadOptionModelInputs,
|
|
54
|
+
loadOptionsChainOpenInterest,
|
|
54
55
|
formatOptionDeltaCell,
|
|
55
56
|
formatOptionIvCell,
|
|
56
57
|
formatOptionQuoteCell,
|
|
@@ -58,7 +59,12 @@ import {
|
|
|
58
59
|
OPTION_MODEL_RATE,
|
|
59
60
|
OPTIONS_USAGE,
|
|
60
61
|
optionRows,
|
|
62
|
+
optionsBasisFields,
|
|
63
|
+
optionsBasisFootnote,
|
|
61
64
|
parseOptionExpiration,
|
|
65
|
+
requestListedOpenInterest,
|
|
66
|
+
valueChainAtModelSpot,
|
|
67
|
+
type OptionsChainBasis,
|
|
62
68
|
} from "./options-chain";
|
|
63
69
|
import { DEFAULT_LEAPS_CRITERIA, LEAPS_USAGE, parseLeapsCriteria, runLeapsScreen, takeLeapsOptions } from "./options-leaps";
|
|
64
70
|
import type { CliResultColumn } from "../result";
|
|
@@ -950,11 +956,13 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
950
956
|
const freshness = rowsFreshness(rows, {
|
|
951
957
|
...REPORTED_DATA, basis: "financial statements", observedKey: "date", oldest: null,
|
|
952
958
|
});
|
|
959
|
+
// The rows are the annual years served; older ones are not, and the closing lines say so.
|
|
960
|
+
const historyNotes = statementHistoryNotes("annual", rows.map((row) => row.date), listing.key);
|
|
953
961
|
// NDJSON keeps the raw statement rows it always wrote; CSV gets the table and its closing lines.
|
|
954
962
|
const tables = tabular
|
|
955
963
|
? selectTables({
|
|
956
964
|
tables: [exportRowsTable("Annual statements", STATEMENT_COLUMNS, rows)],
|
|
957
|
-
footer: reportFooterLines({ freshness }),
|
|
965
|
+
footer: reportFooterLines({ freshness, notes: historyNotes }),
|
|
958
966
|
})
|
|
959
967
|
: undefined;
|
|
960
968
|
ctx.printResult({
|
|
@@ -966,6 +974,7 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
966
974
|
quarterlyStatements: financials.quarterlyStatements.length,
|
|
967
975
|
fundamentals: exportedFundamentals(financials.fundamentals),
|
|
968
976
|
profile: financials.profile,
|
|
977
|
+
...(historyNotes.length > 0 ? { notes: historyNotes } : {}),
|
|
969
978
|
},
|
|
970
979
|
freshness,
|
|
971
980
|
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
@@ -973,6 +982,7 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
973
982
|
heading: listingHeading(identity),
|
|
974
983
|
rows: () => rows,
|
|
975
984
|
columns: STATEMENT_COLUMNS,
|
|
985
|
+
...(historyNotes.length > 0 ? { footnote: historyNotes.join("\n"), exportNotes: historyNotes } : {}),
|
|
976
986
|
...(tables && ctx.cliOptions.format === "csv" ? { tables } : {}),
|
|
977
987
|
// Annual statements in the company's reporting currency.
|
|
978
988
|
exportIdentity: { symbolOrSeries: listing.key, currencyOrUnit: rows.find((row) => row.currency)?.currency ?? null, interval: "annual" },
|
|
@@ -1495,6 +1505,8 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1495
1505
|
const quotePromise = loadListingQuote(market.dataProvider, listing);
|
|
1496
1506
|
const fate = listingFateLoader(listing, market, quotePromise);
|
|
1497
1507
|
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;
|
|
1498
1510
|
const { result, chain: loaded } = await loadForListing(listing, market, ctx, async () => {
|
|
1499
1511
|
const cached = await market.dataProvider.getCachedQuery?.("getOptionsChain", [symbol, exchange, expirationDate, undefined])
|
|
1500
1512
|
.load({ force: refresh });
|
|
@@ -1524,7 +1536,13 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1524
1536
|
}
|
|
1525
1537
|
const quote = await quotePromise;
|
|
1526
1538
|
const identity = listingIdentity(listing, quote);
|
|
1527
|
-
|
|
1539
|
+
// IV and delta are valued at one spot and instant, named under the table and on every exported row.
|
|
1540
|
+
const { chain, spot: modelSpot } = valueChainAtModelSpot(loaded, inputs, Date.now(), identity.exchange);
|
|
1541
|
+
const basis: OptionsChainBasis = { spot: modelSpot, dividendYield: inputs.dividendYield ?? null,
|
|
1542
|
+
openInterest: await loadOptionsChainOpenInterest(symbol, chain, listedOpenInterest) };
|
|
1543
|
+
const basisFields = optionsBasisFields(basis);
|
|
1544
|
+
const currency = [...chain.calls, ...chain.puts].find((contract) => contract.currency?.trim())?.currency.trim().toUpperCase()
|
|
1545
|
+
?? quote?.currency ?? null;
|
|
1528
1546
|
// A failed refresh falls back to the stored chain, which can be days old.
|
|
1529
1547
|
const refreshWarning = result?.refreshError == null ? null
|
|
1530
1548
|
: `Options refresh failed; showing the chain stored ${new Date(result.fetchedAt).toISOString()}`
|
|
@@ -1546,23 +1564,54 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1546
1564
|
...(chain.delayMinutes != null ? { delayMinutes: chain.delayMinutes } : {}),
|
|
1547
1565
|
stale: refreshWarning != null,
|
|
1548
1566
|
});
|
|
1567
|
+
const contractCount = chain.calls.length + chain.puts.length;
|
|
1549
1568
|
ctx.printResult({
|
|
1550
1569
|
data: chain,
|
|
1551
1570
|
metadata: {
|
|
1552
1571
|
...listingMetadata(identity),
|
|
1553
1572
|
expirations: chain.expirationDates,
|
|
1573
|
+
currency,
|
|
1574
|
+
interval: OPTIONS_EXPORT_INTERVAL,
|
|
1575
|
+
// `iv` is annualised volatility and `delta` the option's change per 1.00 move in the underlying, both decimals.
|
|
1576
|
+
units: { iv: "annualized decimal", delta: "decimal per 1.00 move in the underlying" },
|
|
1554
1577
|
// What `iv` and `delta` on each contract are valued from; the provider's own impliedVolatility is left as sent.
|
|
1555
|
-
model: {
|
|
1578
|
+
model: {
|
|
1579
|
+
spot: basisFields.spot, spotBasis: basisFields.spotBasis, spotAsOf: basisFields.spotAsOf,
|
|
1580
|
+
valuationTime: modelSpot ? new Date(modelSpot.valuationTime).toISOString() : null,
|
|
1581
|
+
dividendYield: inputs.dividendYield ?? null, rate: OPTION_MODEL_RATE,
|
|
1582
|
+
},
|
|
1583
|
+
openInterest: { asOf: basisFields.openInterestAsOf, listedAsOf: basis.openInterest?.listedDate ?? null,
|
|
1584
|
+
compared: basis.openInterest?.compared ?? 0, matched: basis.openInterest?.matched ?? 0 },
|
|
1556
1585
|
},
|
|
1557
1586
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
1558
1587
|
freshness,
|
|
1559
1588
|
}, {
|
|
1560
1589
|
heading: listingHeading(identity),
|
|
1561
|
-
rows: optionRows,
|
|
1590
|
+
rows: (data: OptionsChain) => optionRows(data).map((row) => ({ ...row, ...basisFields })),
|
|
1562
1591
|
// Choosing an expiry needs the list; once one is chosen the table is that expiry.
|
|
1563
1592
|
summary: (data: OptionsChain) => expirationDate == null ? formatExpiryList(data.expirationDates) : "",
|
|
1564
1593
|
empty: `No option contracts for ${listingTitle(identity)}.`,
|
|
1594
|
+
...(contractCount > 0 ? { footnote: optionsBasisFootnote(basis) } : {}),
|
|
1595
|
+
exportIdentity: { symbolOrSeries: listing.key, currencyOrUnit: currency, interval: OPTIONS_EXPORT_INTERVAL },
|
|
1596
|
+
// Exports state each decimal's unit in its header and end every row with the spot and dates it is as of.
|
|
1565
1597
|
columns: [
|
|
1598
|
+
{ key: "side", header: "Side" },
|
|
1599
|
+
{ key: "contract", header: "Contract" },
|
|
1600
|
+
{ key: "expiration", header: "Expiry" },
|
|
1601
|
+
{ key: "strike", header: "Strike" },
|
|
1602
|
+
{ key: "last", header: "Last" },
|
|
1603
|
+
{ key: "bid", header: "Bid" },
|
|
1604
|
+
{ key: "ask", header: "Ask" },
|
|
1605
|
+
{ key: "iv", header: "IV (annualized decimal)" },
|
|
1606
|
+
{ key: "delta", header: "Delta (decimal)" },
|
|
1607
|
+
{ key: "volume", header: "Vol" },
|
|
1608
|
+
{ key: "openInterest", header: "OI" },
|
|
1609
|
+
{ key: "spot", header: "Spot" },
|
|
1610
|
+
{ key: "spotBasis", header: "Spot basis" },
|
|
1611
|
+
{ key: "spotAsOf", header: "Spot as of" },
|
|
1612
|
+
{ key: "openInterestAsOf", header: "OI as of" },
|
|
1613
|
+
],
|
|
1614
|
+
textColumns: [
|
|
1566
1615
|
{ key: "side", header: "Side" },
|
|
1567
1616
|
{ key: "contract", header: "Contract", shrink: false },
|
|
1568
1617
|
// The contract symbol already carries the date, so this goes first when the table is narrow.
|
|
@@ -1580,6 +1629,9 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1580
1629
|
});
|
|
1581
1630
|
}
|
|
1582
1631
|
|
|
1632
|
+
/** What an options chain export's `interval` says: one quote snapshot, not bars. */
|
|
1633
|
+
const OPTIONS_EXPORT_INTERVAL = "snapshot";
|
|
1634
|
+
|
|
1583
1635
|
const FX_USAGE = "fx <currency> | fx <base>/<quote>";
|
|
1584
1636
|
|
|
1585
1637
|
async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
@@ -5,6 +5,12 @@ import { cliStyles, cliTerminalWidth, wrapText } from "../../utils/cli-output";
|
|
|
5
5
|
import { isFiniteNumber } from "../../utils/guards";
|
|
6
6
|
import { calculateOptionGreeks, solveChainVolatilities } from "../../plugins/builtin/options/analytics";
|
|
7
7
|
import { optionQuoteSide } from "../../plugins/builtin/options/market-reference";
|
|
8
|
+
import {
|
|
9
|
+
formatModelSpotPrice, modelSpotLabel, optionModelSpot, type OptionModelSpot,
|
|
10
|
+
} from "../../plugins/builtin/options/model-spot";
|
|
11
|
+
import {
|
|
12
|
+
chainOpenInterestAsOf, loadListedOpenInterest, openInterestAsOfLabel, type OpenInterestAsOf,
|
|
13
|
+
} from "../../plugins/builtin/options/open-interest-as-of";
|
|
8
14
|
import { DEFAULT_OPTION_CALC_DRAFT } from "../../plugins/builtin/options-calculator/model";
|
|
9
15
|
import { expiryIsoDate, missingExpiryText, parseOptionExpiration } from "../../utils/option-expiry";
|
|
10
16
|
|
|
@@ -61,6 +67,8 @@ export function formatExpiryList(expirations: readonly number[], width = cliTerm
|
|
|
61
67
|
export interface OptionModelInputs {
|
|
62
68
|
/** The underlying's current price; absent when the quote is missing or stale. */
|
|
63
69
|
spot?: number;
|
|
70
|
+
/** The quote `spot` is read from, for the spot the chain is valued at (`optionModelSpot`). */
|
|
71
|
+
quote?: Quote;
|
|
64
72
|
dividendYield?: number;
|
|
65
73
|
now?: number;
|
|
66
74
|
}
|
|
@@ -119,7 +127,7 @@ export async function loadOptionModelInputs(
|
|
|
119
127
|
.find((candidate) => candidate && candidate.stale !== true && isFiniteNumber(candidate.price) && candidate.price > 0);
|
|
120
128
|
const dividendYield = financials?.fundamentals?.dividendYield;
|
|
121
129
|
return {
|
|
122
|
-
...(current ? { spot: current.price } : {}),
|
|
130
|
+
...(current ? { spot: current.price, quote: current } : {}),
|
|
123
131
|
...(isFiniteNumber(dividendYield) && dividendYield >= 0 ? { dividendYield } : {}),
|
|
124
132
|
};
|
|
125
133
|
}
|
|
@@ -131,6 +139,72 @@ export function chainWithModelFigures(chain: OptionsChain, inputs: OptionModelIn
|
|
|
131
139
|
return { ...chain, calls: chain.calls.map(add), puts: chain.puts.map(add) };
|
|
132
140
|
}
|
|
133
141
|
|
|
142
|
+
/**
|
|
143
|
+
* What a chain's IV, delta and open interest are as of: the spot and instant
|
|
144
|
+
* they are valued at, and the session the open interest counts. The rows and
|
|
145
|
+
* the closing lines of every format say it.
|
|
146
|
+
*/
|
|
147
|
+
export interface OptionsChainBasis {
|
|
148
|
+
spot: OptionModelSpot | null;
|
|
149
|
+
dividendYield: number | null;
|
|
150
|
+
openInterest: OpenInterestAsOf | null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The chain with `iv` and `delta` valued at the spot `optionModelSpot` picks for it, and that spot. */
|
|
154
|
+
export function valueChainAtModelSpot(
|
|
155
|
+
chain: OptionsChain, inputs: OptionModelInputs, now: number, exchange?: string,
|
|
156
|
+
): { chain: OptionsChain; spot: OptionModelSpot | null } {
|
|
157
|
+
const spot = optionModelSpot(inputs.quote, chain, now, exchange);
|
|
158
|
+
return {
|
|
159
|
+
chain: chainWithModelFigures(chain, spot ? { spot: spot.price, dividendYield: inputs.dividendYield, now: spot.valuationTime } : inputs),
|
|
160
|
+
spot,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The open-interest date of the chain's expiry, from OPX's listing of it; null when the listing cannot be read. */
|
|
165
|
+
export async function loadOptionsChainOpenInterest(
|
|
166
|
+
symbol: string, chain: OptionsChain, listed?: ReturnType<typeof loadListedOpenInterest> | null,
|
|
167
|
+
): Promise<OpenInterestAsOf | null> {
|
|
168
|
+
const expiration = [...chain.calls, ...chain.puts][0]?.expiration;
|
|
169
|
+
if (expiration == null) return null;
|
|
170
|
+
const payload = await (listed ?? loadListedOpenInterest(symbol, expiryIsoDate(expiration)));
|
|
171
|
+
return payload ? chainOpenInterestAsOf(chain, payload) : null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** OPX's listing of an expiry, requested while the chain loads. */
|
|
175
|
+
export function requestListedOpenInterest(symbol: string, expiration: number) {
|
|
176
|
+
return loadListedOpenInterest(symbol, expiryIsoDate(expiration));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The line under the chain: `Greeks at spot 89.79 (close Mon 12 Oct 20:00 UTC), dividend yield 2.34%, rate 4% · OI as of Fri 9 Oct`.
|
|
181
|
+
* An open interest whose date cannot be established says why.
|
|
182
|
+
*/
|
|
183
|
+
export function optionsBasisFootnote(basis: OptionsChainBasis): string {
|
|
184
|
+
const parts: string[] = [];
|
|
185
|
+
if (basis.spot) {
|
|
186
|
+
parts.push(`Greeks at spot ${formatModelSpotPrice(basis.spot.price)} (${modelSpotLabel(basis.spot)}), `
|
|
187
|
+
+ `dividend yield ${basis.dividendYield == null ? "0%" : `${Number((basis.dividendYield * 100).toFixed(2))}%`}, `
|
|
188
|
+
+ `rate ${Number((OPTION_MODEL_RATE * 100).toFixed(2))}%`);
|
|
189
|
+
}
|
|
190
|
+
const oi = basis.openInterest;
|
|
191
|
+
parts.push(oi?.date ? openInterestAsOfLabel(oi)
|
|
192
|
+
: oi?.listedDate && oi.compared > 0
|
|
193
|
+
? `OI date not published: the counts differ from OPX's ${oi.listedDate} counts on ${oi.compared - oi.matched} of ${oi.compared} contracts`
|
|
194
|
+
: openInterestAsOfLabel(null));
|
|
195
|
+
return parts.join(" · ");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** The spot and open-interest fields every exported row carries. */
|
|
199
|
+
export function optionsBasisFields(basis: OptionsChainBasis) {
|
|
200
|
+
return {
|
|
201
|
+
spot: basis.spot?.price ?? null,
|
|
202
|
+
spotBasis: basis.spot ? basis.spot.basis === "close" ? "close" : basis.spot.session ?? "last" : null,
|
|
203
|
+
spotAsOf: basis.spot ? new Date(basis.spot.observedAt).toISOString() : null,
|
|
204
|
+
openInterestAsOf: basis.openInterest?.date ?? null,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
134
208
|
export function optionRows(chain: OptionsChain) {
|
|
135
209
|
const contracts = [
|
|
136
210
|
...chain.calls.map((contract) => ({ side: "call", contract })),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { formatShortDate, parseDisplayDate } from "../../utils/datetime-format";
|
|
2
2
|
import { formatReportedMoney } from "../../utils/reported-money";
|
|
3
|
-
import { convertedPriceEarningsNote, epsConversionNote, fundamentalsCurrency, reportedEnterpriseValue, ttmConversionNote } from "../../utils/fundamentals";
|
|
3
|
+
import { convertedPriceEarningsNote, enterpriseValueLabel, enterpriseValueReconciliationNote, epsConversionNote, fundamentalsCurrency, reportedEnterpriseValue, ttmConversionNote } from "../../utils/fundamentals";
|
|
4
4
|
import { latestFinancialPeriod } from "../../utils/latest-financial-period";
|
|
5
5
|
import { exportedFundamentals, formatPriceEarnings, priceEarningsOnEarnings } from "../../utils/price-earnings";
|
|
6
6
|
import { describeFundamentalMarketCap, selectMarketCapitalization } from "../../utils/market-capitalization";
|
|
@@ -336,6 +336,13 @@ function reportedMoney(value: number | undefined, currency: string | undefined,
|
|
|
336
336
|
return { text: formatReportedMoney(value, currency, perShare), value, unit: currency?.trim() || "ccy?" };
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
/** The EV line: labelled "(provider)" when it is more than 5% from market cap + debt - cash, "unavailable" when withdrawn. */
|
|
340
|
+
function enterpriseValueFigure(figure: MetricFigure, fundamentals: TickerFinancials["fundamentals"]): MetricFigure {
|
|
341
|
+
const label = enterpriseValueLabel(fundamentals);
|
|
342
|
+
if (label === "unavailable") return { text: "unavailable", value: null, unit: figure.unit };
|
|
343
|
+
return label === "provider" && figure.text !== "—" ? { ...figure, text: `${figure.text} (provider)` } : figure;
|
|
344
|
+
}
|
|
345
|
+
|
|
339
346
|
function percentFigure(fraction: number | null | undefined, format: (value: number) => string): MetricFigure {
|
|
340
347
|
return fraction != null ? { text: format(fraction), value: fraction * 100, unit: "%" } : { text: "—" };
|
|
341
348
|
}
|
|
@@ -366,7 +373,7 @@ function fundamentalsMetrics(
|
|
|
366
373
|
const receipt = fundamentals?.sharesOutstanding != null && sharesOutstandingInReceipts(quote, fundamentals, profile?.description);
|
|
367
374
|
return [
|
|
368
375
|
{ label: "Market Cap", ...marketCap },
|
|
369
|
-
{ label: "Enterprise Value", ...enterpriseValue },
|
|
376
|
+
{ label: "Enterprise Value", ...enterpriseValueFigure(enterpriseValue, fundamentals) },
|
|
370
377
|
// A multiple over a loss is N/M, even beside a positive one the source served from an older period.
|
|
371
378
|
{ label: "P/E (TTM)", ...multipleFigure(priceEarningsOnEarnings(fundamentals?.trailingPE, fundamentals?.eps)) },
|
|
372
379
|
{ label: "Forward P/E", ...multipleFigure(priceEarningsOnEarnings(fundamentals?.forwardPE, fundamentals?.forwardEps)) },
|
|
@@ -404,10 +411,11 @@ function metricLines(metrics: readonly FundamentalsMetric[]): Array<[string, str
|
|
|
404
411
|
const TRAILING_MONEY_METRICS = new Set(["EPS (TTM)", "Revenue (TTM)", "Net Income (TTM)", "Operating Cash Flow (TTM)", "Free Cash Flow (TTM)"]);
|
|
405
412
|
|
|
406
413
|
/**
|
|
407
|
-
* The
|
|
408
|
-
* currency than the amounts beside it, and what a P/E on
|
|
414
|
+
* The notes under shown fundamentals: how a labelled or withdrawn EV compares with market cap + debt - cash, what a
|
|
415
|
+
* converted TTM block was converted from, an EPS in another currency than the amounts beside it, and what a P/E on
|
|
416
|
+
* such an EPS divides.
|
|
409
417
|
*/
|
|
410
|
-
function
|
|
418
|
+
function fundamentalsNotes(
|
|
411
419
|
fundamentals: TickerFinancials["fundamentals"],
|
|
412
420
|
metrics: readonly FundamentalsMetric[],
|
|
413
421
|
quoteCurrency: string | undefined,
|
|
@@ -418,7 +426,8 @@ function trailingConversionNotes(
|
|
|
418
426
|
const priceEarnings = metrics.some((metric) => metric.label === "P/E (TTM)" && typeof metric.value === "number")
|
|
419
427
|
? convertedPriceEarningsNote(fundamentals, quoteCurrency)
|
|
420
428
|
: undefined;
|
|
421
|
-
|
|
429
|
+
const enterpriseValue = shown("Enterprise Value") ? enterpriseValueReconciliationNote(fundamentals) : undefined;
|
|
430
|
+
return [enterpriseValue, conversion, eps, priceEarnings].filter((note): note is string => !!note);
|
|
422
431
|
}
|
|
423
432
|
|
|
424
433
|
const VALUATION_METRICS = new Set(["Market Cap", "Enterprise Value", "P/E (TTM)", "Forward P/E", "PEG", "EPS (TTM)"]);
|
|
@@ -523,7 +532,7 @@ export function renderFundamentalsReport(
|
|
|
523
532
|
const fund = fundFactsNote(symbol, financials.exchange || quote?.exchangeName, financials);
|
|
524
533
|
if (lines.length === before) lines.push("", cliStyles.muted([`No ${view} reported for ${financials.symbol}.`, fund].filter(Boolean).join(" ")));
|
|
525
534
|
else if (fund) lines.push(cliStyles.muted(fund));
|
|
526
|
-
for (const note of
|
|
535
|
+
for (const note of fundamentalsNotes(financials.fundamentals, metrics, quote?.currency)) lines.push(cliStyles.muted(note));
|
|
527
536
|
if (view === "fundamentals") appendTextSection(lines, "Description", profile?.description);
|
|
528
537
|
return lines.join("\n");
|
|
529
538
|
}
|
|
@@ -563,7 +572,7 @@ export function fundamentalsReportTables(
|
|
|
563
572
|
notes: [
|
|
564
573
|
...metrics.length === 0
|
|
565
574
|
? [`No ${view} reported for ${financials.symbol}.`]
|
|
566
|
-
:
|
|
575
|
+
: fundamentalsNotes(financials.fundamentals, metrics, financials.quote?.currency),
|
|
567
576
|
...fund ? [fund] : [],
|
|
568
577
|
],
|
|
569
578
|
}),
|
|
@@ -726,7 +735,7 @@ export async function buildTickerReport({
|
|
|
726
735
|
const metrics = fundamentalsMetrics(quote, priced.fundamentals, profile, { text: marketCapText }, priceReturns, fundamentalsCurrency(financials), { text: enterpriseValueText });
|
|
727
736
|
const beforeFundamentals = lines.length;
|
|
728
737
|
appendMetricSection(lines, "Fundamentals", metricLines(metrics));
|
|
729
|
-
for (const note of
|
|
738
|
+
for (const note of fundamentalsNotes(fundamentals, metrics, quote?.currency)) lines.push(cliStyles.muted(note));
|
|
730
739
|
// Where a fund's facts would go: under its fundamentals, or in their place.
|
|
731
740
|
const fund = fundFactsNote(quote?.symbol ?? symbol,
|
|
732
741
|
quote?.tradingExchangeName || listingExchange || quote?.listingExchangeName || quote?.exchangeName || tickerFile?.metadata.exchange, financials, tickerFile);
|
package/src/cli/glossary.ts
CHANGED
|
@@ -351,7 +351,7 @@ export const GLOSSARY: readonly GlossaryEntry[] = [
|
|
|
351
351
|
term: "TTM", aliases: ["trailing twelve months", "trailing 12 months", "trailing four quarters", "ltm", "last twelve months"],
|
|
352
352
|
functions: ["FA"],
|
|
353
353
|
definition: "Trailing twelve months: the four latest quarters added up, a year that ends with the latest quarter rather than the fiscal year. "
|
|
354
|
-
+ "FA's quarterly leverage ratios read their flows over it, and show < 4 quarters when fewer are on file.",
|
|
354
|
+
+ "FA's quarterly leverage and valuation ratios (P/E, EV / EBITDA, P/S, FCF yield) read their flows over it, and show < 4 quarters when fewer are on file.",
|
|
355
355
|
},
|
|
356
356
|
// Funds (ETF). Named in what ETF says it cannot show yet.
|
|
357
357
|
{
|