gloomberb 0.16.2 → 0.16.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/brokers/account-cache.ts +45 -0
- package/src/cli/command-options.ts +74 -0
- package/src/cli/commands/command-utils.ts +29 -0
- package/src/cli/commands/market.ts +170 -54
- package/src/cli/commands/overview.ts +62 -19
- package/src/cli/commands/ticker.ts +138 -43
- package/src/cli/desktop-pane-shot.ts +3 -0
- package/src/cli/help.ts +26 -6
- package/src/cli/history-rows.ts +187 -15
- package/src/cli/index.ts +12 -7
- package/src/cli/options.ts +5 -1
- package/src/cli/pane-functions/capabilities.ts +5 -0
- package/src/cli/pane-functions/dom.ts +69 -16
- package/src/cli/pane-functions/function-help.ts +4 -2
- package/src/cli/pane-functions/headless.ts +163 -31
- package/src/cli/pane-functions/index.ts +9 -2
- package/src/cli/pane-functions/report.ts +8 -1
- package/src/cli/pane-functions/resolver.ts +28 -2
- package/src/cli/pane-functions/screenshot.ts +4 -0
- package/src/cli/registry.ts +6 -3
- package/src/cli/report-tables.ts +460 -0
- package/src/cli/result.ts +13 -3
- package/src/components/data-table/export.ts +6 -51
- package/src/components/data-table/view.tsx +3 -1
- package/src/components/layout/shell/active-drag.ts +67 -35
- package/src/components/layout/shell/cursor-occlusion.ts +6 -5
- package/src/components/layout/shell/drag/index.ts +12 -7
- package/src/components/layout/shell/drag/live.ts +122 -0
- package/src/components/layout/shell/drag/overlays.tsx +179 -35
- package/src/components/layout/shell/drag/runtime.ts +62 -50
- package/src/components/layout/shell/drag/slide.ts +10 -0
- package/src/components/layout/shell/index.tsx +37 -51
- package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
- package/src/components/layout/shell/native/surfaces.ts +30 -14
- package/src/components/layout/shell/pane/layers.tsx +419 -179
- package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
- package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
- package/src/data/json-persistence.ts +17 -3
- package/src/data/json-storage.ts +2 -0
- package/src/data/memory-resource-store.ts +46 -0
- package/src/market-data/market/freshness.ts +45 -0
- package/src/market-data/quotes/freshness.ts +5 -0
- package/src/plugins/builtin/analytics/pane-model.ts +10 -6
- package/src/plugins/builtin/attention/pane.tsx +1 -1
- package/src/plugins/builtin/catalysts/detail.tsx +1 -1
- package/src/plugins/builtin/company-attention/pane.tsx +1 -1
- package/src/plugins/builtin/holders/client.ts +3 -6
- package/src/plugins/builtin/holders/format.ts +31 -1
- package/src/plugins/builtin/holders/headless.ts +26 -5
- package/src/plugins/builtin/holders/pane.tsx +22 -13
- package/src/plugins/builtin/holders/report-header.ts +46 -5
- package/src/plugins/builtin/kelly-sizer/pane.tsx +12 -6
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
- package/src/plugins/builtin/members/pane.tsx +10 -0
- package/src/plugins/builtin/perps/pane.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
- package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
- package/src/plugins/builtin/portfolio-list/cli/render.ts +2 -0
- package/src/plugins/builtin/portfolio-list/headless.ts +2 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
- package/src/plugins/builtin/shared/members-menu.tsx +260 -0
- package/src/plugins/builtin/short-volume/pane.tsx +1 -1
- package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
- package/src/plugins/builtin/themes/pane.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
- package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +20 -4
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
- package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
- package/src/plugins/builtin/world-venue-map/index.ts +5 -5
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
- package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
- package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
- package/src/plugins/builtin/world-venue-map/model.ts +16 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
- package/src/renderers/browser/app-services.ts +3 -1
- package/src/renderers/dom/data-table/dom.ts +31 -0
- package/src/renderers/dom/data-table/index.tsx +5 -0
- package/src/renderers/dom/host/box.tsx +113 -17
- package/src/renderers/dom/host/drag-shield.ts +24 -0
- package/src/renderers/dom/host/live-frame.ts +54 -0
- package/src/renderers/dom/host/mouse.ts +8 -1
- package/src/renderers/dom/host/scroll-box.tsx +6 -5
- package/src/renderers/dom/host/tabs.tsx +3 -2
- package/src/renderers/dom/styles.css +13 -5
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +3 -0
- package/src/renderers/electrobun/view/host/native.ts +0 -10
- package/src/renderers/electrobun/view/host/window-move.ts +28 -0
- package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
- package/src/renderers/electrobun/view/ui-host.tsx +2 -5
- package/src/renderers/opentui/ui-host.tsx +7 -1
- package/src/sources/gloomberb-cloud/normalizers.ts +16 -20
- package/src/sources/provider-router/batches.ts +3 -3
- package/src/sources/provider-router/financial-routes.ts +2 -2
- package/src/sources/provider-router/financials.ts +70 -7
- package/src/sources/provider-router/primary.ts +2 -2
- package/src/state/pane-scroll-registry.tsx +67 -35
- package/src/test-support/margin-account.ts +108 -0
- package/src/time-series/resolution.ts +15 -0
- package/src/types/financials.ts +32 -3
- package/src/ui/host.tsx +12 -0
- package/src/ui/index.tsx +8 -3
- package/src/ui/scroll-box-axis.ts +26 -0
- package/src/utils/currency-units.ts +8 -3
- package/src/utils/depositary-receipt.ts +54 -10
- package/src/utils/display-number.ts +63 -0
- package/src/utils/fundamentals.ts +17 -1
- package/src/utils/price-history-integrity.ts +19 -6
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AppResourceStorePort } from "../core/app-service-ports";
|
|
2
|
+
import type { CachedResourceRecord, ResourceCacheKey } from "../data/resource-store";
|
|
2
3
|
import type { BrokerAdapter } from "../types/broker";
|
|
3
4
|
import type { BrokerInstanceConfig } from "../types/config";
|
|
4
5
|
import type { CachePolicy } from "../types/persistence";
|
|
@@ -21,6 +22,50 @@ function brokerAccountNamespace(instance: BrokerInstanceConfig): string {
|
|
|
21
22
|
return `plugin:${instance.brokerType}`;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
/** A saved account snapshot, which a renderer without the cache database has to keep elsewhere. */
|
|
26
|
+
export function isBrokerAccountSnapshotKey(key: Pick<ResourceCacheKey, "namespace" | "kind">): boolean {
|
|
27
|
+
return key.kind === BROKER_ACCOUNT_SNAPSHOT_KIND && key.namespace.startsWith("plugin:");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Saves an account snapshot written by a process without the cache database
|
|
32
|
+
* (the desktop view), keeping its own times. Anything else is refused.
|
|
33
|
+
*/
|
|
34
|
+
export function saveBrokerAccountRecord(
|
|
35
|
+
resources: Pick<AppResourceStorePort, "set">,
|
|
36
|
+
record: CachedResourceRecord | null | undefined,
|
|
37
|
+
): boolean {
|
|
38
|
+
if (!record || typeof record.namespace !== "string" || typeof record.entityKey !== "string"
|
|
39
|
+
|| !isBrokerAccountSnapshotKey(record)
|
|
40
|
+
|| ![record.fetchedAt, record.staleAt, record.expiresAt].every(Number.isFinite)) return false;
|
|
41
|
+
resources.set(record, record.value, {
|
|
42
|
+
schemaVersion: record.schemaVersion,
|
|
43
|
+
provenance: record.provenance,
|
|
44
|
+
fetchedAt: record.fetchedAt,
|
|
45
|
+
cachePolicy: { staleMs: record.staleAt - record.fetchedAt, expireMs: record.expiresAt - record.fetchedAt },
|
|
46
|
+
});
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function deleteBrokerAccountRecord(resources: Pick<AppResourceStorePort, "delete">, key: ResourceCacheKey): void {
|
|
51
|
+
if (isBrokerAccountSnapshotKey(key)) resources.delete(key);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Every unexpired account snapshot saved for these profiles, as stored. */
|
|
55
|
+
export function listPersistedBrokerAccountRecords(
|
|
56
|
+
resources: Pick<AppResourceStorePort, "list">,
|
|
57
|
+
brokerInstances: readonly BrokerInstanceConfig[],
|
|
58
|
+
): CachedResourceRecord[] {
|
|
59
|
+
return brokerInstances.flatMap((instance) => resources.list<PersistedBrokerAccountSnapshot>({
|
|
60
|
+
namespace: brokerAccountNamespace(instance),
|
|
61
|
+
kind: BROKER_ACCOUNT_SNAPSHOT_KIND,
|
|
62
|
+
entityKey: instance.id,
|
|
63
|
+
}, {
|
|
64
|
+
schemaVersion: BROKER_ACCOUNT_SNAPSHOT_SCHEMA_VERSION,
|
|
65
|
+
touch: false,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
|
|
24
69
|
export function getBrokerAccountCacheSourceKey(
|
|
25
70
|
instance: BrokerInstanceConfig,
|
|
26
71
|
broker?: BrokerAdapter | null,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { CliCommandDef } from "../types/plugin";
|
|
2
|
+
import { fail } from "./errors";
|
|
3
|
+
import { suggestCliOption } from "./help";
|
|
4
|
+
|
|
5
|
+
/** A built-in command, which may say more about an option it does not take than the list of those it does. */
|
|
6
|
+
export interface BuiltinCliCommandDef extends CliCommandDef {
|
|
7
|
+
/** A sentence for an unknown option people reach for, such as `--from` on `history`; null for the rest. */
|
|
8
|
+
unknownOptionHint?(flag: string): string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface DeclaredOption {
|
|
12
|
+
flag: string;
|
|
13
|
+
takesValue: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** `--range`, `--range=5Y`: an option. `-5`, `---`, `--` and plain words are not. */
|
|
17
|
+
const OPTION_TOKEN = /^--[a-z]/i;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The options a command's help declares, from each spelling such as
|
|
21
|
+
* `--range <range>` (takes a value) or `--all` (a switch). Null when it takes
|
|
22
|
+
* any option: `--<option> <value>` on `fn` and `shot`.
|
|
23
|
+
*/
|
|
24
|
+
function declaredOptions(command: CliCommandDef): Map<string, DeclaredOption> | null {
|
|
25
|
+
const declared = new Map<string, DeclaredOption>();
|
|
26
|
+
for (const option of command.help?.options ?? []) {
|
|
27
|
+
for (const spelling of option.flags.split(",")) {
|
|
28
|
+
const trimmed = spelling.trim();
|
|
29
|
+
if (trimmed.startsWith("--<")) return null;
|
|
30
|
+
const match = /^(--?[a-z][\w-]*)(?:[ =](.+))?$/i.exec(trimmed);
|
|
31
|
+
if (match) declared.set(match[1]!, { flag: match[1]!, takesValue: match[2] != null });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return declared;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function failUnknownOption(command: BuiltinCliCommandDef, flag: string, declared: Map<string, DeclaredOption>): never {
|
|
38
|
+
const flags = [...declared.keys()];
|
|
39
|
+
const suggestion = suggestCliOption(flag, flags);
|
|
40
|
+
const help = `See gloomberb help ${command.name}.`;
|
|
41
|
+
fail(`Unknown option ${flag} for ${command.name}.`, [
|
|
42
|
+
command.unknownOptionHint?.(flag) ?? "",
|
|
43
|
+
suggestion ? `Did you mean ${suggestion}?` : "",
|
|
44
|
+
flags.length > 0 ? `Options: ${flags.join(", ")}. ${help}` : `${command.name} takes no options. ${help}`,
|
|
45
|
+
].filter(Boolean).join("\n"));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Fails on the first `--option` a built-in command does not declare, which it
|
|
50
|
+
* would otherwise ignore and answer as if it had not been given: `history
|
|
51
|
+
* --from 1995-01-01` printing the default year. Global flags are already out
|
|
52
|
+
* of `args`; a declared option's value is skipped whatever it looks like, and
|
|
53
|
+
* nothing from `literalStart` on (after a bare `--`) is read as an option.
|
|
54
|
+
*/
|
|
55
|
+
export function checkCliCommandOptions(
|
|
56
|
+
command: BuiltinCliCommandDef,
|
|
57
|
+
args: readonly string[],
|
|
58
|
+
literalStart = args.length,
|
|
59
|
+
): void {
|
|
60
|
+
const declared = declaredOptions(command);
|
|
61
|
+
if (!declared) return;
|
|
62
|
+
for (let index = 0; index < Math.min(literalStart, args.length); index += 1) {
|
|
63
|
+
const arg = args[index]!;
|
|
64
|
+
if (!OPTION_TOKEN.test(arg)) continue;
|
|
65
|
+
const equals = arg.indexOf("=");
|
|
66
|
+
const flag = equals >= 0 ? arg.slice(0, equals) : arg;
|
|
67
|
+
const option = declared.get(flag);
|
|
68
|
+
if (!option) failUnknownOption(command, flag, declared);
|
|
69
|
+
if (!option.takesValue && equals >= 0) {
|
|
70
|
+
fail(`${flag} takes no value.`, `Write it alone, as ${flag}. See gloomberb help ${command.name}.`);
|
|
71
|
+
}
|
|
72
|
+
if (option.takesValue && equals < 0) index += 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -46,6 +46,35 @@ export function requireArg(value: string | undefined, usage: string, ctx: CliCom
|
|
|
46
46
|
return value;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Fails on arguments past the `max` a command takes rather than dropping them:
|
|
51
|
+
* `fx NGN ZAR` printing NGN alone reads as an answer for both. `usage` is the
|
|
52
|
+
* command's usage line without `gloomberb`, `takes` what it takes ("one
|
|
53
|
+
* currency", "no arguments").
|
|
54
|
+
*/
|
|
55
|
+
export function rejectExtraArgs(
|
|
56
|
+
args: readonly string[],
|
|
57
|
+
max: number,
|
|
58
|
+
{ usage, takes, advice }: { usage: string; takes: string; advice?: string },
|
|
59
|
+
ctx: Pick<CliCommandContext, "fail">,
|
|
60
|
+
): void {
|
|
61
|
+
if (args.length <= max) return;
|
|
62
|
+
const command = usage.split(" ")[0];
|
|
63
|
+
ctx.fail(`${command} takes ${takes}; got ${args.join(" ")}.`, [advice, `Usage: gloomberb ${usage}`].filter(Boolean).join("\n"));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The one argument a command takes, such as a symbol: missing fails with the usage line, extras are not dropped. */
|
|
67
|
+
export function requireOneArg(
|
|
68
|
+
args: readonly string[],
|
|
69
|
+
usage: string,
|
|
70
|
+
noun: string,
|
|
71
|
+
ctx: CliCommandContext,
|
|
72
|
+
): string {
|
|
73
|
+
const value = requireArg(args[0], `Usage: gloomberb ${usage}`, ctx);
|
|
74
|
+
rejectExtraArgs(args, 1, { usage, takes: `one ${noun}`, advice: `Run it once per ${noun}.` }, ctx);
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
|
|
49
78
|
export function isoDate(value: unknown): string {
|
|
50
79
|
if (value instanceof Date) return value.toISOString();
|
|
51
80
|
if (typeof value === "number") return new Date(value).toISOString();
|
|
@@ -13,8 +13,9 @@ import { currencyMinorDigits, formatMarketPrice, formatMarketPriceWithCurrency,
|
|
|
13
13
|
import { getActiveQuoteDisplay, marketStateLabel } from "../../market-data/market/status";
|
|
14
14
|
import { formatCompact, formatDistributionAmount, formatPercent } from "../../utils/format";
|
|
15
15
|
import { withCliServices, withMarketData } from "../context";
|
|
16
|
-
import { isoDate, parsePositiveInt,
|
|
17
|
-
import {
|
|
16
|
+
import { isoDate, parsePositiveInt, rejectExtraArgs, requireOneArg, takeFlag, takeOption } from "./command-utils";
|
|
17
|
+
import type { BuiltinCliCommandDef } from "../command-options";
|
|
18
|
+
import { CLI_COMMAND_GROUPS, TABLE_SECTION_OPTION } from "../help";
|
|
18
19
|
import {
|
|
19
20
|
formatChangePercentCell,
|
|
20
21
|
formatCountCell,
|
|
@@ -33,10 +34,27 @@ import {
|
|
|
33
34
|
} from "../../plugins/builtin/research/analyst-model";
|
|
34
35
|
import { optionQuoteSide } from "../../plugins/builtin/options/market-reference";
|
|
35
36
|
import { getPublishedUsEquityCalendarYears, getPublishedUsEquitySession } from "../../market-data/published-us-sessions";
|
|
36
|
-
import { renderFundamentalsReport } from "./ticker";
|
|
37
|
-
import {
|
|
37
|
+
import { fundamentalsReportTables, renderFundamentalsReport } from "./ticker";
|
|
38
|
+
import type { CliResultColumn } from "../result";
|
|
39
|
+
import {
|
|
40
|
+
exportRowsTable,
|
|
41
|
+
reportFooterLines,
|
|
42
|
+
selectReportTables,
|
|
43
|
+
type CliReportTables,
|
|
44
|
+
} from "../report-tables";
|
|
45
|
+
import {
|
|
46
|
+
historyFacts,
|
|
47
|
+
historyFlagNote,
|
|
48
|
+
historyAllRangeYears,
|
|
49
|
+
historyIntervalsByRange,
|
|
50
|
+
historyNotes,
|
|
51
|
+
historyPriceDecimals,
|
|
52
|
+
historyRows,
|
|
53
|
+
historyUnit,
|
|
54
|
+
} from "../history-rows";
|
|
38
55
|
import { CRYPTO_BOARD_HINT, quoteNotes } from "./crypto-hints";
|
|
39
56
|
import { formatUtcTime } from "../../utils/utc-time";
|
|
57
|
+
import { isFiniteNumber } from "../../utils/guards";
|
|
40
58
|
import { exportedFundamentals } from "../../utils/price-earnings";
|
|
41
59
|
import { fundamentalsFreshness, quotesFreshness, rowsFreshness } from "../freshness";
|
|
42
60
|
import {
|
|
@@ -65,11 +83,14 @@ import { nonUsSecListingVenue } from "../../utils/sec";
|
|
|
65
83
|
import type { MarketContext } from "../types";
|
|
66
84
|
import {
|
|
67
85
|
holderListFacts,
|
|
86
|
+
holderShareBasisMarker,
|
|
87
|
+
holderShareBasisNote,
|
|
68
88
|
holderValueBasis,
|
|
69
89
|
moneyColumnHeader,
|
|
70
90
|
nonUsHolderCaveat,
|
|
71
91
|
sharedReportDate,
|
|
72
92
|
} from "../../plugins/builtin/holders/report-header";
|
|
93
|
+
import { formatHolderOwnershipPercent, holderValueCurrency } from "../../plugins/builtin/holders/format";
|
|
73
94
|
import { fetchBeneficialOwners } from "../../plugins/builtin/holders/beneficial-client";
|
|
74
95
|
import { filingFormMatches, SEC_FILING_FETCH_LIMIT } from "../../plugins/builtin/sec/forms";
|
|
75
96
|
import {
|
|
@@ -250,6 +271,7 @@ function holderRows(data: HolderData, ownerTypes?: Set<string>) {
|
|
|
250
271
|
value: holder.value ?? null,
|
|
251
272
|
percentHeld: holder.percentHeld ?? null,
|
|
252
273
|
changeShares: holder.changeShares ?? null,
|
|
274
|
+
shareBasis: holder.shareBasis ?? null,
|
|
253
275
|
}));
|
|
254
276
|
}
|
|
255
277
|
|
|
@@ -421,11 +443,16 @@ function failHistory(error: unknown, symbol: string, ctx: Parameters<CliCommandD
|
|
|
421
443
|
return ctx.fail(errorMessage(error) ?? `No history available for ${symbol}`, CRYPTO_BOARD_HINT);
|
|
422
444
|
}
|
|
423
445
|
|
|
446
|
+
const HISTORY_USAGE = "history <symbol> [--range <range>]";
|
|
447
|
+
const HISTORY_ALL_RANGE = `ALL gives the full history the source has, up to ${historyAllRangeYears()} years`;
|
|
448
|
+
/** Options people reach for to ask for dates, which history answers with a range. */
|
|
449
|
+
const DATE_WINDOW_OPTIONS = new Set(["--from", "--to", "--start", "--end", "--since", "--until", "--date", "--start-date", "--end-date"]);
|
|
450
|
+
|
|
424
451
|
async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
425
452
|
const args = [...rawArgs];
|
|
426
453
|
const range = parseRange(takeOption(args, "--range"), ctx);
|
|
427
454
|
const requestedExchange = takeOption(args, "--exchange");
|
|
428
|
-
const raw =
|
|
455
|
+
const raw = requireOneArg(args, HISTORY_USAGE, "symbol", ctx);
|
|
429
456
|
await withMarketData(ctx, async (market) => {
|
|
430
457
|
const listing = await requireCliListing(raw, requestedExchange, market, ctx);
|
|
431
458
|
const { symbol, exchange } = listing.request;
|
|
@@ -439,7 +466,11 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
439
466
|
loadListingQuote(market.dataProvider, listing),
|
|
440
467
|
]);
|
|
441
468
|
const identity = listingIdentity(listing, quote);
|
|
442
|
-
|
|
469
|
+
// Without a quote currency, the listing metadata research reads (as `ticker` does) may still state it.
|
|
470
|
+
const listed = quote?.currency ? quote : await market.dataProvider.getQuoteMetadata?.(symbol, exchange).catch(() => null);
|
|
471
|
+
const unit = historyUnit(listing.symbol, listed ?? quote);
|
|
472
|
+
const data = historyRows(points, resolution, unit.currency);
|
|
473
|
+
const interval = data[0]?.interval ?? null;
|
|
443
474
|
const decimals = historyPriceDecimals(data, listing.saved?.metadata.assetCategory);
|
|
444
475
|
const price = (value: unknown) => typeof value === "number" ? value.toFixed(decimals) : "";
|
|
445
476
|
// Intraday bars print in UTC, as the charts and time and sales label them, not the host zone.
|
|
@@ -450,17 +481,47 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
450
481
|
observedKey: "date",
|
|
451
482
|
oldest: null,
|
|
452
483
|
});
|
|
453
|
-
|
|
454
|
-
|
|
484
|
+
const notes = [...historyNotes(range, interval, data[0]?.date), historyFlagNote(data) ?? []].flat();
|
|
485
|
+
const priceColumns: CliResultColumn[] = [
|
|
486
|
+
intraday
|
|
487
|
+
? { key: "date", header: "Time", format: (value) => typeof value === "string" ? formatUtcTime(value) : "" }
|
|
488
|
+
: { key: "date", header: "Date" },
|
|
489
|
+
{ key: "open", header: "Open", align: "right", format: price },
|
|
490
|
+
{ key: "high", header: "High", align: "right", format: price },
|
|
491
|
+
{ key: "low", header: "Low", align: "right", format: price },
|
|
492
|
+
{ key: "close", header: "Close", align: "right", format: price },
|
|
493
|
+
{ key: "volume", header: "Volume", align: "right", format: formatCountCell },
|
|
494
|
+
];
|
|
495
|
+
// Text shows a flag only on a bar that has one; the column is dropped when none does.
|
|
496
|
+
const flagColumn: CliResultColumn = {
|
|
497
|
+
key: "flag", header: "Flag", format: (value) => typeof value === "string" ? cliStyles.warning(value) : "",
|
|
498
|
+
};
|
|
499
|
+
ctx.printResult({
|
|
500
|
+
data,
|
|
501
|
+
metadata: {
|
|
502
|
+
...listingMetadata(identity),
|
|
503
|
+
range,
|
|
504
|
+
resolution,
|
|
505
|
+
requestedRange: range,
|
|
506
|
+
currency: unit.currency,
|
|
507
|
+
unit: unit.unit,
|
|
508
|
+
interval,
|
|
509
|
+
firstDate: data[0]?.date ?? null,
|
|
510
|
+
lastDate: data.at(-1)?.date ?? null,
|
|
511
|
+
bars: data.length,
|
|
512
|
+
asOf: freshness?.asOf ?? null,
|
|
513
|
+
},
|
|
514
|
+
...(notes.length > 0 ? { warnings: notes } : {}),
|
|
515
|
+
freshness,
|
|
516
|
+
}, {
|
|
517
|
+
heading: `${listingHeading(identity)}\n${cliStyles.muted(historyFacts(unit, data).join(" · "))}`,
|
|
518
|
+
textColumns: [...priceColumns, flagColumn],
|
|
519
|
+
// Every exported row says its currency and bar size, so it survives head and concatenation.
|
|
455
520
|
columns: [
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
{ key: "high", header: "High", align: "right", format: price },
|
|
461
|
-
{ key: "low", header: "Low", align: "right", format: price },
|
|
462
|
-
{ key: "close", header: "Close", align: "right", format: price },
|
|
463
|
-
{ key: "volume", header: "Volume", align: "right", format: formatCountCell },
|
|
521
|
+
...priceColumns,
|
|
522
|
+
{ key: "currency", header: "Currency" },
|
|
523
|
+
{ key: "interval", header: "Interval" },
|
|
524
|
+
flagColumn,
|
|
464
525
|
],
|
|
465
526
|
});
|
|
466
527
|
});
|
|
@@ -468,11 +529,33 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
468
529
|
|
|
469
530
|
type FinancialsView = "statements" | "fundamentals" | "valuation";
|
|
470
531
|
|
|
532
|
+
const STATEMENT_COLUMNS: CliResultColumn<ReturnType<typeof financialStatementRows>[number]>[] = [
|
|
533
|
+
{ key: "date", header: "Date" },
|
|
534
|
+
{ key: "revenue", header: "Revenue", align: "right", format: (value) => value == null ? "" : formatCompact(Number(value)) },
|
|
535
|
+
{ key: "grossProfit", header: "Gross", align: "right", format: (value) => value == null ? "" : formatCompact(Number(value)) },
|
|
536
|
+
{ key: "operatingIncome", header: "Op Inc", align: "right", format: (value) => value == null ? "" : formatCompact(Number(value)) },
|
|
537
|
+
{ key: "netIncome", header: "Net Inc", align: "right", format: (value) => value == null ? "" : formatCompact(Number(value)) },
|
|
538
|
+
{ key: "eps", header: "EPS", align: "right", format: (value) => value == null ? "" : formatPerShareNumber(Number(value)) },
|
|
539
|
+
{ key: "currency", header: "Cur" },
|
|
540
|
+
];
|
|
541
|
+
|
|
471
542
|
async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1], view: FinancialsView) {
|
|
472
543
|
const args = [...rawArgs];
|
|
473
544
|
const exchangeOption = takeOption(args, "--exchange");
|
|
545
|
+
const sectionFlag = args.some((arg) => arg === "--section" || arg.startsWith("--section="));
|
|
546
|
+
const section = takeOption(args, "--section");
|
|
474
547
|
const commandName = view === "statements" ? "financials" : view;
|
|
475
|
-
const
|
|
548
|
+
const tabular = ctx.cliOptions.format === "csv" || ctx.cliOptions.format === "ndjson";
|
|
549
|
+
if (sectionFlag && !tabular) ctx.fail("--section picks one table of --csv or --ndjson output.");
|
|
550
|
+
if (sectionFlag && !section?.trim()) ctx.fail("--section needs a section title or number.");
|
|
551
|
+
const raw = requireOneArg(args, `${commandName} <symbol>`, "symbol", ctx);
|
|
552
|
+
const selectTables = (tables: CliReportTables) => {
|
|
553
|
+
try {
|
|
554
|
+
return selectReportTables(tables, section);
|
|
555
|
+
} catch (error) {
|
|
556
|
+
return ctx.fail(error instanceof Error ? error.message : String(error));
|
|
557
|
+
}
|
|
558
|
+
};
|
|
476
559
|
await withMarketData(ctx, async (market) => {
|
|
477
560
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
478
561
|
const financials = await loadForListing(listing, market, ctx, () => market.dataProvider.getTickerFinancials(
|
|
@@ -486,13 +569,28 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
486
569
|
...financials,
|
|
487
570
|
};
|
|
488
571
|
if (view !== "statements") {
|
|
572
|
+
const freshness = fundamentalsFreshness(financials);
|
|
489
573
|
ctx.printResult({
|
|
490
574
|
// JSON reads a multiple over a loss as null with its reason, never as a number.
|
|
491
575
|
data: { ...data, fundamentals: exportedFundamentals(data.fundamentals) },
|
|
492
|
-
freshness
|
|
493
|
-
}, {
|
|
576
|
+
freshness,
|
|
577
|
+
}, {
|
|
578
|
+
text: () => renderFundamentalsReport(data, view),
|
|
579
|
+
...(tabular ? { tables: selectTables(fundamentalsReportTables(data, view, freshness)) } : {}),
|
|
580
|
+
});
|
|
494
581
|
return;
|
|
495
582
|
}
|
|
583
|
+
const rows = financialStatementRows(data);
|
|
584
|
+
const freshness = rowsFreshness(rows, {
|
|
585
|
+
...REPORTED_DATA, basis: "financial statements", observedKey: "date", oldest: null,
|
|
586
|
+
});
|
|
587
|
+
// NDJSON keeps the raw statement rows it always wrote; CSV gets the table and its closing lines.
|
|
588
|
+
const tables = tabular
|
|
589
|
+
? selectTables({
|
|
590
|
+
tables: [exportRowsTable("Annual statements", STATEMENT_COLUMNS, rows)],
|
|
591
|
+
footer: reportFooterLines({ freshness }),
|
|
592
|
+
})
|
|
593
|
+
: undefined;
|
|
496
594
|
ctx.printResult({
|
|
497
595
|
data,
|
|
498
596
|
metadata: {
|
|
@@ -503,21 +601,12 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
503
601
|
fundamentals: exportedFundamentals(financials.fundamentals),
|
|
504
602
|
profile: financials.profile,
|
|
505
603
|
},
|
|
506
|
-
freshness
|
|
507
|
-
...REPORTED_DATA, basis: "financial statements", observedKey: "date", oldest: null,
|
|
508
|
-
}),
|
|
604
|
+
freshness,
|
|
509
605
|
}, {
|
|
510
606
|
heading: listingHeading(identity),
|
|
511
|
-
rows:
|
|
512
|
-
columns:
|
|
513
|
-
|
|
514
|
-
{ key: "revenue", header: "Revenue", align: "right", value: (row) => row.revenue == null ? "" : formatCompact(Number(row.revenue)) },
|
|
515
|
-
{ key: "grossProfit", header: "Gross", align: "right", value: (row) => row.grossProfit == null ? "" : formatCompact(Number(row.grossProfit)) },
|
|
516
|
-
{ key: "operatingIncome", header: "Op Inc", align: "right", value: (row) => row.operatingIncome == null ? "" : formatCompact(Number(row.operatingIncome)) },
|
|
517
|
-
{ key: "netIncome", header: "Net Inc", align: "right", value: (row) => row.netIncome == null ? "" : formatCompact(Number(row.netIncome)) },
|
|
518
|
-
{ key: "eps", header: "EPS", align: "right", format: (value) => value == null ? "" : formatPerShareNumber(Number(value)) },
|
|
519
|
-
{ key: "currency", header: "Cur" },
|
|
520
|
-
],
|
|
607
|
+
rows: () => rows,
|
|
608
|
+
columns: STATEMENT_COLUMNS,
|
|
609
|
+
...(tables && ctx.cliOptions.format === "csv" ? { tables } : {}),
|
|
521
610
|
});
|
|
522
611
|
});
|
|
523
612
|
}
|
|
@@ -527,6 +616,7 @@ async function runNews(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
527
616
|
const feed = parseNewsFeed(takeOption(args, "--feed"));
|
|
528
617
|
const exchangeOption = takeOption(args, "--exchange");
|
|
529
618
|
if (exchangeOption && !args[0]) ctx.fail("--exchange needs a symbol: gloomberb news <symbol> --exchange <code>");
|
|
619
|
+
rejectExtraArgs(args, 1, { usage: "news [symbol] [--feed <feed>]", takes: "one symbol at most", advice: "Run it once per symbol." }, ctx);
|
|
530
620
|
await withMarketData(ctx, async (market) => {
|
|
531
621
|
const listing = args[0] ? await requireCliListing(args[0], exchangeOption, market, ctx) : null;
|
|
532
622
|
const limit = ctx.cliOptions.limit ?? 20;
|
|
@@ -587,7 +677,7 @@ async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
587
677
|
const count = parsePositiveInt(takeOption(args, "--count"), ctx.cliOptions.limit ?? 15, "Count", ctx);
|
|
588
678
|
const exchangeOption = takeOption(args, "--exchange");
|
|
589
679
|
const form = takeOption(args, "--form")?.trim() || null;
|
|
590
|
-
const raw =
|
|
680
|
+
const raw = requireOneArg(args, "filings <symbol> [--count <n>] [--form <form>]", "symbol", ctx);
|
|
591
681
|
await withMarketData(ctx, async (market) => {
|
|
592
682
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
593
683
|
const { symbol, exchange } = listing.request;
|
|
@@ -702,7 +792,7 @@ async function runHolders(
|
|
|
702
792
|
const exchangeOption = takeOption(args, "--exchange");
|
|
703
793
|
const formOption = commandName === "holders" ? takeOption(args, "--form") : undefined;
|
|
704
794
|
const history = commandName === "holders" && takeFlag(args, "--history");
|
|
705
|
-
const raw =
|
|
795
|
+
const raw = requireOneArg(args, `${commandName} <symbol>`, "symbol", ctx);
|
|
706
796
|
const form = formOption == null ? "13f" : parseHolderForm(formOption);
|
|
707
797
|
if (!form) ctx.fail(`Unknown form "${formOption}".`, `Use one of ${HOLDER_FORMS.join(", ")}.`);
|
|
708
798
|
if (form === "13f" && history) ctx.fail("--history lists 13D/13G reports.", "Add --form 13d, 13g or all.");
|
|
@@ -724,16 +814,23 @@ async function runHolders(
|
|
|
724
814
|
const institutional = !ownerTypes?.has("insider");
|
|
725
815
|
const total = institutional ? data.summary?.institutionsCount ?? null : null;
|
|
726
816
|
const reportDate = sharedReportDate(rows);
|
|
727
|
-
|
|
817
|
+
// A London line's values can be dollars from the 13F filings; the listing's currency is not their unit.
|
|
818
|
+
const valueCurrency = holderValueCurrency(data);
|
|
819
|
+
const valueBasis = rows.length > 0 ? holderValueBasis(reportDate, data.valueBasis, valueCurrency) : null;
|
|
728
820
|
const positionsBasis = rows.length > 0 ? nonUsHolderCaveat(identity.exchange || data.exchange, data.currency) : null;
|
|
821
|
+
const shareBasisNote = holderShareBasisNote(data, rows);
|
|
822
|
+
const markedRows = rows.some((row) => holderShareBasisMarker(row));
|
|
729
823
|
// The heading names the listing; its unit, date and how much of the list follows on the same line.
|
|
730
|
-
const facts = holderListFacts({
|
|
824
|
+
const facts = holderListFacts({
|
|
825
|
+
currency: valueCurrency, listingCurrency: data.currency, asOf: data.asOf, shown, reported: rows.length, total,
|
|
826
|
+
});
|
|
731
827
|
ctx.printResult({
|
|
732
828
|
data,
|
|
733
829
|
metadata: {
|
|
734
830
|
...listingMetadata(identity),
|
|
735
831
|
summary: data.summary,
|
|
736
832
|
currency: data.currency ?? null,
|
|
833
|
+
valueCurrency: valueCurrency ?? null,
|
|
737
834
|
asOf: data.asOf ?? null,
|
|
738
835
|
shown,
|
|
739
836
|
reported: rows.length,
|
|
@@ -750,16 +847,23 @@ async function runHolders(
|
|
|
750
847
|
{ key: "name", header: "Holder" },
|
|
751
848
|
{ key: "reportDate", header: "Date" },
|
|
752
849
|
{ key: "shares", header: "Shares", align: "right", format: formatCountCell },
|
|
850
|
+
// Marks the rows of a home line held as receipts; absent when no row is.
|
|
851
|
+
...(markedRows ? [{ key: "shareBasis", header: "Basis", value: (row: Record<string, unknown>) => holderShareBasisMarker(row) }] : []),
|
|
753
852
|
{
|
|
754
853
|
key: "value",
|
|
755
|
-
header: moneyColumnHeader("Value",
|
|
854
|
+
header: moneyColumnHeader("Value", valueCurrency),
|
|
756
855
|
align: "right",
|
|
757
856
|
value: (row) => row.value == null ? "" : formatCompact(Number(row.value)),
|
|
758
857
|
},
|
|
759
|
-
{
|
|
858
|
+
{
|
|
859
|
+
key: "percentHeld",
|
|
860
|
+
header: "% Held",
|
|
861
|
+
align: "right",
|
|
862
|
+
format: (value) => isFiniteNumber(value) ? formatHolderOwnershipPercent(value) : "",
|
|
863
|
+
},
|
|
760
864
|
],
|
|
761
865
|
summary: (holderData: HolderData) => [
|
|
762
|
-
[valueBasis ? `Value = ${valueBasis}.` : "", positionsBasis ?? ""].filter(Boolean).join(" "),
|
|
866
|
+
[valueBasis ? `Value = ${valueBasis}.` : "", positionsBasis ?? "", shareBasisNote ?? ""].filter(Boolean).join(" "),
|
|
763
867
|
commandName === "insider" && ownerTypes ? insiderSummary(holderData, ownerTypes) : "",
|
|
764
868
|
].filter(Boolean).join("\n"),
|
|
765
869
|
empty: `No holders reported for ${listingTitle(identity)}.`,
|
|
@@ -770,7 +874,7 @@ async function runHolders(
|
|
|
770
874
|
async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
771
875
|
const args = [...rawArgs];
|
|
772
876
|
const exchangeOption = takeOption(args, "--exchange");
|
|
773
|
-
const raw =
|
|
877
|
+
const raw = requireOneArg(args, "analyst <symbol>", "symbol", ctx);
|
|
774
878
|
await withMarketData(ctx, async (market) => {
|
|
775
879
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
776
880
|
const [data, quote] = await Promise.all([
|
|
@@ -807,7 +911,7 @@ async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
807
911
|
async function runEvents(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
808
912
|
const args = [...rawArgs];
|
|
809
913
|
const exchangeOption = takeOption(args, "--exchange");
|
|
810
|
-
const raw =
|
|
914
|
+
const raw = requireOneArg(args, "events <symbol>", "symbol", ctx);
|
|
811
915
|
await withMarketData(ctx, async (market) => {
|
|
812
916
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
813
917
|
const [data, quote] = await Promise.all([
|
|
@@ -837,7 +941,7 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
837
941
|
const args = [...rawArgs];
|
|
838
942
|
const expiration = takeOption(args, "--expiration");
|
|
839
943
|
const exchangeOption = takeOption(args, "--exchange");
|
|
840
|
-
const raw =
|
|
944
|
+
const raw = requireOneArg(args, "options <symbol> [--expiration <unix>]", "symbol", ctx);
|
|
841
945
|
await withMarketData(ctx, async (market) => {
|
|
842
946
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
843
947
|
const { symbol, exchange } = listing.request;
|
|
@@ -884,8 +988,10 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
884
988
|
});
|
|
885
989
|
}
|
|
886
990
|
|
|
991
|
+
const FX_USAGE = "fx <currency>";
|
|
992
|
+
|
|
887
993
|
async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
888
|
-
const currency =
|
|
994
|
+
const currency = requireOneArg(rawArgs.map((arg) => arg.trim().toUpperCase()), FX_USAGE, "currency", ctx);
|
|
889
995
|
await withMarketData(ctx, async (market) => {
|
|
890
996
|
const baseCurrency = market.config.baseCurrency.trim().toUpperCase();
|
|
891
997
|
const load = (code: string) => market.dataProvider.getCachedQuery("getExchangeRate", [code])
|
|
@@ -948,7 +1054,7 @@ async function runEarnings(rawArgs: string[], ctx: Parameters<CliCommandDef["exe
|
|
|
948
1054
|
});
|
|
949
1055
|
}
|
|
950
1056
|
|
|
951
|
-
export const marketDataCliCommands:
|
|
1057
|
+
export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
952
1058
|
{
|
|
953
1059
|
name: "quote",
|
|
954
1060
|
description: "Show the latest price for one or more symbols",
|
|
@@ -976,13 +1082,23 @@ export const marketDataCliCommands: CliCommandDef[] = [
|
|
|
976
1082
|
description: "Fetch open, high, low, close, and volume over a range",
|
|
977
1083
|
help: {
|
|
978
1084
|
group: CLI_COMMAND_GROUPS.research,
|
|
979
|
-
usage: [
|
|
1085
|
+
usage: [HISTORY_USAGE],
|
|
980
1086
|
options: [
|
|
981
|
-
{ flags: "--range <range>", description: `${TIME_RANGES.join(", ")} (default 1Y)` },
|
|
1087
|
+
{ flags: "--range <range>", description: `${TIME_RANGES.join(", ")} (default 1Y); ${HISTORY_ALL_RANGE}` },
|
|
982
1088
|
EXCHANGE_OPTION,
|
|
983
1089
|
],
|
|
984
|
-
|
|
1090
|
+
sections: [{
|
|
1091
|
+
title: "Bars",
|
|
1092
|
+
lines: [
|
|
1093
|
+
`Each range comes in one bar size: ${historyIntervalsByRange()}. The line under the heading, and the --json metadata, give the currency, the bar size actually served, the first and last bar and the bar count. A warning says when the bars are coarser, or start later, than the range asked for.`,
|
|
1094
|
+
"A bar whose prices contradict each other (high below the open or close, or low above them) is left blank and flagged, such as high<open, with the count in a warning. Every exported row (--csv, --ndjson, --json) carries currency, interval and flag.",
|
|
1095
|
+
],
|
|
1096
|
+
}],
|
|
1097
|
+
examples: ["history AAPL", "history BHP:ASX --range 5Y", "history ZAR=X --range ALL", "history AAPL --range 5Y --csv > aapl.csv"],
|
|
985
1098
|
},
|
|
1099
|
+
unknownOptionHint: (flag) => DATE_WINDOW_OPTIONS.has(flag)
|
|
1100
|
+
? `history takes a --range instead of dates: ${TIME_RANGES.join(", ")}. ${HISTORY_ALL_RANGE}.`
|
|
1101
|
+
: null,
|
|
986
1102
|
execute: runHistory,
|
|
987
1103
|
},
|
|
988
1104
|
{
|
|
@@ -1032,8 +1148,8 @@ export const marketDataCliCommands: CliCommandDef[] = [
|
|
|
1032
1148
|
help: {
|
|
1033
1149
|
group: CLI_COMMAND_GROUPS.companyData,
|
|
1034
1150
|
usage: ["financials <symbol>"],
|
|
1035
|
-
options: [EXCHANGE_OPTION],
|
|
1036
|
-
examples: ["financials MSFT", "financials SAN:EPA", "financials MSFT --json"],
|
|
1151
|
+
options: [EXCHANGE_OPTION, TABLE_SECTION_OPTION],
|
|
1152
|
+
examples: ["financials MSFT", "financials SAN:EPA", "financials MSFT --json", "financials MSFT --csv > msft.csv"],
|
|
1037
1153
|
},
|
|
1038
1154
|
execute: (args, ctx) => runFinancials(args, ctx, "statements"),
|
|
1039
1155
|
},
|
|
@@ -1043,8 +1159,8 @@ export const marketDataCliCommands: CliCommandDef[] = [
|
|
|
1043
1159
|
help: {
|
|
1044
1160
|
group: CLI_COMMAND_GROUPS.companyData,
|
|
1045
1161
|
usage: ["fundamentals <symbol>"],
|
|
1046
|
-
options: [EXCHANGE_OPTION],
|
|
1047
|
-
examples: ["fundamentals NVDA", "fundamentals ASML:AMS"],
|
|
1162
|
+
options: [EXCHANGE_OPTION, TABLE_SECTION_OPTION],
|
|
1163
|
+
examples: ["fundamentals NVDA", "fundamentals ASML:AMS", "fundamentals NVDA --csv --section fundamentals"],
|
|
1048
1164
|
},
|
|
1049
1165
|
execute: (args, ctx) => runFinancials(args, ctx, "fundamentals"),
|
|
1050
1166
|
},
|
|
@@ -1054,8 +1170,8 @@ export const marketDataCliCommands: CliCommandDef[] = [
|
|
|
1054
1170
|
help: {
|
|
1055
1171
|
group: CLI_COMMAND_GROUPS.companyData,
|
|
1056
1172
|
usage: ["valuation <symbol>"],
|
|
1057
|
-
options: [EXCHANGE_OPTION],
|
|
1058
|
-
examples: ["valuation NVDA", "valuation BP:LSE"],
|
|
1173
|
+
options: [EXCHANGE_OPTION, TABLE_SECTION_OPTION],
|
|
1174
|
+
examples: ["valuation NVDA", "valuation BP:LSE", "valuation NVDA --csv"],
|
|
1059
1175
|
},
|
|
1060
1176
|
execute: (args, ctx) => runFinancials(args, ctx, "valuation"),
|
|
1061
1177
|
},
|
|
@@ -1167,7 +1283,7 @@ export const marketDataCliCommands: CliCommandDef[] = [
|
|
|
1167
1283
|
description: "Convert a currency into your base currency",
|
|
1168
1284
|
help: {
|
|
1169
1285
|
group: CLI_COMMAND_GROUPS.markets,
|
|
1170
|
-
usage: [
|
|
1286
|
+
usage: [FX_USAGE],
|
|
1171
1287
|
examples: ["fx EUR", "fx JPY --json"],
|
|
1172
1288
|
},
|
|
1173
1289
|
execute: runFx,
|