@quantbrasil/cli 0.1.0-beta.1 → 0.1.0-beta.11
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 +64 -11
- package/dist/cli/client.js +4 -0
- package/dist/cli/index.d.ts +14 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +72 -14
- package/dist/cli/prompt.d.ts +1 -0
- package/dist/cli/prompt.d.ts.map +1 -1
- package/dist/cli/prompt.js +17 -0
- package/dist/cli/skills.d.ts +9 -0
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +68 -4
- package/dist/commands/auth.d.ts +18 -0
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +49 -0
- package/dist/commands/cointegration.d.ts +52 -0
- package/dist/commands/cointegration.d.ts.map +1 -0
- package/dist/commands/cointegration.js +118 -0
- package/dist/commands/market.d.ts +1 -0
- package/dist/commands/market.d.ts.map +1 -1
- package/dist/commands/market.js +17 -1
- package/dist/commands/portfolios.d.ts +148 -8
- package/dist/commands/portfolios.d.ts.map +1 -1
- package/dist/commands/portfolios.js +557 -55
- package/dist/commands/rankings.d.ts +82 -0
- package/dist/commands/rankings.d.ts.map +1 -0
- package/dist/commands/rankings.js +235 -0
- package/dist/commands/screening.d.ts +120 -0
- package/dist/commands/screening.d.ts.map +1 -0
- package/dist/commands/screening.js +361 -0
- package/dist/commands/skills.js +7 -7
- package/dist/commands/update.d.ts +23 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +209 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/vendor/core/capabilities/cointegration.d.ts +52 -0
- package/dist/vendor/core/capabilities/cointegration.d.ts.map +1 -0
- package/dist/vendor/core/capabilities/cointegration.js +63 -0
- package/dist/vendor/core/capabilities/index.d.ts +3 -1
- package/dist/vendor/core/capabilities/index.d.ts.map +1 -1
- package/dist/vendor/core/capabilities/index.js +3 -1
- package/dist/vendor/core/capabilities/market.d.ts +9 -1
- package/dist/vendor/core/capabilities/market.d.ts.map +1 -1
- package/dist/vendor/core/capabilities/market.js +10 -0
- package/dist/vendor/core/capabilities/portfolios.d.ts +452 -56
- package/dist/vendor/core/capabilities/portfolios.d.ts.map +1 -1
- package/dist/vendor/core/capabilities/portfolios.js +434 -116
- package/dist/vendor/core/capabilities/rankings.d.ts +83 -0
- package/dist/vendor/core/capabilities/rankings.d.ts.map +1 -0
- package/dist/vendor/core/capabilities/rankings.js +96 -0
- package/dist/vendor/core/capabilities/registry.d.ts +1380 -414
- package/dist/vendor/core/capabilities/registry.d.ts.map +1 -1
- package/dist/vendor/core/capabilities/registry.js +6 -2
- package/dist/vendor/core/capabilities/screening.d.ts +136 -0
- package/dist/vendor/core/capabilities/screening.d.ts.map +1 -0
- package/dist/vendor/core/capabilities/screening.js +155 -0
- package/dist/vendor/core/capabilities/types.d.ts +1 -1
- package/dist/vendor/core/capabilities/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/skills/quantbrasil/SKILL.md +31 -11
- package/skills/quantbrasil/references/cli.md +112 -19
- package/skills/quantbrasil/references/cointegration.md +40 -0
- package/skills/quantbrasil/references/costs.md +10 -4
- package/skills/quantbrasil/references/errors.md +16 -5
- package/skills/quantbrasil/references/portfolios.md +114 -0
- package/skills/quantbrasil/references/quality-eval-queries.json +147 -0
- package/skills/quantbrasil/references/rankings.md +64 -0
- package/skills/quantbrasil/references/screening.md +212 -0
- package/skills/quantbrasil/references/unsupported.md +9 -2
- package/skills/quantbrasil/references/workflows.md +117 -23
- 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
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
# Workflows
|
|
2
2
|
|
|
3
|
+
## Choose command from user intent
|
|
4
|
+
|
|
5
|
+
- asset price, quote, or daily move → `market price`
|
|
6
|
+
- supported assets, tickers, or market universe → `market assets`
|
|
7
|
+
- asset performance, technicals, risk, fundamentals, or ranking → `assets overview`
|
|
8
|
+
- current ordered asset lists such as Magic Formula, momentum, Low Risk, or user rankings → `rankings list`, then `rankings current`
|
|
9
|
+
- watchlist details or changes → `watchlists ...`
|
|
10
|
+
- holding details or changes → `holdings ...`
|
|
11
|
+
- holding return, beta, risk, VaR, or comparison → `holdings historical-return|beta|var`
|
|
12
|
+
- indicator screening over saved asset sets → `screening universes`, `screening indicators` when needed, then `screening run`
|
|
13
|
+
- cointegration or Long & Short between two explicit assets → `cointegration pair`
|
|
14
|
+
|
|
3
15
|
## Find supported ticker, then get price
|
|
4
16
|
|
|
5
17
|
Use this when user gives company theme, partial name, or market universe question.
|
|
6
18
|
|
|
7
19
|
```bash
|
|
8
|
-
quantbrasil market assets --
|
|
20
|
+
quantbrasil market assets --search PETR
|
|
9
21
|
quantbrasil market price PETR4
|
|
10
22
|
```
|
|
11
23
|
|
|
12
24
|
If exact ticker already known, skip discovery and go straight to `market price`.
|
|
25
|
+
Use `--type` when the user asks for a whole supported universe.
|
|
13
26
|
|
|
14
27
|
## Get price on specific date
|
|
15
28
|
|
|
@@ -26,6 +39,9 @@ Use absolute ISO dates.
|
|
|
26
39
|
- need richer analysis for one asset → `assets overview`
|
|
27
40
|
|
|
28
41
|
Do not jump to `assets overview` if price-only answer is enough.
|
|
42
|
+
Do not use generic web or finance search for QuantBrasil-supported market data
|
|
43
|
+
unless the CLI is unavailable, the requested data is outside the supported
|
|
44
|
+
surface, or the user explicitly asks for an external source.
|
|
29
45
|
|
|
30
46
|
## Cost-aware `assets overview` section choice
|
|
31
47
|
|
|
@@ -48,52 +64,130 @@ Guidance:
|
|
|
48
64
|
|
|
49
65
|
Avoid asking for every section unless user clearly wants full report.
|
|
50
66
|
|
|
51
|
-
##
|
|
67
|
+
## Get current ranked assets
|
|
52
68
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
69
|
+
Use this when the user asks for an ordered list such as top Magic Formula,
|
|
70
|
+
momentum leaders, dividend-yield leaders, Low Risk, Momentum Double, or a saved
|
|
71
|
+
user ranking.
|
|
56
72
|
|
|
57
73
|
```bash
|
|
58
|
-
quantbrasil
|
|
59
|
-
quantbrasil
|
|
60
|
-
quantbrasil
|
|
61
|
-
quantbrasil
|
|
62
|
-
quantbrasil
|
|
74
|
+
quantbrasil rankings list
|
|
75
|
+
quantbrasil rankings current --system momentum-90d --top 20
|
|
76
|
+
quantbrasil rankings current --system magic-formula --top 10
|
|
77
|
+
quantbrasil rankings current --system low-risk --top 30
|
|
78
|
+
quantbrasil rankings current --id 123 --top 20
|
|
63
79
|
```
|
|
64
80
|
|
|
65
|
-
|
|
81
|
+
Rules:
|
|
82
|
+
|
|
83
|
+
- rankings answer order-first questions
|
|
84
|
+
- system rankings use their slug from `rankings list`
|
|
85
|
+
- user rankings use their numeric id from `rankings list`
|
|
86
|
+
- use exactly one selector: `--system <slug>` or `--id <id>`
|
|
87
|
+
- do not call user rankings "factor:" in command syntax; ranking fatorial is a saved user ranking
|
|
88
|
+
- use `--json` if another step needs to parse tickers, ranks, or ordering metrics
|
|
89
|
+
- do not route ranking-first questions through screening unless the user asks for indicator conditions
|
|
90
|
+
|
|
91
|
+
## Analyze saved holding
|
|
92
|
+
|
|
93
|
+
1. Discover holding id.
|
|
94
|
+
2. Inspect the holding if needed.
|
|
95
|
+
3. Run the holding metric.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
quantbrasil holdings list
|
|
99
|
+
quantbrasil holdings get 93
|
|
100
|
+
quantbrasil holdings historical-return 93 --period 1y
|
|
101
|
+
quantbrasil holdings beta 93 --years 1
|
|
102
|
+
quantbrasil holdings var 93 --years 1 --confidence 95
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Run indicator screening
|
|
106
|
+
|
|
107
|
+
1. Discover available universes.
|
|
108
|
+
2. Discover available indicators when the filter JSON is not already known.
|
|
109
|
+
3. Pick exactly one system portfolio, watchlist, or holding.
|
|
110
|
+
4. Run screening with a full `ScreenerRequest` JSON file.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
quantbrasil screening universes
|
|
114
|
+
quantbrasil screening indicators
|
|
115
|
+
quantbrasil screening run --system acoes-mais-liquidas --query-file ./screening.json
|
|
116
|
+
quantbrasil screening run --watchlist 93 --query-file ./screening.json --limit 25
|
|
117
|
+
quantbrasil screening run --holding 182 --query-file ./screening.json --sort ticker
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Rules:
|
|
121
|
+
|
|
122
|
+
- screening universes are portfolio-backed only: system portfolios, watchlists, or holdings
|
|
123
|
+
- a holding used as a screening universe contributes only its asset set
|
|
124
|
+
- do not invent ad-hoc ticker-list screening commands
|
|
125
|
+
- load `references/screening.md` for IFR/RSI examples and full JSON payloads
|
|
126
|
+
- use `--json` when the result will be parsed by an agent or script
|
|
127
|
+
|
|
128
|
+
## Run pair cointegration / Long & Short
|
|
129
|
+
|
|
130
|
+
Use this when the user asks to run cointegração, cointegration, Long & Short,
|
|
131
|
+
long and short, long-short, pair trading statistics, z-score, p-value, or
|
|
132
|
+
half-life for two explicit assets.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
quantbrasil cointegration pair PETR4 VALE3
|
|
136
|
+
quantbrasil cointegration pair PETR4 VALE3 --window 120
|
|
137
|
+
quantbrasil cointegration pair PETR4 VALE3 --json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Rules:
|
|
141
|
+
|
|
142
|
+
- require two explicit tickers
|
|
143
|
+
- frame Long & Short phrasing as pair analysis, not order execution or a guaranteed trade recommendation
|
|
144
|
+
- use `market assets --search` first only if a ticker is ambiguous
|
|
145
|
+
- do not invent a universe scan command for "quais pares cointegrados"; the public CLI currently supports explicit pairs
|
|
146
|
+
- use `--json` if the z-score or beta series will feed another calculation
|
|
147
|
+
|
|
148
|
+
## Analyze a theoretical composition
|
|
66
149
|
|
|
67
|
-
|
|
150
|
+
Create a holding with target weights, then run holding metrics on its id.
|
|
68
151
|
|
|
69
152
|
Rules:
|
|
70
153
|
|
|
71
|
-
-
|
|
72
|
-
-
|
|
154
|
+
- `holdings create` returns the holding id
|
|
155
|
+
- use `holdings set-targets` to replace target weights later
|
|
156
|
+
- use `holdings set-positions` for actual share/unit/coin quantities
|
|
73
157
|
- `beta` only accepts `--years 1|3|5`
|
|
74
158
|
- `var --confidence` takes percent like `95` or `99`
|
|
75
159
|
|
|
76
160
|
Example:
|
|
77
161
|
|
|
78
162
|
```bash
|
|
79
|
-
quantbrasil
|
|
80
|
-
quantbrasil
|
|
81
|
-
quantbrasil
|
|
163
|
+
quantbrasil holdings create "Teórica energia" --target VALE3:50 --target PRIO3:50
|
|
164
|
+
quantbrasil holdings historical-return 93 --from 2025-01-01 --to 2026-01-01
|
|
165
|
+
quantbrasil holdings beta 93 --years 1
|
|
166
|
+
quantbrasil holdings var 93 --years 1 --confidence 99
|
|
82
167
|
```
|
|
83
168
|
|
|
84
|
-
## Modify
|
|
169
|
+
## Modify watchlists and holdings
|
|
85
170
|
|
|
86
171
|
Use explicit mutation commands. Do not retry failed mutations automatically.
|
|
87
172
|
|
|
88
173
|
```bash
|
|
89
|
-
quantbrasil
|
|
90
|
-
quantbrasil
|
|
91
|
-
quantbrasil
|
|
92
|
-
quantbrasil
|
|
174
|
+
quantbrasil watchlists create "Dividendos"
|
|
175
|
+
quantbrasil watchlists rename 93 "Longo Prazo"
|
|
176
|
+
quantbrasil watchlists add-assets 93 PETR4 VALE3
|
|
177
|
+
quantbrasil watchlists remove-assets 93 PETR4
|
|
178
|
+
|
|
179
|
+
quantbrasil holdings create "Longo Prazo"
|
|
180
|
+
quantbrasil holdings create "Longo Prazo" --target PETR4:50 --target VALE3:50
|
|
181
|
+
quantbrasil holdings create "Carteira Real" --mode position
|
|
182
|
+
quantbrasil holdings rename 182 "Carteira Real"
|
|
183
|
+
quantbrasil holdings set-targets 182 PETR4:50 VALE3:50
|
|
184
|
+
quantbrasil holdings set-positions 182 PRIO3:1600 BTC-USD:0.25 QQQ:10
|
|
93
185
|
```
|
|
94
186
|
|
|
95
187
|
Rules:
|
|
96
188
|
|
|
97
|
-
- use `
|
|
189
|
+
- use `watchlists list` or `holdings list` first if the user names a saved object instead of giving id
|
|
98
190
|
- confirm destructive intent before removing assets when user request is ambiguous
|
|
191
|
+
- positions are quantity-only in the public CLI; do not pass monetary values as positions
|
|
192
|
+
- target weights and positions are independent; updating positions does not clear target weights
|
|
99
193
|
- use `--json` only when structured output is needed
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import type { JsonValue } from "../vendor/core/index.js";
|
|
3
|
-
import { type CliInvokeContext } from "../cli/client.js";
|
|
4
|
-
import { type TerminalWriter } from "../cli/terminal.js";
|
|
5
|
-
export interface AnalyticsCommandIO {
|
|
6
|
-
stdout: TerminalWriter;
|
|
7
|
-
}
|
|
8
|
-
export interface AnalyticsCommandContext extends CliInvokeContext {
|
|
9
|
-
io?: AnalyticsCommandIO;
|
|
10
|
-
}
|
|
11
|
-
export interface HistoricalReturnCommandOptions {
|
|
12
|
-
portfolio?: number;
|
|
13
|
-
asset?: string[];
|
|
14
|
-
from: string;
|
|
15
|
-
to: string;
|
|
16
|
-
json?: boolean;
|
|
17
|
-
}
|
|
18
|
-
export interface BetaCommandOptions {
|
|
19
|
-
portfolio?: number;
|
|
20
|
-
asset?: string[];
|
|
21
|
-
years?: string;
|
|
22
|
-
json?: boolean;
|
|
23
|
-
}
|
|
24
|
-
export interface VarCommandOptions {
|
|
25
|
-
portfolio?: number;
|
|
26
|
-
asset?: string[];
|
|
27
|
-
years?: string;
|
|
28
|
-
confidence?: string;
|
|
29
|
-
json?: boolean;
|
|
30
|
-
}
|
|
31
|
-
export interface HistoricalReturnHolding {
|
|
32
|
-
[key: string]: JsonValue;
|
|
33
|
-
ticker: string;
|
|
34
|
-
weight_pct: number;
|
|
35
|
-
total_return: number;
|
|
36
|
-
contribution: number;
|
|
37
|
-
}
|
|
38
|
-
export interface HistoricalReturnResponse {
|
|
39
|
-
[key: string]: JsonValue;
|
|
40
|
-
source_type: "saved_portfolio" | "ad_hoc";
|
|
41
|
-
portfolio_id: number | null;
|
|
42
|
-
portfolio_name: string | null;
|
|
43
|
-
requested_start_date: string;
|
|
44
|
-
requested_end_date: string;
|
|
45
|
-
effective_start_date: string;
|
|
46
|
-
effective_end_date: string;
|
|
47
|
-
total_return: number;
|
|
48
|
-
annualized_return: number;
|
|
49
|
-
max_drawdown: number;
|
|
50
|
-
daily_volatility: number;
|
|
51
|
-
annualized_volatility: number;
|
|
52
|
-
sharpe_ratio: number;
|
|
53
|
-
ibov_return: number;
|
|
54
|
-
cdi_return: number;
|
|
55
|
-
ipca_return: number;
|
|
56
|
-
holdings: HistoricalReturnHolding[];
|
|
57
|
-
summary_markdown: string;
|
|
58
|
-
assumptions: string[];
|
|
59
|
-
warnings: string[];
|
|
60
|
-
}
|
|
61
|
-
export interface BetaHolding {
|
|
62
|
-
[key: string]: JsonValue;
|
|
63
|
-
ticker: string;
|
|
64
|
-
weight_pct: number;
|
|
65
|
-
beta: number | null;
|
|
66
|
-
weighted_beta: number | null;
|
|
67
|
-
correlation: number | null;
|
|
68
|
-
asset_daily_volatility: number | null;
|
|
69
|
-
benchmark_daily_volatility: number | null;
|
|
70
|
-
last_updated: string | null;
|
|
71
|
-
}
|
|
72
|
-
export interface BetaResponse {
|
|
73
|
-
[key: string]: JsonValue;
|
|
74
|
-
source_type: "saved_portfolio" | "ad_hoc";
|
|
75
|
-
portfolio_id: number | null;
|
|
76
|
-
portfolio_name: string | null;
|
|
77
|
-
benchmark: "IBOV";
|
|
78
|
-
lookback_years: number;
|
|
79
|
-
beta: number;
|
|
80
|
-
correlation: number;
|
|
81
|
-
daily_volatility: number;
|
|
82
|
-
annualized_volatility: number;
|
|
83
|
-
long_exposure_pct: number;
|
|
84
|
-
short_exposure_pct: number;
|
|
85
|
-
total_weight_pct: number;
|
|
86
|
-
holdings: BetaHolding[];
|
|
87
|
-
summary_markdown: string;
|
|
88
|
-
assumptions: string[];
|
|
89
|
-
warnings: string[];
|
|
90
|
-
}
|
|
91
|
-
export interface VarHolding {
|
|
92
|
-
[key: string]: JsonValue;
|
|
93
|
-
ticker: string;
|
|
94
|
-
weight_pct: number;
|
|
95
|
-
}
|
|
96
|
-
export interface VarResponse {
|
|
97
|
-
[key: string]: JsonValue;
|
|
98
|
-
source_type: "saved_portfolio" | "ad_hoc";
|
|
99
|
-
portfolio_id: number | null;
|
|
100
|
-
portfolio_name: string | null;
|
|
101
|
-
lookback_years: number;
|
|
102
|
-
confidence_pct: number;
|
|
103
|
-
time_horizon: "1d";
|
|
104
|
-
var: number;
|
|
105
|
-
long_exposure_pct: number;
|
|
106
|
-
short_exposure_pct: number;
|
|
107
|
-
total_weight_pct: number;
|
|
108
|
-
holdings: VarHolding[];
|
|
109
|
-
histogram_data: JsonValue[];
|
|
110
|
-
summary_markdown: string;
|
|
111
|
-
assumptions: string[];
|
|
112
|
-
warnings: string[];
|
|
113
|
-
}
|
|
114
|
-
interface ParsedAssetInput {
|
|
115
|
-
[key: string]: JsonValue;
|
|
116
|
-
ticker: string;
|
|
117
|
-
weight_pct: number | null;
|
|
118
|
-
}
|
|
119
|
-
export declare function registerAnalyticsCommands(program: Command, context?: AnalyticsCommandContext): void;
|
|
120
|
-
export declare function runHistoricalReturnCommand(options: HistoricalReturnCommandOptions, context?: AnalyticsCommandContext): Promise<void>;
|
|
121
|
-
export declare function runBetaCommand(options: BetaCommandOptions, context?: AnalyticsCommandContext): Promise<void>;
|
|
122
|
-
export declare function runVarCommand(options: VarCommandOptions, context?: AnalyticsCommandContext): Promise<void>;
|
|
123
|
-
export declare function buildHistoricalReturnInput(options: HistoricalReturnCommandOptions): JsonValue;
|
|
124
|
-
export declare function buildBetaInput(options: BetaCommandOptions): JsonValue;
|
|
125
|
-
export declare function buildVarInput(options: VarCommandOptions): JsonValue;
|
|
126
|
-
export declare function formatHistoricalReturnHuman(data: HistoricalReturnResponse, theme?: import("../cli/terminal.js").TerminalTheme): string;
|
|
127
|
-
export declare function formatBetaHuman(data: BetaResponse, theme?: import("../cli/terminal.js").TerminalTheme): string;
|
|
128
|
-
export declare function formatVarHuman(data: VarResponse, theme?: import("../cli/terminal.js").TerminalTheme): string;
|
|
129
|
-
export declare function parseAssetSpec(rawAsset: string): ParsedAssetInput;
|
|
130
|
-
export {};
|
|
131
|
-
//# sourceMappingURL=analytics.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/commands/analytics.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import { invokeCliCapability } from "../cli/client.js";
|
|
2
|
-
import { createCliValidationError } from "../cli/errors.js";
|
|
3
|
-
import { createTerminalTheme } from "../cli/terminal.js";
|
|
4
|
-
export function registerAnalyticsCommands(program, context = {}) {
|
|
5
|
-
const analyticsCommand = program
|
|
6
|
-
.command("analytics")
|
|
7
|
-
.description("Read portfolio analytics operations");
|
|
8
|
-
analyticsCommand
|
|
9
|
-
.command("historical-return")
|
|
10
|
-
.description("Calculate historical return for a saved portfolio or basket")
|
|
11
|
-
.option("--portfolio <id>", "Saved portfolio id", parsePositiveIntegerOption)
|
|
12
|
-
.option("--asset <asset>", "Repeatable asset input in TICKER[:WEIGHT_PCT] form", collectStringOption, [])
|
|
13
|
-
.requiredOption("--from <date>", "Inclusive start date in ISO format")
|
|
14
|
-
.requiredOption("--to <date>", "Inclusive end date in ISO format")
|
|
15
|
-
.option("--json", "Show JSON output")
|
|
16
|
-
.action(async (options) => {
|
|
17
|
-
await runHistoricalReturnCommand(options, context);
|
|
18
|
-
});
|
|
19
|
-
analyticsCommand
|
|
20
|
-
.command("beta")
|
|
21
|
-
.description("Calculate portfolio beta against IBOV")
|
|
22
|
-
.option("--portfolio <id>", "Saved portfolio id", parsePositiveIntegerOption)
|
|
23
|
-
.option("--asset <asset>", "Repeatable asset input in TICKER[:WEIGHT_PCT] form", collectStringOption, [])
|
|
24
|
-
.option("--years <years>", "Lookback window in years", "1")
|
|
25
|
-
.option("--json", "Show JSON output")
|
|
26
|
-
.action(async (options) => {
|
|
27
|
-
await runBetaCommand(options, context);
|
|
28
|
-
});
|
|
29
|
-
analyticsCommand
|
|
30
|
-
.command("var")
|
|
31
|
-
.description("Calculate one-day Value-at-Risk for a portfolio or basket")
|
|
32
|
-
.option("--portfolio <id>", "Saved portfolio id", parsePositiveIntegerOption)
|
|
33
|
-
.option("--asset <asset>", "Repeatable asset input in TICKER[:WEIGHT_PCT] form", collectStringOption, [])
|
|
34
|
-
.option("--years <years>", "Lookback window in years", "1")
|
|
35
|
-
.option("--confidence <confidence>", "Confidence level in percent", "95")
|
|
36
|
-
.option("--json", "Show JSON output")
|
|
37
|
-
.action(async (options) => {
|
|
38
|
-
await runVarCommand(options, context);
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
export async function runHistoricalReturnCommand(options, context = {}) {
|
|
42
|
-
const stdout = context.io?.stdout ?? process.stdout;
|
|
43
|
-
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
44
|
-
const response = await invokeCliCapability({
|
|
45
|
-
capability: "analytics.historical-return",
|
|
46
|
-
input: buildHistoricalReturnInput(options),
|
|
47
|
-
env: context.env,
|
|
48
|
-
fetch: context.fetch,
|
|
49
|
-
});
|
|
50
|
-
if (options.json) {
|
|
51
|
-
stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
stdout.write(`${formatHistoricalReturnHuman(response.data, theme)}\n`);
|
|
55
|
-
}
|
|
56
|
-
export async function runBetaCommand(options, context = {}) {
|
|
57
|
-
const stdout = context.io?.stdout ?? process.stdout;
|
|
58
|
-
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
59
|
-
const response = await invokeCliCapability({
|
|
60
|
-
capability: "analytics.beta",
|
|
61
|
-
input: buildBetaInput(options),
|
|
62
|
-
env: context.env,
|
|
63
|
-
fetch: context.fetch,
|
|
64
|
-
});
|
|
65
|
-
if (options.json) {
|
|
66
|
-
stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
stdout.write(`${formatBetaHuman(response.data, theme)}\n`);
|
|
70
|
-
}
|
|
71
|
-
export async function runVarCommand(options, context = {}) {
|
|
72
|
-
const stdout = context.io?.stdout ?? process.stdout;
|
|
73
|
-
const theme = createTerminalTheme(stdout, context.env ?? process.env);
|
|
74
|
-
const response = await invokeCliCapability({
|
|
75
|
-
capability: "analytics.var",
|
|
76
|
-
input: buildVarInput(options),
|
|
77
|
-
env: context.env,
|
|
78
|
-
fetch: context.fetch,
|
|
79
|
-
});
|
|
80
|
-
if (options.json) {
|
|
81
|
-
stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
stdout.write(`${formatVarHuman(response.data, theme)}\n`);
|
|
85
|
-
}
|
|
86
|
-
export function buildHistoricalReturnInput(options) {
|
|
87
|
-
const source = resolveAnalyticsSource(options.portfolio, options.asset ?? []);
|
|
88
|
-
return {
|
|
89
|
-
...source,
|
|
90
|
-
start_date: options.from,
|
|
91
|
-
end_date: options.to,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
export function buildBetaInput(options) {
|
|
95
|
-
const source = resolveAnalyticsSource(options.portfolio, options.asset ?? []);
|
|
96
|
-
return {
|
|
97
|
-
...source,
|
|
98
|
-
years: parseYearsOption(options.years ?? "1"),
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
export function buildVarInput(options) {
|
|
102
|
-
const source = resolveAnalyticsSource(options.portfolio, options.asset ?? []);
|
|
103
|
-
return {
|
|
104
|
-
...source,
|
|
105
|
-
years: parsePositiveIntegerOption(options.years ?? "1"),
|
|
106
|
-
confidence_pct: parseNumberOption(options.confidence ?? "95", "confidence"),
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
export function formatHistoricalReturnHuman(data, theme = createTerminalTheme(process.stdout)) {
|
|
110
|
-
const lines = [
|
|
111
|
-
theme.label("Historical return"),
|
|
112
|
-
"",
|
|
113
|
-
formatSourceLabel(data, theme),
|
|
114
|
-
`${theme.label("Period:")} ${data.effective_start_date} → ${data.effective_end_date}`,
|
|
115
|
-
`${theme.label("Requested:")} ${data.requested_start_date} → ${data.requested_end_date}`,
|
|
116
|
-
"",
|
|
117
|
-
`${theme.label("Portfolio return:")} ${formatPercentValue(data.total_return)}`,
|
|
118
|
-
`${theme.label("Annualized return:")} ${formatPercentValue(data.annualized_return)}`,
|
|
119
|
-
`${theme.label("Max drawdown:")} ${formatPercentValue(data.max_drawdown)}`,
|
|
120
|
-
`${theme.label("Daily volatility:")} ${formatPercentValue(data.daily_volatility)}`,
|
|
121
|
-
`${theme.label("Annualized volatility:")} ${formatPercentValue(data.annualized_volatility)}`,
|
|
122
|
-
`${theme.label("Sharpe ratio:")} ${formatNumber(data.sharpe_ratio)}`,
|
|
123
|
-
"",
|
|
124
|
-
theme.label("Benchmarks"),
|
|
125
|
-
` IBOV: ${formatPercentValue(data.ibov_return)}`,
|
|
126
|
-
` CDI: ${formatPercentValue(data.cdi_return)}`,
|
|
127
|
-
` IPCA: ${formatPercentValue(data.ipca_return)}`,
|
|
128
|
-
"",
|
|
129
|
-
theme.label("Holdings"),
|
|
130
|
-
...data.holdings.map(item => ` - ${item.ticker}: weight=${formatPercentDirect(item.weight_pct)}, return=${formatPercentValue(item.total_return)}, contribution=${formatPercentValue(item.contribution)}`),
|
|
131
|
-
];
|
|
132
|
-
pushNotes(lines, "Assumptions", data.assumptions, theme);
|
|
133
|
-
pushNotes(lines, "Warnings", data.warnings, theme);
|
|
134
|
-
return lines.join("\n");
|
|
135
|
-
}
|
|
136
|
-
export function formatBetaHuman(data, theme = createTerminalTheme(process.stdout)) {
|
|
137
|
-
const lines = [
|
|
138
|
-
theme.label("Portfolio beta"),
|
|
139
|
-
"",
|
|
140
|
-
formatSourceLabel(data, theme),
|
|
141
|
-
`${theme.label("Benchmark:")} ${data.benchmark}`,
|
|
142
|
-
`${theme.label("Lookback:")} ${data.lookback_years}Y`,
|
|
143
|
-
"",
|
|
144
|
-
`${theme.label("Beta:")} ${formatNumber(data.beta)}`,
|
|
145
|
-
`${theme.label("Correlation:")} ${formatNumber(data.correlation)}`,
|
|
146
|
-
`${theme.label("Daily volatility:")} ${formatPercentValue(data.daily_volatility)}`,
|
|
147
|
-
`${theme.label("Annualized volatility:")} ${formatPercentValue(data.annualized_volatility)}`,
|
|
148
|
-
`${theme.label("Long exposure:")} ${formatPercentDirect(data.long_exposure_pct)}`,
|
|
149
|
-
`${theme.label("Short exposure:")} ${formatPercentDirect(data.short_exposure_pct)}`,
|
|
150
|
-
`${theme.label("Net exposure:")} ${formatPercentDirect(data.total_weight_pct)}`,
|
|
151
|
-
"",
|
|
152
|
-
theme.label("Holdings"),
|
|
153
|
-
...data.holdings.map(item => ` - ${item.ticker}: weight=${formatPercentDirect(item.weight_pct)}, beta=${formatNullableNumber(item.beta)}, weighted=${formatNullableNumber(item.weighted_beta)}, corr=${formatNullableNumber(item.correlation)}`),
|
|
154
|
-
];
|
|
155
|
-
pushNotes(lines, "Assumptions", data.assumptions, theme);
|
|
156
|
-
pushNotes(lines, "Warnings", data.warnings, theme);
|
|
157
|
-
return lines.join("\n");
|
|
158
|
-
}
|
|
159
|
-
export function formatVarHuman(data, theme = createTerminalTheme(process.stdout)) {
|
|
160
|
-
const lines = [
|
|
161
|
-
theme.label("Portfolio VaR"),
|
|
162
|
-
"",
|
|
163
|
-
formatSourceLabel(data, theme),
|
|
164
|
-
`${theme.label("Lookback:")} ${data.lookback_years}Y`,
|
|
165
|
-
`${theme.label("Confidence:")} ${formatPercentDirect(data.confidence_pct)}`,
|
|
166
|
-
`${theme.label("Horizon:")} ${data.time_horizon}`,
|
|
167
|
-
"",
|
|
168
|
-
`${theme.label("VaR:")} ${formatPercentValue(data.var)}`,
|
|
169
|
-
`${theme.label("Long exposure:")} ${formatPercentDirect(data.long_exposure_pct)}`,
|
|
170
|
-
`${theme.label("Short exposure:")} ${formatPercentDirect(data.short_exposure_pct)}`,
|
|
171
|
-
`${theme.label("Net exposure:")} ${formatPercentDirect(data.total_weight_pct)}`,
|
|
172
|
-
"",
|
|
173
|
-
theme.label("Holdings"),
|
|
174
|
-
...data.holdings.map(item => ` - ${item.ticker}: weight=${formatPercentDirect(item.weight_pct)}`),
|
|
175
|
-
"",
|
|
176
|
-
`${theme.label("Histogram bins:")} ${formatInteger(data.histogram_data.length)}`,
|
|
177
|
-
];
|
|
178
|
-
pushNotes(lines, "Assumptions", data.assumptions, theme);
|
|
179
|
-
pushNotes(lines, "Warnings", data.warnings, theme);
|
|
180
|
-
return lines.join("\n");
|
|
181
|
-
}
|
|
182
|
-
export function parseAssetSpec(rawAsset) {
|
|
183
|
-
const normalized = rawAsset.trim();
|
|
184
|
-
if (!normalized) {
|
|
185
|
-
throw createCliValidationError("Asset input cannot be empty.");
|
|
186
|
-
}
|
|
187
|
-
const parts = normalized.split(":");
|
|
188
|
-
if (parts.length > 2) {
|
|
189
|
-
throw createCliValidationError(`Invalid asset input "${rawAsset}". Use TICKER[:WEIGHT_PCT].`);
|
|
190
|
-
}
|
|
191
|
-
const [rawTicker, rawWeight] = parts;
|
|
192
|
-
const ticker = rawTicker?.trim().toUpperCase();
|
|
193
|
-
if (!ticker) {
|
|
194
|
-
throw createCliValidationError(`Invalid asset input "${rawAsset}". Use TICKER[:WEIGHT_PCT].`);
|
|
195
|
-
}
|
|
196
|
-
if (rawWeight === undefined) {
|
|
197
|
-
return { ticker, weight_pct: null };
|
|
198
|
-
}
|
|
199
|
-
const weight_pct = Number(rawWeight.trim());
|
|
200
|
-
if (!Number.isFinite(weight_pct)) {
|
|
201
|
-
throw createCliValidationError(`Invalid asset weight in "${rawAsset}". Use TICKER[:WEIGHT_PCT].`);
|
|
202
|
-
}
|
|
203
|
-
return { ticker, weight_pct };
|
|
204
|
-
}
|
|
205
|
-
function resolveAnalyticsSource(portfolioId, assets) {
|
|
206
|
-
const hasPortfolio = portfolioId !== undefined;
|
|
207
|
-
const hasAssets = assets.length > 0;
|
|
208
|
-
if (hasPortfolio === hasAssets) {
|
|
209
|
-
throw createCliValidationError("Use exactly one source: --portfolio <id> or one or more --asset TICKER[:WEIGHT_PCT].");
|
|
210
|
-
}
|
|
211
|
-
if (hasPortfolio) {
|
|
212
|
-
return { portfolio_id: portfolioId };
|
|
213
|
-
}
|
|
214
|
-
return {
|
|
215
|
-
assets: assets.map(parseAssetSpec),
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
function collectStringOption(value, previous) {
|
|
219
|
-
previous.push(value);
|
|
220
|
-
return previous;
|
|
221
|
-
}
|
|
222
|
-
function parsePositiveIntegerOption(value) {
|
|
223
|
-
const normalized = value.trim();
|
|
224
|
-
if (!/^\d+$/.test(normalized)) {
|
|
225
|
-
throw createCliValidationError("Value must be a positive integer.");
|
|
226
|
-
}
|
|
227
|
-
const parsed = Number(normalized);
|
|
228
|
-
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
229
|
-
throw createCliValidationError("Value must be a positive integer.");
|
|
230
|
-
}
|
|
231
|
-
return parsed;
|
|
232
|
-
}
|
|
233
|
-
function parseYearsOption(value) {
|
|
234
|
-
const parsed = parsePositiveIntegerOption(value);
|
|
235
|
-
if (![1, 3, 5].includes(parsed)) {
|
|
236
|
-
throw createCliValidationError("Years must be one of: 1, 3, 5.");
|
|
237
|
-
}
|
|
238
|
-
return parsed;
|
|
239
|
-
}
|
|
240
|
-
function parseNumberOption(value, fieldName) {
|
|
241
|
-
const parsed = Number(value.trim());
|
|
242
|
-
if (!Number.isFinite(parsed)) {
|
|
243
|
-
throw createCliValidationError(`${fieldName} must be a number.`);
|
|
244
|
-
}
|
|
245
|
-
return parsed;
|
|
246
|
-
}
|
|
247
|
-
function formatSourceLabel(data, theme) {
|
|
248
|
-
if (data.source_type === "saved_portfolio" && data.portfolio_id !== null) {
|
|
249
|
-
const name = data.portfolio_name ? ` · ${data.portfolio_name}` : "";
|
|
250
|
-
return `${theme.bold(String(data.portfolio_id))}${theme.dim(name)}`;
|
|
251
|
-
}
|
|
252
|
-
return theme.bold("Ad-hoc basket");
|
|
253
|
-
}
|
|
254
|
-
function pushNotes(lines, title, items, theme) {
|
|
255
|
-
if (items.length === 0) {
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
lines.push("");
|
|
259
|
-
lines.push(theme.label(title));
|
|
260
|
-
for (const item of items) {
|
|
261
|
-
lines.push(` - ${item}`);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
function formatNumber(value) {
|
|
265
|
-
return new Intl.NumberFormat("en-US", {
|
|
266
|
-
maximumFractionDigits: 4,
|
|
267
|
-
}).format(value);
|
|
268
|
-
}
|
|
269
|
-
function formatInteger(value) {
|
|
270
|
-
return new Intl.NumberFormat("en-US", {
|
|
271
|
-
maximumFractionDigits: 0,
|
|
272
|
-
}).format(value);
|
|
273
|
-
}
|
|
274
|
-
function formatNullableNumber(value) {
|
|
275
|
-
if (value === null) {
|
|
276
|
-
return "n/a";
|
|
277
|
-
}
|
|
278
|
-
return formatNumber(value);
|
|
279
|
-
}
|
|
280
|
-
function formatPercentValue(value) {
|
|
281
|
-
return `${new Intl.NumberFormat("en-US", {
|
|
282
|
-
maximumFractionDigits: 2,
|
|
283
|
-
minimumFractionDigits: 2,
|
|
284
|
-
}).format(value * 100)}%`;
|
|
285
|
-
}
|
|
286
|
-
function formatPercentDirect(value) {
|
|
287
|
-
return `${new Intl.NumberFormat("en-US", {
|
|
288
|
-
maximumFractionDigits: 2,
|
|
289
|
-
minimumFractionDigits: 2,
|
|
290
|
-
}).format(value)}%`;
|
|
291
|
-
}
|