@quantbrasil/cli 0.1.0-beta.2 → 0.1.0-beta.4
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 +14 -11
- package/dist/cli/index.d.ts +2 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -10
- package/dist/commands/portfolios.d.ts +133 -8
- package/dist/commands/portfolios.d.ts.map +1 -1
- package/dist/commands/portfolios.js +434 -55
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/vendor/core/capabilities/index.d.ts +0 -1
- package/dist/vendor/core/capabilities/index.d.ts.map +1 -1
- package/dist/vendor/core/capabilities/index.js +0 -1
- package/dist/vendor/core/capabilities/portfolios.d.ts +401 -56
- package/dist/vendor/core/capabilities/portfolios.d.ts.map +1 -1
- package/dist/vendor/core/capabilities/portfolios.js +383 -115
- package/dist/vendor/core/capabilities/registry.d.ts +586 -266
- package/dist/vendor/core/capabilities/registry.d.ts.map +1 -1
- package/dist/vendor/core/capabilities/registry.js +0 -2
- package/dist/vendor/core/capabilities/types.d.ts +1 -1
- package/dist/vendor/core/capabilities/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/quantbrasil/SKILL.md +13 -5
- package/skills/quantbrasil/references/cli.md +22 -25
- package/skills/quantbrasil/references/costs.md +4 -4
- package/skills/quantbrasil/references/errors.md +16 -5
- package/skills/quantbrasil/references/portfolios.md +92 -0
- package/skills/quantbrasil/references/unsupported.md +2 -2
- package/skills/quantbrasil/references/workflows.md +41 -22
- package/dist/commands/analytics.d.ts +0 -131
- package/dist/commands/analytics.d.ts.map +0 -1
- package/dist/commands/analytics.js +0 -291
- package/dist/vendor/core/capabilities/analytics.d.ts +0 -187
- package/dist/vendor/core/capabilities/analytics.d.ts.map +0 -1
- package/dist/vendor/core/capabilities/analytics.js +0 -214
|
@@ -2,65 +2,146 @@ import { invokeCliCapability } from "../cli/client.js";
|
|
|
2
2
|
import { createCliMutationFailedError, createCliValidationError, } from "../cli/errors.js";
|
|
3
3
|
import { createTerminalTheme } from "../cli/terminal.js";
|
|
4
4
|
export function registerPortfoliosCommands(program, context = {}) {
|
|
5
|
-
const
|
|
6
|
-
.command("
|
|
7
|
-
.description("Manage
|
|
8
|
-
|
|
5
|
+
const watchlistsCommand = program
|
|
6
|
+
.command("watchlists")
|
|
7
|
+
.description("Manage watchlists for tracking ideas and filters");
|
|
8
|
+
watchlistsCommand
|
|
9
9
|
.command("list")
|
|
10
|
-
.description("List
|
|
10
|
+
.description("List watchlists for authenticated user")
|
|
11
11
|
.option("--json", "Show JSON output")
|
|
12
12
|
.action(async (options) => {
|
|
13
|
-
await
|
|
13
|
+
await runWatchlistListCommand(options, context);
|
|
14
14
|
});
|
|
15
|
-
|
|
15
|
+
watchlistsCommand
|
|
16
16
|
.command("get")
|
|
17
|
-
.description("Get one
|
|
18
|
-
.argument("<
|
|
17
|
+
.description("Get one watchlist by id")
|
|
18
|
+
.argument("<watchlist-id>", "Watchlist id", parsePortfolioIdArgument)
|
|
19
19
|
.option("--json", "Show JSON output")
|
|
20
20
|
.action(async (portfolioId, options) => {
|
|
21
|
-
await
|
|
21
|
+
await runWatchlistGetCommand(portfolioId, options, context);
|
|
22
22
|
});
|
|
23
|
-
|
|
23
|
+
watchlistsCommand
|
|
24
24
|
.command("create")
|
|
25
|
-
.description("Create a new
|
|
26
|
-
.argument("<name>", "
|
|
25
|
+
.description("Create a new watchlist")
|
|
26
|
+
.argument("<name>", "Watchlist name")
|
|
27
27
|
.option("--json", "Show JSON output")
|
|
28
28
|
.action(async (name, options) => {
|
|
29
|
-
await
|
|
29
|
+
await runWatchlistCreateCommand(name, options, context);
|
|
30
30
|
});
|
|
31
|
-
|
|
31
|
+
watchlistsCommand
|
|
32
32
|
.command("rename")
|
|
33
|
-
.description("Rename an existing
|
|
34
|
-
.argument("<
|
|
35
|
-
.argument("<name>", "New
|
|
33
|
+
.description("Rename an existing watchlist")
|
|
34
|
+
.argument("<watchlist-id>", "Watchlist id", parsePortfolioIdArgument)
|
|
35
|
+
.argument("<name>", "New watchlist name")
|
|
36
36
|
.option("--json", "Show JSON output")
|
|
37
37
|
.action(async (portfolioId, name, options) => {
|
|
38
|
-
await
|
|
38
|
+
await runWatchlistRenameCommand(portfolioId, name, options, context);
|
|
39
39
|
});
|
|
40
|
-
|
|
40
|
+
watchlistsCommand
|
|
41
41
|
.command("add-assets")
|
|
42
|
-
.description("Add one or more monitored tickers to a
|
|
43
|
-
.argument("<
|
|
42
|
+
.description("Add one or more monitored tickers to a watchlist")
|
|
43
|
+
.argument("<watchlist-id>", "Watchlist id", parsePortfolioIdArgument)
|
|
44
44
|
.argument("<ticker...>", "One or more tickers to add")
|
|
45
45
|
.option("--json", "Show JSON output")
|
|
46
46
|
.action(async (portfolioId, symbols, options) => {
|
|
47
|
-
await
|
|
47
|
+
await runWatchlistAddAssetsCommand(portfolioId, symbols, options, context);
|
|
48
48
|
});
|
|
49
|
-
|
|
49
|
+
watchlistsCommand
|
|
50
50
|
.command("remove-assets")
|
|
51
|
-
.description("Remove one or more monitored tickers from a
|
|
52
|
-
.argument("<
|
|
51
|
+
.description("Remove one or more monitored tickers from a watchlist")
|
|
52
|
+
.argument("<watchlist-id>", "Watchlist id", parsePortfolioIdArgument)
|
|
53
53
|
.argument("<ticker...>", "One or more tickers to remove")
|
|
54
54
|
.option("--json", "Show JSON output")
|
|
55
55
|
.action(async (portfolioId, symbols, options) => {
|
|
56
|
-
await
|
|
56
|
+
await runWatchlistRemoveAssetsCommand(portfolioId, symbols, options, context);
|
|
57
|
+
});
|
|
58
|
+
const holdingsCommand = program
|
|
59
|
+
.command("holdings")
|
|
60
|
+
.description("Manage holdings for investable metrics");
|
|
61
|
+
holdingsCommand
|
|
62
|
+
.command("list")
|
|
63
|
+
.description("List holdings for authenticated user")
|
|
64
|
+
.option("--json", "Show JSON output")
|
|
65
|
+
.action(async (options) => {
|
|
66
|
+
await runHoldingListCommand(options, context);
|
|
67
|
+
});
|
|
68
|
+
holdingsCommand
|
|
69
|
+
.command("get")
|
|
70
|
+
.description("Get one holding by id")
|
|
71
|
+
.argument("<holding-id>", "Holding id", parsePortfolioIdArgument)
|
|
72
|
+
.option("--json", "Show JSON output")
|
|
73
|
+
.action(async (portfolioId, options) => {
|
|
74
|
+
await runHoldingGetCommand(portfolioId, options, context);
|
|
75
|
+
});
|
|
76
|
+
holdingsCommand
|
|
77
|
+
.command("create")
|
|
78
|
+
.description("Create a new holding")
|
|
79
|
+
.argument("<name>", "Holding name")
|
|
80
|
+
.option("--mode <mode>", "Initial mode: target or position", "target")
|
|
81
|
+
.option("--target <target>", "Initial target weight in TICKER:WEIGHT_PCT form", collectStringOption, [])
|
|
82
|
+
.option("--json", "Show JSON output")
|
|
83
|
+
.action(async (name, options) => {
|
|
84
|
+
await runHoldingCreateCommand(name, options, context);
|
|
85
|
+
});
|
|
86
|
+
holdingsCommand
|
|
87
|
+
.command("rename")
|
|
88
|
+
.description("Rename an existing holding")
|
|
89
|
+
.argument("<holding-id>", "Holding id", parsePortfolioIdArgument)
|
|
90
|
+
.argument("<name>", "New holding name")
|
|
91
|
+
.option("--json", "Show JSON output")
|
|
92
|
+
.action(async (portfolioId, name, options) => {
|
|
93
|
+
await runHoldingRenameCommand(portfolioId, name, options, context);
|
|
94
|
+
});
|
|
95
|
+
holdingsCommand
|
|
96
|
+
.command("set-targets")
|
|
97
|
+
.description("Set target weights for a holding")
|
|
98
|
+
.argument("<holding-id>", "Holding id", parsePortfolioIdArgument)
|
|
99
|
+
.argument("<target...>", "One or more target weights in TICKER:WEIGHT_PCT form")
|
|
100
|
+
.option("--json", "Show JSON output")
|
|
101
|
+
.action(async (portfolioId, targets, options) => {
|
|
102
|
+
await runHoldingSetTargetsCommand(portfolioId, targets, options, context);
|
|
103
|
+
});
|
|
104
|
+
holdingsCommand
|
|
105
|
+
.command("historical-return")
|
|
106
|
+
.description("Calculate historical return for a saved holding")
|
|
107
|
+
.argument("<holding-id>", "Holding id", parsePortfolioIdArgument)
|
|
108
|
+
.requiredOption("--from <date>", "Inclusive start date in ISO format")
|
|
109
|
+
.requiredOption("--to <date>", "Inclusive end date in ISO format")
|
|
110
|
+
.option("--json", "Show JSON output")
|
|
111
|
+
.action(async (portfolioId, options) => {
|
|
112
|
+
await runHoldingHistoricalReturnCommand(portfolioId, options, context);
|
|
113
|
+
});
|
|
114
|
+
holdingsCommand
|
|
115
|
+
.command("beta")
|
|
116
|
+
.description("Calculate holding beta against IBOV")
|
|
117
|
+
.argument("<holding-id>", "Holding id", parsePortfolioIdArgument)
|
|
118
|
+
.option("--years <years>", "Lookback window in years", "1")
|
|
119
|
+
.option("--json", "Show JSON output")
|
|
120
|
+
.action(async (portfolioId, options) => {
|
|
121
|
+
await runHoldingBetaCommand(portfolioId, options, context);
|
|
122
|
+
});
|
|
123
|
+
holdingsCommand
|
|
124
|
+
.command("var")
|
|
125
|
+
.description("Calculate one-day Value-at-Risk for a holding")
|
|
126
|
+
.argument("<holding-id>", "Holding id", parsePortfolioIdArgument)
|
|
127
|
+
.option("--years <years>", "Lookback window in years", "1")
|
|
128
|
+
.option("--confidence <confidence>", "Confidence level in percent", "95")
|
|
129
|
+
.option("--json", "Show JSON output")
|
|
130
|
+
.action(async (portfolioId, options) => {
|
|
131
|
+
await runHoldingVarCommand(portfolioId, options, context);
|
|
57
132
|
});
|
|
58
133
|
}
|
|
59
|
-
export async function
|
|
134
|
+
export async function runWatchlistListCommand(options, context = {}) {
|
|
135
|
+
await runPortfolioListLikeCommand("watchlists.list", "Watchlists", options, context);
|
|
136
|
+
}
|
|
137
|
+
export async function runHoldingListCommand(options, context = {}) {
|
|
138
|
+
await runPortfolioListLikeCommand("holdings.list", "Holdings", options, context);
|
|
139
|
+
}
|
|
140
|
+
async function runPortfolioListLikeCommand(capability, title, options, context) {
|
|
60
141
|
const stdout = context.io?.stdout ?? process.stdout;
|
|
61
142
|
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
62
143
|
const response = await invokeCliCapability({
|
|
63
|
-
capability
|
|
144
|
+
capability,
|
|
64
145
|
env: context.env,
|
|
65
146
|
fetch: context.fetch,
|
|
66
147
|
});
|
|
@@ -68,13 +149,19 @@ export async function runPortfolioListCommand(options, context = {}) {
|
|
|
68
149
|
stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
|
|
69
150
|
return;
|
|
70
151
|
}
|
|
71
|
-
stdout.write(`${formatPortfolioListHuman(response.data, theme)}\n`);
|
|
152
|
+
stdout.write(`${formatPortfolioListHuman(response.data, theme, title)}\n`);
|
|
153
|
+
}
|
|
154
|
+
export async function runWatchlistGetCommand(portfolioId, options, context = {}) {
|
|
155
|
+
await runPortfolioGetLikeCommand("watchlists.get", "Watchlist", portfolioId, options, context);
|
|
72
156
|
}
|
|
73
|
-
export async function
|
|
157
|
+
export async function runHoldingGetCommand(portfolioId, options, context = {}) {
|
|
158
|
+
await runPortfolioGetLikeCommand("holdings.get", "Holding", portfolioId, options, context);
|
|
159
|
+
}
|
|
160
|
+
async function runPortfolioGetLikeCommand(capability, title, portfolioId, options, context) {
|
|
74
161
|
const stdout = context.io?.stdout ?? process.stdout;
|
|
75
162
|
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
76
163
|
const response = await invokeCliCapability({
|
|
77
|
-
capability
|
|
164
|
+
capability,
|
|
78
165
|
input: {
|
|
79
166
|
portfolio_id: portfolioId,
|
|
80
167
|
},
|
|
@@ -85,14 +172,62 @@ export async function runPortfolioGetCommand(portfolioId, options, context = {})
|
|
|
85
172
|
stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
|
|
86
173
|
return;
|
|
87
174
|
}
|
|
88
|
-
stdout.write(`${formatPortfolioGetHuman(response.data, theme)}\n`);
|
|
175
|
+
stdout.write(`${formatPortfolioGetHuman(response.data, theme, title)}\n`);
|
|
176
|
+
}
|
|
177
|
+
export async function runWatchlistCreateCommand(name, options, context = {}) {
|
|
178
|
+
const stdout = context.io?.stdout ?? process.stdout;
|
|
179
|
+
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
180
|
+
const response = await invokeCliCapability({
|
|
181
|
+
capability: "watchlists.create",
|
|
182
|
+
input: {
|
|
183
|
+
name: parsePortfolioNameArgument(name),
|
|
184
|
+
},
|
|
185
|
+
env: context.env,
|
|
186
|
+
fetch: context.fetch,
|
|
187
|
+
});
|
|
188
|
+
writeMutationResponse(response.data, options.json, stdout, theme);
|
|
189
|
+
}
|
|
190
|
+
export async function runHoldingCreateCommand(name, options, context = {}) {
|
|
191
|
+
const stdout = context.io?.stdout ?? process.stdout;
|
|
192
|
+
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
193
|
+
const defaultAllocationInput = parseHoldingModeOption(options.mode ?? "target");
|
|
194
|
+
const targets = parseTargetArguments(options.target ?? []);
|
|
195
|
+
if (targets.length > 0 && defaultAllocationInput === "POSITION") {
|
|
196
|
+
throw createCliValidationError("Initial targets require target mode.");
|
|
197
|
+
}
|
|
198
|
+
const response = await invokeCliCapability({
|
|
199
|
+
capability: "holdings.create",
|
|
200
|
+
input: {
|
|
201
|
+
name: parsePortfolioNameArgument(name),
|
|
202
|
+
default_allocation_input: defaultAllocationInput,
|
|
203
|
+
...(targets.length > 0 ? { targets } : {}),
|
|
204
|
+
},
|
|
205
|
+
env: context.env,
|
|
206
|
+
fetch: context.fetch,
|
|
207
|
+
});
|
|
208
|
+
writeMutationResponse(response.data, options.json, stdout, theme);
|
|
209
|
+
}
|
|
210
|
+
export async function runHoldingSetTargetsCommand(portfolioId, targets, options, context = {}) {
|
|
211
|
+
const stdout = context.io?.stdout ?? process.stdout;
|
|
212
|
+
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
213
|
+
const response = await invokeCliCapability({
|
|
214
|
+
capability: "holdings.set-targets",
|
|
215
|
+
input: {
|
|
216
|
+
portfolio_id: portfolioId,
|
|
217
|
+
targets: parseTargetArguments(targets),
|
|
218
|
+
},
|
|
219
|
+
env: context.env,
|
|
220
|
+
fetch: context.fetch,
|
|
221
|
+
});
|
|
222
|
+
writeMutationResponse(response.data, options.json, stdout, theme);
|
|
89
223
|
}
|
|
90
|
-
export async function
|
|
224
|
+
export async function runWatchlistRenameCommand(portfolioId, name, options, context = {}) {
|
|
91
225
|
const stdout = context.io?.stdout ?? process.stdout;
|
|
92
226
|
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
93
227
|
const response = await invokeCliCapability({
|
|
94
|
-
capability: "
|
|
228
|
+
capability: "watchlists.rename",
|
|
95
229
|
input: {
|
|
230
|
+
portfolio_id: portfolioId,
|
|
96
231
|
name: parsePortfolioNameArgument(name),
|
|
97
232
|
},
|
|
98
233
|
env: context.env,
|
|
@@ -100,11 +235,11 @@ export async function runPortfolioCreateCommand(name, options, context = {}) {
|
|
|
100
235
|
});
|
|
101
236
|
writeMutationResponse(response.data, options.json, stdout, theme);
|
|
102
237
|
}
|
|
103
|
-
export async function
|
|
238
|
+
export async function runHoldingRenameCommand(portfolioId, name, options, context = {}) {
|
|
104
239
|
const stdout = context.io?.stdout ?? process.stdout;
|
|
105
240
|
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
106
241
|
const response = await invokeCliCapability({
|
|
107
|
-
capability: "
|
|
242
|
+
capability: "holdings.rename",
|
|
108
243
|
input: {
|
|
109
244
|
portfolio_id: portfolioId,
|
|
110
245
|
name: parsePortfolioNameArgument(name),
|
|
@@ -114,11 +249,11 @@ export async function runPortfolioRenameCommand(portfolioId, name, options, cont
|
|
|
114
249
|
});
|
|
115
250
|
writeMutationResponse(response.data, options.json, stdout, theme);
|
|
116
251
|
}
|
|
117
|
-
export async function
|
|
252
|
+
export async function runWatchlistAddAssetsCommand(portfolioId, symbols, options, context = {}) {
|
|
118
253
|
const stdout = context.io?.stdout ?? process.stdout;
|
|
119
254
|
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
120
255
|
const response = await invokeCliCapability({
|
|
121
|
-
capability: "
|
|
256
|
+
capability: "watchlists.add-assets",
|
|
122
257
|
input: {
|
|
123
258
|
portfolio_id: portfolioId,
|
|
124
259
|
symbols: parseSymbolArguments(symbols),
|
|
@@ -128,11 +263,11 @@ export async function runPortfolioAddAssetsCommand(portfolioId, symbols, options
|
|
|
128
263
|
});
|
|
129
264
|
writeMutationResponse(response.data, options.json, stdout, theme);
|
|
130
265
|
}
|
|
131
|
-
export async function
|
|
266
|
+
export async function runWatchlistRemoveAssetsCommand(portfolioId, symbols, options, context = {}) {
|
|
132
267
|
const stdout = context.io?.stdout ?? process.stdout;
|
|
133
268
|
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
134
269
|
const response = await invokeCliCapability({
|
|
135
|
-
capability: "
|
|
270
|
+
capability: "watchlists.remove-assets",
|
|
136
271
|
input: {
|
|
137
272
|
portfolio_id: portfolioId,
|
|
138
273
|
symbols: parseSymbolArguments(symbols),
|
|
@@ -142,15 +277,71 @@ export async function runPortfolioRemoveAssetsCommand(portfolioId, symbols, opti
|
|
|
142
277
|
});
|
|
143
278
|
writeMutationResponse(response.data, options.json, stdout, theme);
|
|
144
279
|
}
|
|
145
|
-
export function
|
|
280
|
+
export async function runHoldingHistoricalReturnCommand(portfolioId, options, context = {}) {
|
|
281
|
+
const stdout = context.io?.stdout ?? process.stdout;
|
|
282
|
+
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
283
|
+
const response = await invokeCliCapability({
|
|
284
|
+
capability: "holdings.historical-return",
|
|
285
|
+
input: {
|
|
286
|
+
portfolio_id: portfolioId,
|
|
287
|
+
start_date: options.from,
|
|
288
|
+
end_date: options.to,
|
|
289
|
+
},
|
|
290
|
+
env: context.env,
|
|
291
|
+
fetch: context.fetch,
|
|
292
|
+
});
|
|
293
|
+
if (options.json) {
|
|
294
|
+
stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
stdout.write(`${formatHistoricalReturnHuman(response.data, theme)}\n`);
|
|
298
|
+
}
|
|
299
|
+
export async function runHoldingBetaCommand(portfolioId, options, context = {}) {
|
|
300
|
+
const stdout = context.io?.stdout ?? process.stdout;
|
|
301
|
+
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
302
|
+
const response = await invokeCliCapability({
|
|
303
|
+
capability: "holdings.beta",
|
|
304
|
+
input: {
|
|
305
|
+
portfolio_id: portfolioId,
|
|
306
|
+
years: parseYearsOption(options.years ?? "1"),
|
|
307
|
+
},
|
|
308
|
+
env: context.env,
|
|
309
|
+
fetch: context.fetch,
|
|
310
|
+
});
|
|
311
|
+
if (options.json) {
|
|
312
|
+
stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
stdout.write(`${formatBetaHuman(response.data, theme)}\n`);
|
|
316
|
+
}
|
|
317
|
+
export async function runHoldingVarCommand(portfolioId, options, context = {}) {
|
|
318
|
+
const stdout = context.io?.stdout ?? process.stdout;
|
|
319
|
+
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
320
|
+
const response = await invokeCliCapability({
|
|
321
|
+
capability: "holdings.var",
|
|
322
|
+
input: {
|
|
323
|
+
portfolio_id: portfolioId,
|
|
324
|
+
years: parsePositiveIntegerOption(options.years ?? "1"),
|
|
325
|
+
confidence_pct: parseNumberOption(options.confidence ?? "95", "confidence"),
|
|
326
|
+
},
|
|
327
|
+
env: context.env,
|
|
328
|
+
fetch: context.fetch,
|
|
329
|
+
});
|
|
330
|
+
if (options.json) {
|
|
331
|
+
stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
stdout.write(`${formatVarHuman(response.data, theme)}\n`);
|
|
335
|
+
}
|
|
336
|
+
export function formatPortfolioListHuman(data, theme = createTerminalTheme(process.stdout), title = "Saved items") {
|
|
146
337
|
const lines = [
|
|
147
|
-
theme.label(
|
|
338
|
+
theme.label(title),
|
|
148
339
|
"",
|
|
149
340
|
`${theme.label("Total:")} ${formatInteger(data.total)}`,
|
|
150
341
|
];
|
|
151
342
|
if (data.portfolios.length === 0) {
|
|
152
343
|
lines.push("");
|
|
153
|
-
lines.push(
|
|
344
|
+
lines.push(`No ${title.toLowerCase()} saved.`);
|
|
154
345
|
return lines.join("\n");
|
|
155
346
|
}
|
|
156
347
|
for (const portfolio of data.portfolios) {
|
|
@@ -163,17 +354,17 @@ export function formatPortfolioListHuman(data, theme = createTerminalTheme(proce
|
|
|
163
354
|
}
|
|
164
355
|
return lines.join("\n");
|
|
165
356
|
}
|
|
166
|
-
export function formatPortfolioGetHuman(data, theme = createTerminalTheme(process.stdout)) {
|
|
357
|
+
export function formatPortfolioGetHuman(data, theme = createTerminalTheme(process.stdout), title = "Saved item") {
|
|
167
358
|
const { portfolio } = data;
|
|
168
359
|
const lines = [
|
|
169
|
-
theme.label(
|
|
360
|
+
theme.label(title),
|
|
170
361
|
"",
|
|
171
362
|
`${theme.bold(String(portfolio.id))} ${theme.dim("·")} ${portfolio.name}`,
|
|
172
363
|
`${theme.label("Assets:")} ${formatInteger(portfolio.symbols.length)}`,
|
|
173
364
|
];
|
|
174
365
|
if (portfolio.symbols.length === 0) {
|
|
175
366
|
lines.push("");
|
|
176
|
-
lines.push("No assets
|
|
367
|
+
lines.push("No assets saved.");
|
|
177
368
|
return lines.join("\n");
|
|
178
369
|
}
|
|
179
370
|
lines.push("");
|
|
@@ -193,21 +384,24 @@ export function formatPortfolioGetHuman(data, theme = createTerminalTheme(proces
|
|
|
193
384
|
return lines.join("\n");
|
|
194
385
|
}
|
|
195
386
|
export function formatPortfolioMutationHuman(data, theme = createTerminalTheme(process.stdout)) {
|
|
387
|
+
const label = formatPortfolioKind(data.portfolio);
|
|
196
388
|
const lines = [
|
|
197
389
|
data.ok
|
|
198
|
-
? theme.success(theme.bold(
|
|
199
|
-
: theme.danger(theme.bold("
|
|
390
|
+
? theme.success(theme.bold(label))
|
|
391
|
+
: theme.danger(theme.bold("Mutation failed")),
|
|
200
392
|
"",
|
|
201
393
|
data.summary_markdown,
|
|
202
394
|
"",
|
|
203
|
-
`${theme.bold(String(data.portfolio.id))} ${theme.dim("·")} ${data.portfolio.name ||
|
|
395
|
+
`${theme.bold(String(data.portfolio.id))} ${theme.dim("·")} ${data.portfolio.name || label}`,
|
|
204
396
|
`${theme.label("Assets:")} ${formatInteger(data.portfolio.symbols.length)}`,
|
|
205
397
|
];
|
|
206
398
|
if (data.portfolio.symbols.length > 0) {
|
|
207
399
|
lines.push("");
|
|
208
400
|
lines.push(theme.label("Holdings"));
|
|
209
401
|
for (const symbol of data.portfolio.symbols) {
|
|
210
|
-
|
|
402
|
+
const weight = data.portfolio.weights?.[symbol];
|
|
403
|
+
const suffix = weight === undefined ? "" : `: ${formatNumber(weight)}%`;
|
|
404
|
+
lines.push(` - ${symbol}${suffix}`);
|
|
211
405
|
}
|
|
212
406
|
}
|
|
213
407
|
return lines.join("\n");
|
|
@@ -215,24 +409,69 @@ export function formatPortfolioMutationHuman(data, theme = createTerminalTheme(p
|
|
|
215
409
|
function parsePortfolioIdArgument(value) {
|
|
216
410
|
const normalized = value.trim();
|
|
217
411
|
if (!/^\d+$/.test(normalized)) {
|
|
218
|
-
throw createCliValidationError("
|
|
412
|
+
throw createCliValidationError("Id must be a positive integer.");
|
|
219
413
|
}
|
|
220
414
|
const portfolioId = Number(normalized);
|
|
221
415
|
if (!Number.isSafeInteger(portfolioId) || portfolioId <= 0) {
|
|
222
|
-
throw createCliValidationError("
|
|
416
|
+
throw createCliValidationError("Id must be a positive integer.");
|
|
223
417
|
}
|
|
224
418
|
return portfolioId;
|
|
225
419
|
}
|
|
226
420
|
function parsePortfolioNameArgument(value) {
|
|
227
421
|
const normalized = value.trim();
|
|
228
422
|
if (!normalized) {
|
|
229
|
-
throw createCliValidationError("
|
|
423
|
+
throw createCliValidationError("Name cannot be empty.");
|
|
230
424
|
}
|
|
231
425
|
if (normalized.length > 100) {
|
|
232
|
-
throw createCliValidationError("
|
|
426
|
+
throw createCliValidationError("Name cannot exceed 100 characters.");
|
|
233
427
|
}
|
|
234
428
|
return normalized;
|
|
235
429
|
}
|
|
430
|
+
function parseHoldingModeOption(value) {
|
|
431
|
+
const normalized = value.trim().toLowerCase();
|
|
432
|
+
if (normalized === "target" || normalized === "target-weight") {
|
|
433
|
+
return "TARGET_WEIGHT";
|
|
434
|
+
}
|
|
435
|
+
if (normalized === "position" || normalized === "positions") {
|
|
436
|
+
return "POSITION";
|
|
437
|
+
}
|
|
438
|
+
throw createCliValidationError("Holding mode must be target or position.");
|
|
439
|
+
}
|
|
440
|
+
function collectStringOption(value, previous) {
|
|
441
|
+
previous.push(value);
|
|
442
|
+
return previous;
|
|
443
|
+
}
|
|
444
|
+
function parseTargetArguments(values) {
|
|
445
|
+
if (values.length === 0) {
|
|
446
|
+
return [];
|
|
447
|
+
}
|
|
448
|
+
const targets = values.map(parseTargetArgument);
|
|
449
|
+
const tickers = targets.map(target => target.ticker);
|
|
450
|
+
if (new Set(tickers).size !== tickers.length) {
|
|
451
|
+
throw createCliValidationError("Do not send duplicated tickers.");
|
|
452
|
+
}
|
|
453
|
+
return targets;
|
|
454
|
+
}
|
|
455
|
+
function parseTargetArgument(value) {
|
|
456
|
+
const normalized = value.trim();
|
|
457
|
+
if (!normalized) {
|
|
458
|
+
throw createCliValidationError("Target input cannot be empty.");
|
|
459
|
+
}
|
|
460
|
+
const parts = normalized.split(":");
|
|
461
|
+
if (parts.length !== 2) {
|
|
462
|
+
throw createCliValidationError(`Invalid target input "${value}". Use TICKER:WEIGHT_PCT.`);
|
|
463
|
+
}
|
|
464
|
+
const [rawTicker, rawWeight] = parts;
|
|
465
|
+
const ticker = rawTicker?.trim().toUpperCase();
|
|
466
|
+
if (!ticker) {
|
|
467
|
+
throw createCliValidationError(`Invalid target input "${value}". Use TICKER:WEIGHT_PCT.`);
|
|
468
|
+
}
|
|
469
|
+
const weight_pct = Number(rawWeight?.trim());
|
|
470
|
+
if (!Number.isFinite(weight_pct) || weight_pct < 0 || weight_pct > 100) {
|
|
471
|
+
throw createCliValidationError(`Invalid target weight in "${value}". Use a number from 0 to 100.`);
|
|
472
|
+
}
|
|
473
|
+
return { ticker, weight_pct };
|
|
474
|
+
}
|
|
236
475
|
function parseSymbolArguments(values) {
|
|
237
476
|
const symbols = values.map(symbol => symbol.trim().toUpperCase());
|
|
238
477
|
if (symbols.length === 0 || symbols.some(symbol => symbol.length === 0)) {
|
|
@@ -252,6 +491,31 @@ function dedupeSymbols(symbols) {
|
|
|
252
491
|
}
|
|
253
492
|
return deduped;
|
|
254
493
|
}
|
|
494
|
+
function parsePositiveIntegerOption(value) {
|
|
495
|
+
const normalized = value.trim();
|
|
496
|
+
if (!/^\d+$/.test(normalized)) {
|
|
497
|
+
throw createCliValidationError("Value must be a positive integer.");
|
|
498
|
+
}
|
|
499
|
+
const parsed = Number(normalized);
|
|
500
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
501
|
+
throw createCliValidationError("Value must be a positive integer.");
|
|
502
|
+
}
|
|
503
|
+
return parsed;
|
|
504
|
+
}
|
|
505
|
+
function parseYearsOption(value) {
|
|
506
|
+
const parsed = parsePositiveIntegerOption(value);
|
|
507
|
+
if (![1, 3, 5].includes(parsed)) {
|
|
508
|
+
throw createCliValidationError("Years must be one of: 1, 3, 5.");
|
|
509
|
+
}
|
|
510
|
+
return parsed;
|
|
511
|
+
}
|
|
512
|
+
function parseNumberOption(value, fieldName) {
|
|
513
|
+
const parsed = Number(value.trim());
|
|
514
|
+
if (!Number.isFinite(parsed)) {
|
|
515
|
+
throw createCliValidationError(`${fieldName} must be a number.`);
|
|
516
|
+
}
|
|
517
|
+
return parsed;
|
|
518
|
+
}
|
|
255
519
|
function writeMutationResponse(data, json, stdout, theme) {
|
|
256
520
|
if (!data.ok) {
|
|
257
521
|
if (!json) {
|
|
@@ -283,9 +547,124 @@ function formatWeightsInline(portfolio) {
|
|
|
283
547
|
})
|
|
284
548
|
.join(", ");
|
|
285
549
|
}
|
|
550
|
+
function formatPortfolioKind(portfolio) {
|
|
551
|
+
if (portfolio.kind === "WATCHLIST") {
|
|
552
|
+
return "Watchlist";
|
|
553
|
+
}
|
|
554
|
+
if (portfolio.kind === "HOLDING") {
|
|
555
|
+
return "Holding";
|
|
556
|
+
}
|
|
557
|
+
return "Saved item";
|
|
558
|
+
}
|
|
286
559
|
function createCountSuffix(count) {
|
|
287
560
|
return count === 1 ? "(1 asset)" : `(${count} assets)`;
|
|
288
561
|
}
|
|
562
|
+
export function formatHistoricalReturnHuman(data, theme = createTerminalTheme(process.stdout)) {
|
|
563
|
+
const lines = [
|
|
564
|
+
theme.label("Historical return"),
|
|
565
|
+
"",
|
|
566
|
+
formatSourceLabel(data, theme),
|
|
567
|
+
`${theme.label("Period:")} ${data.effective_start_date} -> ${data.effective_end_date}`,
|
|
568
|
+
`${theme.label("Requested:")} ${data.requested_start_date} -> ${data.requested_end_date}`,
|
|
569
|
+
"",
|
|
570
|
+
`${theme.label("Holding return:")} ${formatPercentValue(data.total_return)}`,
|
|
571
|
+
`${theme.label("Annualized return:")} ${formatPercentValue(data.annualized_return)}`,
|
|
572
|
+
`${theme.label("Max drawdown:")} ${formatPercentValue(data.max_drawdown)}`,
|
|
573
|
+
`${theme.label("Daily volatility:")} ${formatPercentValue(data.daily_volatility)}`,
|
|
574
|
+
`${theme.label("Annualized volatility:")} ${formatPercentValue(data.annualized_volatility)}`,
|
|
575
|
+
`${theme.label("Sharpe ratio:")} ${formatNumber(data.sharpe_ratio)}`,
|
|
576
|
+
"",
|
|
577
|
+
theme.label("Benchmarks"),
|
|
578
|
+
` IBOV: ${formatPercentValue(data.ibov_return)}`,
|
|
579
|
+
` CDI: ${formatPercentValue(data.cdi_return)}`,
|
|
580
|
+
` IPCA: ${formatPercentValue(data.ipca_return)}`,
|
|
581
|
+
"",
|
|
582
|
+
theme.label("Holdings"),
|
|
583
|
+
...data.holdings.map(item => ` - ${item.ticker}: weight=${formatPercentDirect(item.weight_pct)}, return=${formatPercentValue(item.total_return)}, contribution=${formatPercentValue(item.contribution)}`),
|
|
584
|
+
];
|
|
585
|
+
pushNotes(lines, "Assumptions", data.assumptions, theme);
|
|
586
|
+
pushNotes(lines, "Warnings", data.warnings, theme);
|
|
587
|
+
return lines.join("\n");
|
|
588
|
+
}
|
|
589
|
+
export function formatBetaHuman(data, theme = createTerminalTheme(process.stdout)) {
|
|
590
|
+
const lines = [
|
|
591
|
+
theme.label("Holding beta"),
|
|
592
|
+
"",
|
|
593
|
+
formatSourceLabel(data, theme),
|
|
594
|
+
`${theme.label("Benchmark:")} ${data.benchmark}`,
|
|
595
|
+
`${theme.label("Lookback:")} ${data.lookback_years}Y`,
|
|
596
|
+
"",
|
|
597
|
+
`${theme.label("Beta:")} ${formatNumber(data.beta)}`,
|
|
598
|
+
`${theme.label("Correlation:")} ${formatNumber(data.correlation)}`,
|
|
599
|
+
`${theme.label("Daily volatility:")} ${formatPercentValue(data.daily_volatility)}`,
|
|
600
|
+
`${theme.label("Annualized volatility:")} ${formatPercentValue(data.annualized_volatility)}`,
|
|
601
|
+
`${theme.label("Long exposure:")} ${formatPercentDirect(data.long_exposure_pct)}`,
|
|
602
|
+
`${theme.label("Short exposure:")} ${formatPercentDirect(data.short_exposure_pct)}`,
|
|
603
|
+
`${theme.label("Net exposure:")} ${formatPercentDirect(data.total_weight_pct)}`,
|
|
604
|
+
"",
|
|
605
|
+
theme.label("Holdings"),
|
|
606
|
+
...data.holdings.map(item => ` - ${item.ticker}: weight=${formatPercentDirect(item.weight_pct)}, beta=${formatNullableNumber(item.beta)}, weighted=${formatNullableNumber(item.weighted_beta)}, corr=${formatNullableNumber(item.correlation)}`),
|
|
607
|
+
];
|
|
608
|
+
pushNotes(lines, "Assumptions", data.assumptions, theme);
|
|
609
|
+
pushNotes(lines, "Warnings", data.warnings, theme);
|
|
610
|
+
return lines.join("\n");
|
|
611
|
+
}
|
|
612
|
+
export function formatVarHuman(data, theme = createTerminalTheme(process.stdout)) {
|
|
613
|
+
const lines = [
|
|
614
|
+
theme.label("Holding VaR"),
|
|
615
|
+
"",
|
|
616
|
+
formatSourceLabel(data, theme),
|
|
617
|
+
`${theme.label("Lookback:")} ${data.lookback_years}Y`,
|
|
618
|
+
`${theme.label("Confidence:")} ${formatPercentDirect(data.confidence_pct)}`,
|
|
619
|
+
`${theme.label("Horizon:")} ${data.time_horizon}`,
|
|
620
|
+
"",
|
|
621
|
+
`${theme.label("VaR:")} ${formatPercentValue(data.var)}`,
|
|
622
|
+
`${theme.label("Long exposure:")} ${formatPercentDirect(data.long_exposure_pct)}`,
|
|
623
|
+
`${theme.label("Short exposure:")} ${formatPercentDirect(data.short_exposure_pct)}`,
|
|
624
|
+
`${theme.label("Net exposure:")} ${formatPercentDirect(data.total_weight_pct)}`,
|
|
625
|
+
"",
|
|
626
|
+
theme.label("Holdings"),
|
|
627
|
+
...data.holdings.map(item => ` - ${item.ticker}: weight=${formatPercentDirect(item.weight_pct)}`),
|
|
628
|
+
"",
|
|
629
|
+
`${theme.label("Histogram bins:")} ${formatInteger(data.histogram_data.length)}`,
|
|
630
|
+
];
|
|
631
|
+
pushNotes(lines, "Assumptions", data.assumptions, theme);
|
|
632
|
+
pushNotes(lines, "Warnings", data.warnings, theme);
|
|
633
|
+
return lines.join("\n");
|
|
634
|
+
}
|
|
635
|
+
function formatSourceLabel(data, theme) {
|
|
636
|
+
const id = data.portfolio_id === null ? "n/a" : String(data.portfolio_id);
|
|
637
|
+
const name = data.portfolio_name ? ` · ${data.portfolio_name}` : "";
|
|
638
|
+
return `${theme.bold(id)}${theme.dim(name)}`;
|
|
639
|
+
}
|
|
640
|
+
function pushNotes(lines, title, items, theme) {
|
|
641
|
+
if (items.length === 0) {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
lines.push("");
|
|
645
|
+
lines.push(theme.label(title));
|
|
646
|
+
for (const item of items) {
|
|
647
|
+
lines.push(` - ${item}`);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function formatNullableNumber(value) {
|
|
651
|
+
if (value === null) {
|
|
652
|
+
return "n/a";
|
|
653
|
+
}
|
|
654
|
+
return formatNumber(value);
|
|
655
|
+
}
|
|
656
|
+
function formatPercentValue(value) {
|
|
657
|
+
return `${new Intl.NumberFormat("en-US", {
|
|
658
|
+
maximumFractionDigits: 2,
|
|
659
|
+
minimumFractionDigits: 2,
|
|
660
|
+
}).format(value * 100)}%`;
|
|
661
|
+
}
|
|
662
|
+
function formatPercentDirect(value) {
|
|
663
|
+
return `${new Intl.NumberFormat("en-US", {
|
|
664
|
+
maximumFractionDigits: 2,
|
|
665
|
+
minimumFractionDigits: 2,
|
|
666
|
+
}).format(value)}%`;
|
|
667
|
+
}
|
|
289
668
|
function formatInteger(value) {
|
|
290
669
|
return new Intl.NumberFormat("en-US", {
|
|
291
670
|
maximumFractionDigits: 0,
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ export * from "./cli/auth.js";
|
|
|
2
2
|
export * from "./cli/client.js";
|
|
3
3
|
export * from "./cli/errors.js";
|
|
4
4
|
export * from "./cli/index.js";
|
|
5
|
-
export * from "./commands/analytics.js";
|
|
6
5
|
export * from "./commands/assets.js";
|
|
7
6
|
export * from "./commands/auth.js";
|
|
8
7
|
export * from "./commands/capabilities.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC"}
|