gloomberb 0.9.2 → 0.9.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/README.md +23 -2
- package/bin/gloomberb +1 -1
- package/package.json +1 -1
- package/src/app.tsx +10 -7
- package/src/capabilities/registry.ts +1 -0
- package/src/capabilities/types.ts +23 -0
- package/src/cli/commands/api.ts +119 -0
- package/src/cli/commands/automation.ts +173 -0
- package/src/cli/commands/command-utils.ts +53 -0
- package/src/cli/commands/market.ts +527 -0
- package/src/cli/commands/overview.ts +222 -0
- package/src/cli/commands/search.ts +39 -0
- package/src/cli/commands/system.ts +553 -0
- package/src/cli/commands/ticker.ts +104 -12
- package/src/cli/context.ts +35 -2
- package/src/cli/entry.ts +51 -0
- package/src/cli/errors.ts +56 -5
- package/src/cli/index.ts +73 -11
- package/src/cli/options.test.ts +38 -0
- package/src/cli/options.ts +95 -0
- package/src/cli/pane-functions/index.ts +10 -1
- package/src/cli/registry.test.ts +40 -0
- package/src/cli/registry.ts +46 -5
- package/src/cli/result.test.ts +82 -0
- package/src/cli/result.ts +149 -0
- package/src/cli/types.ts +11 -0
- package/src/cli.test.ts +11 -11
- package/src/components/chart/comparison-stock-chart/interactions.ts +7 -1
- package/src/components/chart/comparison-stock-chart/legend.tsx +4 -0
- package/src/components/chart/comparison-stock-chart/render-data.ts +37 -8
- package/src/components/chart/comparison-stock-chart/selection-runtime.ts +19 -2
- package/src/components/chart/comparison-stock-chart/sources.ts +8 -4
- package/src/components/chart/comparison-stock-chart/toolbar.tsx +13 -3
- package/src/components/chart/comparison-stock-chart/view.tsx +12 -2
- package/src/components/chart/core/pane-settings.test.ts +27 -0
- package/src/components/chart/core/pane-settings.ts +60 -1
- package/src/components/chart/core/pointer.ts +2 -0
- package/src/components/chart/stock-chart/resolved/runtime.ts +1 -1
- package/src/components/chart/stock-chart/settings.ts +7 -3
- package/src/components/chart/stock-chart/viewport/runtime.ts +5 -5
- package/src/components/command-bar/pane-templates/workflow-route.ts +10 -0
- package/src/components/command-bar/surface/pane.test.tsx +76 -0
- package/src/components/data-table/view.tsx +13 -2
- package/src/components/feed-data-table/stack-view.tsx +9 -1
- package/src/components/index.ts +1 -1
- package/src/components/input-search-bar.test.tsx +103 -0
- package/src/components/input-search-bar.tsx +16 -0
- package/src/components/layout/shell/active-drag.ts +5 -1
- package/src/components/layout/shell/drag/runtime.ts +21 -3
- package/src/components/layout/shell/fullscreen.ts +10 -0
- package/src/components/layout/shell/index.test.tsx +395 -2
- package/src/components/layout/shell/index.tsx +128 -2
- package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
- package/src/components/layout/shell/pane/layers.tsx +26 -14
- package/src/components/layout/shell/pane/management-shortcuts.ts +19 -0
- package/src/components/layout/shell/shortcuts.ts +4 -0
- package/src/components/layout/shell/terminal-pointer-runtime.ts +29 -19
- package/src/components/layout/shell/window-mode/index.ts +11 -3
- package/src/components/layout/shell/window-mode/overlays.tsx +8 -1
- package/src/components/layout/status-bar.test.tsx +105 -0
- package/src/components/layout/status-bar.tsx +32 -6
- package/src/components/layout/transient-layout.tsx +57 -0
- package/src/components/layout/window-edit/mode.test.ts +76 -5
- package/src/components/layout/window-edit/mode.ts +47 -2
- package/src/components/layout/window-edit/presentation.ts +2 -1
- package/src/components/price-performance.tsx +9 -1
- package/src/core/app-services.ts +7 -4
- package/src/data/config/store/index.ts +1 -2
- package/src/plugins/builtin/cloud-tweets/pane.tsx +2 -0
- package/src/plugins/builtin/cloud-tweets/search-bar.tsx +3 -0
- package/src/plugins/builtin/cloud-tweets/table.tsx +14 -2
- package/src/plugins/builtin/comparison-chart/index.test.tsx +130 -6
- package/src/plugins/builtin/news/wire/read-state.ts +9 -32
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +23 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +19 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
- package/src/plugins/builtin/shared/read-state.ts +43 -0
- package/src/plugins/builtin/substack/article-detail.test.tsx +10 -0
- package/src/plugins/builtin/substack/article-detail.tsx +39 -15
- package/src/plugins/builtin/substack/article-stack.tsx +6 -2
- package/src/plugins/builtin/substack/feed-tabs.tsx +12 -9
- package/src/plugins/builtin/substack/pane.tsx +8 -3
- package/src/plugins/builtin/substack/read-state.ts +55 -0
- package/src/plugins/builtin/thirteenf/pane.test.tsx +26 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +12 -1
- package/src/plugins/pane-manager/docking.ts +9 -2
- package/src/plugins/pane-manager/queries.ts +8 -2
- package/src/plugins/prediction-markets/controller/index.ts +2 -0
- package/src/plugins/prediction-markets/controller/keyboard.ts +34 -5
- package/src/plugins/prediction-markets/pane.test.tsx +32 -0
- package/src/plugins/prediction-markets/pane.tsx +21 -1
- package/src/plugins/prediction-markets/test-helpers.tsx +1 -0
- package/src/renderers/electrobun/bun/application-menu/click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/click.ts +1 -0
- package/src/renderers/electrobun/bun/application-menu/index.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
- package/src/renderers/electrobun/bun/index.ts +13 -0
- package/src/renderers/electrobun/bun/window/focus.test.ts +35 -0
- package/src/renderers/electrobun/bun/window/focus.ts +5 -0
- package/src/renderers/electrobun/view/app-services.ts +6 -4
- package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
- package/src/renderers/electrobun/view/host/text.test.ts +22 -0
- package/src/renderers/electrobun/view/host/text.tsx +24 -20
- package/src/renderers/electrobun/view/ui-host.tsx +1 -1
- package/src/renderers/opentui/start.tsx +13 -5
- package/src/state/app/input-capture.ts +12 -0
- package/src/types/plugin.ts +28 -0
- package/src/utils/cli-output.ts +6 -0
- package/src/utils/search-focus-navigation.ts +24 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { apiClient } from "../../api-client";
|
|
2
|
+
import type { CliCommandDef } from "../../types/plugin";
|
|
3
|
+
import { formatCompact } from "../../utils/format";
|
|
4
|
+
import { attachFearGreedPersistence, loadFearGreed, resetFearGreedPersistence } from "../../plugins/builtin/fear-greed/cache";
|
|
5
|
+
import { createPluginPersistence } from "../../plugins/plugin-persistence";
|
|
6
|
+
import {
|
|
7
|
+
fetchScreener,
|
|
8
|
+
fetchTrending,
|
|
9
|
+
MARKET_SUMMARY_SYMBOLS,
|
|
10
|
+
type ScreenerCategory,
|
|
11
|
+
} from "../../plugins/builtin/market-movers/screener";
|
|
12
|
+
import { loadCalendar, matchesCountry, matchesImpact, type CountryFilter, type ImpactFilter } from "../../plugins/builtin/econ/calendar-model";
|
|
13
|
+
import { isoDate, requireArg, takeOption } from "./command-utils";
|
|
14
|
+
|
|
15
|
+
const SECTOR_ETFS = [
|
|
16
|
+
"XLC", "XLY", "XLP", "XLE", "XLF", "XLV", "XLI", "XLK", "XLB", "XLRE", "XLU",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
function screenerCategory(value: string | undefined): ScreenerCategory | "trending" {
|
|
20
|
+
if (value === "losers") return "day_losers";
|
|
21
|
+
if (value === "active" || value === "most-active") return "most_actives";
|
|
22
|
+
if (value === "trending") return "trending";
|
|
23
|
+
return "day_gainers";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function quoteRows(results: Awaited<ReturnType<NonNullable<import("../../types/data-provider").AssetDataProvider["getQuotesBatch"]>>>) {
|
|
27
|
+
return results.map((result) => {
|
|
28
|
+
const quote = result.quote;
|
|
29
|
+
return {
|
|
30
|
+
symbol: result.target.symbol,
|
|
31
|
+
name: quote?.name ?? "",
|
|
32
|
+
price: quote?.price ?? null,
|
|
33
|
+
change: quote?.change ?? null,
|
|
34
|
+
changePercent: quote?.changePercent == null ? null : Number(quote.changePercent.toFixed(2)),
|
|
35
|
+
currency: quote?.currency ?? "",
|
|
36
|
+
providerId: quote?.providerId ?? "",
|
|
37
|
+
marketCap: quote?.marketCap ?? null,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function runMoverCommand(args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
43
|
+
const category = screenerCategory(args[0]);
|
|
44
|
+
const limit = ctx.cliOptions.limit ?? 25;
|
|
45
|
+
if (category === "trending") {
|
|
46
|
+
const services = await ctx.initServices();
|
|
47
|
+
try {
|
|
48
|
+
const trending = await fetchTrending(limit, undefined, { forceRefresh: ctx.cliOptions.refresh });
|
|
49
|
+
const results = await services.dataProvider.getQuotesBatch(
|
|
50
|
+
trending.map(({ symbol }) => ({ symbol, exchange: "" })),
|
|
51
|
+
{ forceRefresh: ctx.cliOptions.refresh },
|
|
52
|
+
);
|
|
53
|
+
ctx.printResult({ data: quoteRows(results), metadata: { category } });
|
|
54
|
+
} finally {
|
|
55
|
+
services.destroy();
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const rows = await fetchScreener(category, limit, undefined, { forceRefresh: ctx.cliOptions.refresh });
|
|
61
|
+
ctx.printResult({ data: rows }, {
|
|
62
|
+
columns: [
|
|
63
|
+
{ key: "symbol", header: "Symbol" },
|
|
64
|
+
{ key: "name", header: "Name" },
|
|
65
|
+
{ key: "price", header: "Last", align: "right" },
|
|
66
|
+
{ key: "changePercent", header: "Chg%", align: "right" },
|
|
67
|
+
{ key: "volume", header: "Volume", align: "right", value: (row) => formatCompact(Number(row.volume)) },
|
|
68
|
+
{ key: "marketCap", header: "Mkt Cap", align: "right", value: (row) => row.marketCap == null ? "" : formatCompact(Number(row.marketCap)) },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function runQuoteBasket(symbols: string[], ctx: Parameters<CliCommandDef["execute"]>[1], metadata: Record<string, unknown>) {
|
|
74
|
+
const services = await ctx.initServices();
|
|
75
|
+
try {
|
|
76
|
+
const results = await services.dataProvider.getQuotesBatch(
|
|
77
|
+
symbols.map((symbol) => ({ symbol, exchange: "" })),
|
|
78
|
+
{ forceRefresh: ctx.cliOptions.refresh },
|
|
79
|
+
);
|
|
80
|
+
ctx.printResult({ data: quoteRows(results), metadata }, {
|
|
81
|
+
columns: [
|
|
82
|
+
{ key: "symbol", header: "Symbol" },
|
|
83
|
+
{ key: "name", header: "Name" },
|
|
84
|
+
{ key: "price", header: "Last", align: "right" },
|
|
85
|
+
{ key: "changePercent", header: "Chg%", align: "right" },
|
|
86
|
+
{ key: "marketCap", header: "Mkt Cap", align: "right", value: (row) => row.marketCap == null ? "" : formatCompact(Number(row.marketCap)) },
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
} finally {
|
|
90
|
+
services.destroy();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function runFearGreed(_args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
95
|
+
const context = await ctx.initConfigData();
|
|
96
|
+
attachFearGreedPersistence(createPluginPersistence(
|
|
97
|
+
context.persistence.pluginState,
|
|
98
|
+
context.persistence.resources,
|
|
99
|
+
"plugin:fear-greed",
|
|
100
|
+
"fear-greed",
|
|
101
|
+
));
|
|
102
|
+
try {
|
|
103
|
+
const data = await loadFearGreed(ctx.cliOptions.refresh);
|
|
104
|
+
ctx.printResult({
|
|
105
|
+
data: [{
|
|
106
|
+
score: data.overall.score,
|
|
107
|
+
rating: data.overall.rating,
|
|
108
|
+
updatedAt: data.overall.updatedAt?.toISOString() ?? "",
|
|
109
|
+
previousClose: data.overall.previousClose,
|
|
110
|
+
previousWeek: data.overall.previousWeek,
|
|
111
|
+
previousMonth: data.overall.previousMonth,
|
|
112
|
+
previousYear: data.overall.previousYear,
|
|
113
|
+
}],
|
|
114
|
+
metadata: { indicators: data.indicators.map((indicator) => ({ id: indicator.definition.id, score: indicator.score, rating: indicator.rating })) },
|
|
115
|
+
});
|
|
116
|
+
} finally {
|
|
117
|
+
resetFearGreedPersistence();
|
|
118
|
+
context.persistence.close();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
123
|
+
const rawArgs = [...args];
|
|
124
|
+
const country = (takeOption(rawArgs, "--country") ?? "all") as CountryFilter;
|
|
125
|
+
const impact = (takeOption(rawArgs, "--impact") ?? "all") as ImpactFilter;
|
|
126
|
+
const services = await ctx.initServices();
|
|
127
|
+
try {
|
|
128
|
+
const events = await loadCalendar(ctx.cliOptions.refresh);
|
|
129
|
+
const rows = events
|
|
130
|
+
.filter((event) => matchesCountry(event, country) && matchesImpact(event, impact))
|
|
131
|
+
.sort((left, right) => left.date.getTime() - right.date.getTime())
|
|
132
|
+
.slice(0, ctx.cliOptions.limit ?? 50)
|
|
133
|
+
.map((event) => ({
|
|
134
|
+
date: isoDate(event.date),
|
|
135
|
+
time: event.time,
|
|
136
|
+
country: event.country,
|
|
137
|
+
impact: event.impact,
|
|
138
|
+
event: event.event,
|
|
139
|
+
actual: event.actual ?? "",
|
|
140
|
+
forecast: event.forecast ?? "",
|
|
141
|
+
prior: event.prior ?? "",
|
|
142
|
+
}));
|
|
143
|
+
ctx.printResult({ data: rows, metadata: { country, impact } }, {
|
|
144
|
+
columns: [
|
|
145
|
+
{ key: "date", header: "Date" },
|
|
146
|
+
{ key: "time", header: "Time" },
|
|
147
|
+
{ key: "country", header: "Country" },
|
|
148
|
+
{ key: "impact", header: "Impact" },
|
|
149
|
+
{ key: "event", header: "Event" },
|
|
150
|
+
{ key: "actual", header: "Actual" },
|
|
151
|
+
{ key: "forecast", header: "Forecast" },
|
|
152
|
+
{ key: "prior", header: "Prior" },
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
} finally {
|
|
156
|
+
services.destroy();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function runFred(args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
161
|
+
const seriesId = requireArg(args[0]?.toUpperCase(), "Usage: gloomberb fred <series-id>", ctx);
|
|
162
|
+
const startDate = takeOption(args, "--start") ?? "2021-01-01";
|
|
163
|
+
const sortOrder = (takeOption(args, "--sort") ?? "desc") as "asc" | "desc";
|
|
164
|
+
const data = await apiClient.getCloudFredSeries(seriesId, { startDate, sortOrder });
|
|
165
|
+
const rows = data.observations.slice(0, ctx.cliOptions.limit ?? data.observations.length);
|
|
166
|
+
ctx.printResult({ data: rows, metadata: { info: data.info, seriesId, startDate, sortOrder } });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function runYieldCurve(args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
170
|
+
const startDate = takeOption(args, "--start") ?? "2021-01-01";
|
|
171
|
+
const series = ["DGS3MO", "DGS2", "DGS10", "DGS30"];
|
|
172
|
+
const results = await Promise.all(series.map(async (seriesId) => {
|
|
173
|
+
const data = await apiClient.getCloudFredSeries(seriesId, { startDate, sortOrder: "desc" });
|
|
174
|
+
const latest = data.observations[0];
|
|
175
|
+
return {
|
|
176
|
+
seriesId,
|
|
177
|
+
date: latest?.date ?? "",
|
|
178
|
+
value: latest?.value ?? null,
|
|
179
|
+
title: data.info?.title ?? "",
|
|
180
|
+
};
|
|
181
|
+
}));
|
|
182
|
+
ctx.printResult({ data: results, metadata: { startDate } });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function runCorrelation(args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
186
|
+
const left = requireArg(args[0]?.toUpperCase(), "Usage: gloomberb correlation <symbol-a> <symbol-b>", ctx);
|
|
187
|
+
const right = requireArg(args[1]?.toUpperCase(), "Usage: gloomberb correlation <symbol-a> <symbol-b>", ctx);
|
|
188
|
+
const services = await ctx.initServices();
|
|
189
|
+
try {
|
|
190
|
+
const [leftHistory, rightHistory] = await Promise.all([
|
|
191
|
+
services.dataProvider.getPriceHistory(left, "", "1Y"),
|
|
192
|
+
services.dataProvider.getPriceHistory(right, "", "1Y"),
|
|
193
|
+
]);
|
|
194
|
+
const rightByDate = new Map(rightHistory.map((point) => [isoDate(point.date).slice(0, 10), point.close]));
|
|
195
|
+
const pairs = leftHistory
|
|
196
|
+
.map((point) => [point.close, rightByDate.get(isoDate(point.date).slice(0, 10))] as const)
|
|
197
|
+
.filter((pair): pair is readonly [number, number] => pair[1] != null);
|
|
198
|
+
const leftMean = pairs.reduce((sum, pair) => sum + pair[0], 0) / Math.max(1, pairs.length);
|
|
199
|
+
const rightMean = pairs.reduce((sum, pair) => sum + pair[1], 0) / Math.max(1, pairs.length);
|
|
200
|
+
const numerator = pairs.reduce((sum, pair) => sum + ((pair[0] - leftMean) * (pair[1] - rightMean)), 0);
|
|
201
|
+
const leftVariance = pairs.reduce((sum, pair) => sum + ((pair[0] - leftMean) ** 2), 0);
|
|
202
|
+
const rightVariance = pairs.reduce((sum, pair) => sum + ((pair[1] - rightMean) ** 2), 0);
|
|
203
|
+
const correlation = leftVariance > 0 && rightVariance > 0
|
|
204
|
+
? numerator / Math.sqrt(leftVariance * rightVariance)
|
|
205
|
+
: null;
|
|
206
|
+
ctx.printResult({ data: [{ left, right, samples: pairs.length, correlation }] });
|
|
207
|
+
} finally {
|
|
208
|
+
services.destroy();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export const overviewCliCommands: CliCommandDef[] = [
|
|
213
|
+
{ name: "movers", description: "Fetch gainers, losers, active, or trending market movers", help: { usage: ["movers [gainers|losers|active|trending]"] }, execute: runMoverCommand },
|
|
214
|
+
{ name: "indices", description: "Fetch major US index quotes", execute: (args, ctx) => runQuoteBasket([...MARKET_SUMMARY_SYMBOLS], ctx, { group: "indices" }) },
|
|
215
|
+
{ name: "sectors", description: "Fetch SPDR sector ETF quotes", execute: (args, ctx) => runQuoteBasket(SECTOR_ETFS, ctx, { group: "sectors" }) },
|
|
216
|
+
{ name: "fear-greed", description: "Fetch CNN Fear & Greed gauge data", execute: runFearGreed },
|
|
217
|
+
{ name: "econ", description: "Fetch economic calendar events", help: { usage: ["econ [--country US|G7|EU|all] [--impact high|medium|low|all]"] }, execute: runEcon },
|
|
218
|
+
{ name: "fred", description: "Fetch a FRED series through the configured cloud session", help: { usage: ["fred <series-id> [--start yyyy-mm-dd]"] }, execute: runFred },
|
|
219
|
+
{ name: "yield-curve", description: "Fetch standard Treasury yield FRED series", help: { usage: ["yield-curve [--start yyyy-mm-dd]"] }, execute: runYieldCurve },
|
|
220
|
+
{ name: "correlation", description: "Compute 1Y close-price correlation for two symbols", help: { usage: ["correlation <symbol-a> <symbol-b>"] }, execute: runCorrelation },
|
|
221
|
+
{ name: "relationship", description: "Alias for correlation", help: { usage: ["relationship <symbol-a> <symbol-b>"] }, execute: runCorrelation },
|
|
222
|
+
];
|
|
@@ -12,10 +12,12 @@ import {
|
|
|
12
12
|
import { initMarketData } from "../context";
|
|
13
13
|
import { fail } from "../errors";
|
|
14
14
|
import type { MarketContext } from "../types";
|
|
15
|
+
import type { CliCommandContext } from "../../types/plugin";
|
|
15
16
|
|
|
16
17
|
interface SearchCommandDependencies {
|
|
17
18
|
initMarketData?: () => Promise<MarketContext>;
|
|
18
19
|
fail?: (message: string, details?: string) => never;
|
|
20
|
+
printResult?: CliCommandContext["printResult"];
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export async function searchCandidatesForCli({
|
|
@@ -54,6 +56,28 @@ function resolveSearchName(candidate: TickerSearchCandidate): string {
|
|
|
54
56
|
return candidate.detail.split(" | ")[0] || candidate.detail || "—";
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
function searchCandidateRows(candidates: TickerSearchCandidate[]) {
|
|
60
|
+
return candidates.map((candidate) => ({
|
|
61
|
+
category: candidate.category,
|
|
62
|
+
symbol: candidate.label,
|
|
63
|
+
name: resolveSearchName(candidate),
|
|
64
|
+
exchange: candidate.result?.exchange
|
|
65
|
+
|| candidate.result?.primaryExchange
|
|
66
|
+
|| candidate.ticker?.metadata.exchange
|
|
67
|
+
|| candidate.right
|
|
68
|
+
|| "",
|
|
69
|
+
type: candidate.result?.type
|
|
70
|
+
|| candidate.result?.brokerContract?.secType
|
|
71
|
+
|| candidate.ticker?.metadata.assetCategory
|
|
72
|
+
|| "",
|
|
73
|
+
source: candidate.kind === "ticker"
|
|
74
|
+
? "Saved"
|
|
75
|
+
: candidate.result?.brokerLabel || candidate.result?.providerId || "Provider",
|
|
76
|
+
providerId: candidate.result?.providerId ?? "",
|
|
77
|
+
saved: candidate.kind === "ticker",
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
|
|
57
81
|
export function buildSearchReport({
|
|
58
82
|
query,
|
|
59
83
|
candidates,
|
|
@@ -119,6 +143,21 @@ export async function search(query: string, dependencies: SearchCommandDependenc
|
|
|
119
143
|
dataProvider,
|
|
120
144
|
});
|
|
121
145
|
|
|
146
|
+
if (dependencies.printResult) {
|
|
147
|
+
dependencies.printResult({ data: searchCandidateRows(candidates), metadata: { query: trimmedQuery } }, {
|
|
148
|
+
columns: [
|
|
149
|
+
{ key: "category", header: "Category" },
|
|
150
|
+
{ key: "symbol", header: "Symbol" },
|
|
151
|
+
{ key: "name", header: "Name" },
|
|
152
|
+
{ key: "exchange", header: "Exchange" },
|
|
153
|
+
{ key: "type", header: "Type" },
|
|
154
|
+
{ key: "source", header: "Source" },
|
|
155
|
+
],
|
|
156
|
+
});
|
|
157
|
+
persistence.close();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
122
161
|
console.log(buildSearchReport({
|
|
123
162
|
query: trimmedQuery,
|
|
124
163
|
candidates,
|