aipa-cli 0.1.26__tar.gz → 0.1.28__tar.gz
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.
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/CHANGELOG.md +15 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/PKG-INFO +83 -6
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/README.md +81 -4
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/pyproject.toml +1 -1
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/__init__.py +1 -1
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/agents/tools.py +178 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/cli.py +30 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/cli_commands.py +145 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/.gitignore +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/LICENSE +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/__main__.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/actions.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/agents/__init__.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/agents/agent.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/agents/callbacks.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/agents/config.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/agents/personas.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/analyze.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/app.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/bindings.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/chart.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/chat.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/cli_setup.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/deep_research.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/session.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/settings_tab.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/theme.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/ticker_data.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/user_settings.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/utils.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/widgets/__init__.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/widgets/chat_input.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/widgets/safe_rich_log.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/widgets/ticker_select.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/src/aipriceaction_terminal/workflows.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/tests/conftest.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/tests/openrouter_responses.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/tests/test_app.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/tests/test_chat.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/tests/test_integration.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/tests/test_settings_api.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/tests/test_thinking.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/tests/test_tool_call_streaming.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/tests/test_tools.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/tests/test_utils.py +0 -0
- {aipa_cli-0.1.26 → aipa_cli-0.1.28}/tests/test_workflows.py +0 -0
|
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.28] - 2026-05-11
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `--group` sector filter for `aipa performers` and `get_performers` agent tool (e.g. `--group NGAN_HANG`)
|
|
12
|
+
|
|
13
|
+
## [0.1.27] - 2026-05-11
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- `aipa performers` CLI command: rank top/worst performers by price change, volume, value (close × volume), MA scores, or money flow (defaults to VN stocks)
|
|
17
|
+
- `aipa volume-profile TICKER` CLI command: volume-by-price histogram analysis with POC, value area, and statistics
|
|
18
|
+
- `get_performers` and `get_volume_profile` agent tools for AI-powered market analysis
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- Require `aipriceaction>=0.1.13` for performers and volume_profile modules
|
|
22
|
+
|
|
8
23
|
## [0.1.26] - 2026-05-11
|
|
9
24
|
|
|
10
25
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aipa-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.28
|
|
4
4
|
Summary: Terminal TUI for AI-powered ticker analysis
|
|
5
5
|
Project-URL: Homepage, https://github.com/quanhua92/aipriceaction
|
|
6
6
|
Project-URL: Repository, https://github.com/quanhua92/aipriceaction
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
16
|
Classifier: Topic :: Office/Business :: Financial
|
|
17
17
|
Requires-Python: >=3.13
|
|
18
|
-
Requires-Dist: aipriceaction>=0.1.
|
|
18
|
+
Requires-Dist: aipriceaction>=0.1.13
|
|
19
19
|
Requires-Dist: langchain-core
|
|
20
20
|
Requires-Dist: langchain-openai
|
|
21
21
|
Requires-Dist: langgraph
|
|
@@ -134,6 +134,21 @@ aipa ticker-list --source vn --group NGAN_HANG
|
|
|
134
134
|
# Compact output (symbols only)
|
|
135
135
|
aipa ticker-list --source crypto --compact
|
|
136
136
|
|
|
137
|
+
# Top 10 VN performers by price change (default)
|
|
138
|
+
aipa performers
|
|
139
|
+
|
|
140
|
+
# Top 5 by volume
|
|
141
|
+
aipa performers --sort-by volume --limit 5
|
|
142
|
+
|
|
143
|
+
# Top 10 by trading value
|
|
144
|
+
aipa performers --sort-by value --limit 10
|
|
145
|
+
|
|
146
|
+
# Volume profile for VCB today
|
|
147
|
+
aipa volume-profile VCB
|
|
148
|
+
|
|
149
|
+
# Volume profile for BTCUSDT
|
|
150
|
+
aipa volume-profile BTCUSDT --source crypto --bins 30
|
|
151
|
+
|
|
137
152
|
# List saved chat sessions
|
|
138
153
|
aipa resume
|
|
139
154
|
|
|
@@ -175,7 +190,7 @@ aipa analyze VCB --lang en
|
|
|
175
190
|
| `--question TEXT` | Custom analysis question |
|
|
176
191
|
| `--questions` | List available question templates and exit |
|
|
177
192
|
| `--context-only` | Dump raw context without LLM (no API key needed) |
|
|
178
|
-
| `--interval` | Time interval: `1m`, `5m`, `15m`, `30m`, `1h`, `4h`, `1D`, `1W` (default: `1D`) |
|
|
193
|
+
| `--interval` | Time interval: `1m`, `5m`, `15m`, `30m`, `1h`, `4h`, `1D`, `1W`, `2W` (default: `1D`) |
|
|
179
194
|
| `--limit N` | Number of bars (default: 20) |
|
|
180
195
|
| `--source` | Filter by source: `vn` or `crypto` |
|
|
181
196
|
| `--start-date` / `--end-date` | Date range (e.g. `2026-04-01`) |
|
|
@@ -241,7 +256,7 @@ aipa get-ohlcv-data BTCUSDT --interval 1D --limit 30
|
|
|
241
256
|
|
|
242
257
|
| Flag | Description |
|
|
243
258
|
|---|---|
|
|
244
|
-
| `--interval` | Time interval (default: `1D`) |
|
|
259
|
+
| `--interval` | Time interval: `1m`, `5m`, `15m`, `30m`, `1h`, `4h`, `1D`, `1W`, `2W` (default: `1D`) |
|
|
245
260
|
| `--limit N` | Number of bars |
|
|
246
261
|
| `--start-date` / `--end-date` | Date range |
|
|
247
262
|
| `--source` | Filter by source: `vn` or `crypto` |
|
|
@@ -272,7 +287,7 @@ aipa live-data VCB TCB MBB
|
|
|
272
287
|
|---|---|
|
|
273
288
|
| `TICKERS...` | Optional ticker symbols (omit for top N) |
|
|
274
289
|
| `--top N` | Number of top tickers to show (default: 50) |
|
|
275
|
-
| `--interval` | Time interval: `
|
|
290
|
+
| `--interval` | Time interval: `1m`, `5m`, `15m`, `30m`, `1h`, `4h`, `1D`, `1W`, `2W` (default: `1D`) |
|
|
276
291
|
| `--source` | Filter by source: `vn`, `crypto`, `global`, `sjc` |
|
|
277
292
|
|
|
278
293
|
### `aipa ticker-list`
|
|
@@ -299,6 +314,66 @@ aipa ticker-list --source crypto --compact
|
|
|
299
314
|
| `--group` | Filter by group (e.g. `NGAN_HANG`, `CHUNG_KHOAN`, `BAT_DONG_SAN`) |
|
|
300
315
|
| `--compact` | Output symbols only, comma-separated |
|
|
301
316
|
|
|
317
|
+
### `aipa performers`
|
|
318
|
+
|
|
319
|
+
Rank top and worst performers by any metric. Fetches live daily data with MA indicators — no API key needed. Defaults to VN stocks.
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
# Top 10 VN stocks by price change (default)
|
|
323
|
+
aipa performers
|
|
324
|
+
|
|
325
|
+
# Top 5 by volume, ascending
|
|
326
|
+
aipa performers --sort-by volume --direction asc --limit 5
|
|
327
|
+
|
|
328
|
+
# Top 20 by MA50 score
|
|
329
|
+
aipa performers --sort-by ma50_score --limit 20
|
|
330
|
+
|
|
331
|
+
# Crypto performers
|
|
332
|
+
aipa performers --source crypto --limit 5
|
|
333
|
+
|
|
334
|
+
# Banking sector only
|
|
335
|
+
aipa performers --group NGAN_HANG --sort-by value
|
|
336
|
+
|
|
337
|
+
# Securities sector top gainers
|
|
338
|
+
aipa performers --group CHUNG_KHOAN --sort-by close_changed --limit 5
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
| Flag | Description |
|
|
342
|
+
|---|---|
|
|
343
|
+
| `--sort-by` | Metric: `close_changed` (default), `volume`, `value`, `volume_changed`, `ma10_score`, `ma20_score`, `ma50_score`, `ma100_score`, `ma200_score`, `total_money_changed` |
|
|
344
|
+
| `--direction` | `desc` (default) or `asc` |
|
|
345
|
+
| `--limit N` | Entries per list (default: 10) |
|
|
346
|
+
| `--min-volume N` | Min volume filter for VN tickers (default: 10000) |
|
|
347
|
+
| `--source` | Data source: `vn` (default), `crypto`, `global`, `yahoo`, `sjc` |
|
|
348
|
+
| `--group` | Filter by sector (e.g. `NGAN_HANG`, `CHUNG_KHOAN`, `BAT_DONG_SAN`) |
|
|
349
|
+
|
|
350
|
+
### `aipa volume-profile`
|
|
351
|
+
|
|
352
|
+
Volume-by-price histogram analysis from 1-minute data. Shows POC, value area, volume-weighted statistics, and a visual bar chart — no API key needed.
|
|
353
|
+
|
|
354
|
+
```
|
|
355
|
+
# Today's profile for VCB
|
|
356
|
+
aipa volume-profile VCB
|
|
357
|
+
|
|
358
|
+
# Specific date
|
|
359
|
+
aipa volume-profile VCB --date 2026-05-09
|
|
360
|
+
|
|
361
|
+
# Crypto with fewer bins
|
|
362
|
+
aipa volume-profile BTCUSDT --source crypto --bins 30
|
|
363
|
+
|
|
364
|
+
# Date range with custom value area
|
|
365
|
+
aipa volume-profile FPT --start-date 2026-05-05 --end-date 2026-05-09 --value-area-pct 80
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
| Flag | Description |
|
|
369
|
+
|---|---|
|
|
370
|
+
| `TICKER` | Ticker symbol (required) |
|
|
371
|
+
| `--date` | Single date (YYYY-MM-DD), defaults to today |
|
|
372
|
+
| `--start-date` / `--end-date` | Date range |
|
|
373
|
+
| `--source` | Source for tick size logic: `vn` (default), `crypto`, `global`, `yahoo`, `sjc` |
|
|
374
|
+
| `--bins N` | Number of price bins (default: 50, range: 2–200) |
|
|
375
|
+
| `--value-area-pct` | Value area target % (default: 70, range: 60–90) |
|
|
376
|
+
|
|
302
377
|
### `aipa setup`
|
|
303
378
|
|
|
304
379
|
Interactive first-run configuration. Prompts for language, reference ticker, API key, base URL, and model. Settings are saved to `~/.aipriceaction/settings.json`. Re-running shows current values as defaults.
|
|
@@ -336,6 +411,8 @@ Commands that require an API key will auto-run `aipa setup` on first use if not
|
|
|
336
411
|
| `aipa get-ohlcv-data` | No setup needed |
|
|
337
412
|
| `aipa live-data` | No setup needed |
|
|
338
413
|
| `aipa ticker-list` | No setup needed |
|
|
414
|
+
| `aipa performers` | No setup needed |
|
|
415
|
+
| `aipa volume-profile VCB` | No setup needed |
|
|
339
416
|
| `aipa analyze VCB --context-only` | No setup needed |
|
|
340
417
|
| `aipa analyze VCB --questions` | No setup needed |
|
|
341
418
|
| `aipa resume` | No setup needed |
|
|
@@ -350,10 +427,10 @@ Commands that require an API key will auto-run `aipa setup` on first use if not
|
|
|
350
427
|
Launch the TUI with `aipa`. The interface has six tabs:
|
|
351
428
|
|
|
352
429
|
- **Chat** — AI-powered chat with streaming responses, thinking/reasoning display, slash commands, and arrow-key history navigation
|
|
430
|
+
- **Workflows** — Structured analysis forms with question bank dropdown for ticker analysis and deep research
|
|
353
431
|
- **Vietnam** — Browse and search Vietnamese stock tickers
|
|
354
432
|
- **Crypto** — Browse and search cryptocurrency tickers
|
|
355
433
|
- **Global** — Browse and search global/Yahoo tickers
|
|
356
|
-
- **Workflows** — Structured analysis forms with question bank dropdown for ticker analysis and deep research
|
|
357
434
|
- **Settings** — Configure API key, model, base URL, and other preferences
|
|
358
435
|
|
|
359
436
|
### Slash Commands (Chat tab)
|
|
@@ -108,6 +108,21 @@ aipa ticker-list --source vn --group NGAN_HANG
|
|
|
108
108
|
# Compact output (symbols only)
|
|
109
109
|
aipa ticker-list --source crypto --compact
|
|
110
110
|
|
|
111
|
+
# Top 10 VN performers by price change (default)
|
|
112
|
+
aipa performers
|
|
113
|
+
|
|
114
|
+
# Top 5 by volume
|
|
115
|
+
aipa performers --sort-by volume --limit 5
|
|
116
|
+
|
|
117
|
+
# Top 10 by trading value
|
|
118
|
+
aipa performers --sort-by value --limit 10
|
|
119
|
+
|
|
120
|
+
# Volume profile for VCB today
|
|
121
|
+
aipa volume-profile VCB
|
|
122
|
+
|
|
123
|
+
# Volume profile for BTCUSDT
|
|
124
|
+
aipa volume-profile BTCUSDT --source crypto --bins 30
|
|
125
|
+
|
|
111
126
|
# List saved chat sessions
|
|
112
127
|
aipa resume
|
|
113
128
|
|
|
@@ -149,7 +164,7 @@ aipa analyze VCB --lang en
|
|
|
149
164
|
| `--question TEXT` | Custom analysis question |
|
|
150
165
|
| `--questions` | List available question templates and exit |
|
|
151
166
|
| `--context-only` | Dump raw context without LLM (no API key needed) |
|
|
152
|
-
| `--interval` | Time interval: `1m`, `5m`, `15m`, `30m`, `1h`, `4h`, `1D`, `1W` (default: `1D`) |
|
|
167
|
+
| `--interval` | Time interval: `1m`, `5m`, `15m`, `30m`, `1h`, `4h`, `1D`, `1W`, `2W` (default: `1D`) |
|
|
153
168
|
| `--limit N` | Number of bars (default: 20) |
|
|
154
169
|
| `--source` | Filter by source: `vn` or `crypto` |
|
|
155
170
|
| `--start-date` / `--end-date` | Date range (e.g. `2026-04-01`) |
|
|
@@ -215,7 +230,7 @@ aipa get-ohlcv-data BTCUSDT --interval 1D --limit 30
|
|
|
215
230
|
|
|
216
231
|
| Flag | Description |
|
|
217
232
|
|---|---|
|
|
218
|
-
| `--interval` | Time interval (default: `1D`) |
|
|
233
|
+
| `--interval` | Time interval: `1m`, `5m`, `15m`, `30m`, `1h`, `4h`, `1D`, `1W`, `2W` (default: `1D`) |
|
|
219
234
|
| `--limit N` | Number of bars |
|
|
220
235
|
| `--start-date` / `--end-date` | Date range |
|
|
221
236
|
| `--source` | Filter by source: `vn` or `crypto` |
|
|
@@ -246,7 +261,7 @@ aipa live-data VCB TCB MBB
|
|
|
246
261
|
|---|---|
|
|
247
262
|
| `TICKERS...` | Optional ticker symbols (omit for top N) |
|
|
248
263
|
| `--top N` | Number of top tickers to show (default: 50) |
|
|
249
|
-
| `--interval` | Time interval: `
|
|
264
|
+
| `--interval` | Time interval: `1m`, `5m`, `15m`, `30m`, `1h`, `4h`, `1D`, `1W`, `2W` (default: `1D`) |
|
|
250
265
|
| `--source` | Filter by source: `vn`, `crypto`, `global`, `sjc` |
|
|
251
266
|
|
|
252
267
|
### `aipa ticker-list`
|
|
@@ -273,6 +288,66 @@ aipa ticker-list --source crypto --compact
|
|
|
273
288
|
| `--group` | Filter by group (e.g. `NGAN_HANG`, `CHUNG_KHOAN`, `BAT_DONG_SAN`) |
|
|
274
289
|
| `--compact` | Output symbols only, comma-separated |
|
|
275
290
|
|
|
291
|
+
### `aipa performers`
|
|
292
|
+
|
|
293
|
+
Rank top and worst performers by any metric. Fetches live daily data with MA indicators — no API key needed. Defaults to VN stocks.
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
# Top 10 VN stocks by price change (default)
|
|
297
|
+
aipa performers
|
|
298
|
+
|
|
299
|
+
# Top 5 by volume, ascending
|
|
300
|
+
aipa performers --sort-by volume --direction asc --limit 5
|
|
301
|
+
|
|
302
|
+
# Top 20 by MA50 score
|
|
303
|
+
aipa performers --sort-by ma50_score --limit 20
|
|
304
|
+
|
|
305
|
+
# Crypto performers
|
|
306
|
+
aipa performers --source crypto --limit 5
|
|
307
|
+
|
|
308
|
+
# Banking sector only
|
|
309
|
+
aipa performers --group NGAN_HANG --sort-by value
|
|
310
|
+
|
|
311
|
+
# Securities sector top gainers
|
|
312
|
+
aipa performers --group CHUNG_KHOAN --sort-by close_changed --limit 5
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
| Flag | Description |
|
|
316
|
+
|---|---|
|
|
317
|
+
| `--sort-by` | Metric: `close_changed` (default), `volume`, `value`, `volume_changed`, `ma10_score`, `ma20_score`, `ma50_score`, `ma100_score`, `ma200_score`, `total_money_changed` |
|
|
318
|
+
| `--direction` | `desc` (default) or `asc` |
|
|
319
|
+
| `--limit N` | Entries per list (default: 10) |
|
|
320
|
+
| `--min-volume N` | Min volume filter for VN tickers (default: 10000) |
|
|
321
|
+
| `--source` | Data source: `vn` (default), `crypto`, `global`, `yahoo`, `sjc` |
|
|
322
|
+
| `--group` | Filter by sector (e.g. `NGAN_HANG`, `CHUNG_KHOAN`, `BAT_DONG_SAN`) |
|
|
323
|
+
|
|
324
|
+
### `aipa volume-profile`
|
|
325
|
+
|
|
326
|
+
Volume-by-price histogram analysis from 1-minute data. Shows POC, value area, volume-weighted statistics, and a visual bar chart — no API key needed.
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
# Today's profile for VCB
|
|
330
|
+
aipa volume-profile VCB
|
|
331
|
+
|
|
332
|
+
# Specific date
|
|
333
|
+
aipa volume-profile VCB --date 2026-05-09
|
|
334
|
+
|
|
335
|
+
# Crypto with fewer bins
|
|
336
|
+
aipa volume-profile BTCUSDT --source crypto --bins 30
|
|
337
|
+
|
|
338
|
+
# Date range with custom value area
|
|
339
|
+
aipa volume-profile FPT --start-date 2026-05-05 --end-date 2026-05-09 --value-area-pct 80
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
| Flag | Description |
|
|
343
|
+
|---|---|
|
|
344
|
+
| `TICKER` | Ticker symbol (required) |
|
|
345
|
+
| `--date` | Single date (YYYY-MM-DD), defaults to today |
|
|
346
|
+
| `--start-date` / `--end-date` | Date range |
|
|
347
|
+
| `--source` | Source for tick size logic: `vn` (default), `crypto`, `global`, `yahoo`, `sjc` |
|
|
348
|
+
| `--bins N` | Number of price bins (default: 50, range: 2–200) |
|
|
349
|
+
| `--value-area-pct` | Value area target % (default: 70, range: 60–90) |
|
|
350
|
+
|
|
276
351
|
### `aipa setup`
|
|
277
352
|
|
|
278
353
|
Interactive first-run configuration. Prompts for language, reference ticker, API key, base URL, and model. Settings are saved to `~/.aipriceaction/settings.json`. Re-running shows current values as defaults.
|
|
@@ -310,6 +385,8 @@ Commands that require an API key will auto-run `aipa setup` on first use if not
|
|
|
310
385
|
| `aipa get-ohlcv-data` | No setup needed |
|
|
311
386
|
| `aipa live-data` | No setup needed |
|
|
312
387
|
| `aipa ticker-list` | No setup needed |
|
|
388
|
+
| `aipa performers` | No setup needed |
|
|
389
|
+
| `aipa volume-profile VCB` | No setup needed |
|
|
313
390
|
| `aipa analyze VCB --context-only` | No setup needed |
|
|
314
391
|
| `aipa analyze VCB --questions` | No setup needed |
|
|
315
392
|
| `aipa resume` | No setup needed |
|
|
@@ -324,10 +401,10 @@ Commands that require an API key will auto-run `aipa setup` on first use if not
|
|
|
324
401
|
Launch the TUI with `aipa`. The interface has six tabs:
|
|
325
402
|
|
|
326
403
|
- **Chat** — AI-powered chat with streaming responses, thinking/reasoning display, slash commands, and arrow-key history navigation
|
|
404
|
+
- **Workflows** — Structured analysis forms with question bank dropdown for ticker analysis and deep research
|
|
327
405
|
- **Vietnam** — Browse and search Vietnamese stock tickers
|
|
328
406
|
- **Crypto** — Browse and search cryptocurrency tickers
|
|
329
407
|
- **Global** — Browse and search global/Yahoo tickers
|
|
330
|
-
- **Workflows** — Structured analysis forms with question bank dropdown for ticker analysis and deep research
|
|
331
408
|
- **Settings** — Configure API key, model, base URL, and other preferences
|
|
332
409
|
|
|
333
410
|
### Slash Commands (Chat tab)
|
|
@@ -238,10 +238,188 @@ def create_live_data_tool(lang: str = "en") -> ToolDef:
|
|
|
238
238
|
)
|
|
239
239
|
|
|
240
240
|
|
|
241
|
+
def create_performers_tool(lang: str = "en") -> ToolDef:
|
|
242
|
+
"""Factory: creates the get_performers tool."""
|
|
243
|
+
|
|
244
|
+
@tool
|
|
245
|
+
def get_performers(
|
|
246
|
+
sort_by: str = "close_changed",
|
|
247
|
+
direction: str = "desc",
|
|
248
|
+
limit: int = 10,
|
|
249
|
+
source: str | None = None,
|
|
250
|
+
group: str | None = None,
|
|
251
|
+
) -> str:
|
|
252
|
+
"""Rank top and worst performers from live daily data by a chosen metric.
|
|
253
|
+
|
|
254
|
+
Returns two ranked lists (top and worst) of performers. Useful for
|
|
255
|
+
identifying market leaders, laggards, and sector trends.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
sort_by: Metric to rank by — "close_changed" (default), "volume",
|
|
259
|
+
"value" (close × volume), "volume_changed",
|
|
260
|
+
"ma10_score", "ma20_score", "ma50_score",
|
|
261
|
+
"ma100_score", "ma200_score", "total_money_changed".
|
|
262
|
+
direction: Sort direction — "desc" (default, strongest first in top)
|
|
263
|
+
or "asc" (weakest first in top).
|
|
264
|
+
limit: Number of entries per list (default 10, max 100).
|
|
265
|
+
source: Filter by source — "vn" (default), "crypto", "yahoo". None = vn.
|
|
266
|
+
group: Filter by group/sector — e.g. "NGAN_HANG", "CHUNG_KHOAN",
|
|
267
|
+
"BAT_DONG_SAN", "CONG_NGHE", "DAU_KHI". None = all sectors.
|
|
268
|
+
"""
|
|
269
|
+
from aipriceaction.performers import build_performers
|
|
270
|
+
|
|
271
|
+
client, _ = _ensure_clients(lang)
|
|
272
|
+
try:
|
|
273
|
+
data = client.fetch_live_data("1D", ma=True)
|
|
274
|
+
except Exception as e:
|
|
275
|
+
return f"Error fetching live data: {e}"
|
|
276
|
+
if not data:
|
|
277
|
+
return "No live data available."
|
|
278
|
+
|
|
279
|
+
sector_map: dict[str, str] = {}
|
|
280
|
+
if source:
|
|
281
|
+
tickers_meta = client.get_tickers(source=source)
|
|
282
|
+
sector_map = {t.ticker: t.group for t in tickers_meta if t.group}
|
|
283
|
+
source_symbols = {t.ticker for t in tickers_meta}
|
|
284
|
+
data = {k: v for k, v in data.items() if k in source_symbols}
|
|
285
|
+
|
|
286
|
+
# Filter by group/sector if specified
|
|
287
|
+
if group:
|
|
288
|
+
group_upper = group.upper()
|
|
289
|
+
data = {k: v for k, v in data.items() if sector_map.get(k, "").upper() == group_upper}
|
|
290
|
+
|
|
291
|
+
top, worst = build_performers(
|
|
292
|
+
data, sector_map,
|
|
293
|
+
sort_by=sort_by,
|
|
294
|
+
direction=direction,
|
|
295
|
+
limit=limit,
|
|
296
|
+
source=source,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
lines = [f"Top {len(top)} performers (by {sort_by}, {direction}):"]
|
|
300
|
+
for i, p in enumerate(top, 1):
|
|
301
|
+
chg = f"{p.close_changed:+.2f}%" if p.close_changed is not None else "N/A"
|
|
302
|
+
sector = f" [{p.sector}]" if p.sector else ""
|
|
303
|
+
lines.append(f" {i}. {p.symbol}: close={p.close:.2f} change={chg} vol={p.volume:,} value={p.value:,.0f}{sector}")
|
|
304
|
+
|
|
305
|
+
lines.append(f"\nWorst {len(worst)} performers (by {sort_by}):")
|
|
306
|
+
for i, p in enumerate(worst, 1):
|
|
307
|
+
chg = f"{p.close_changed:+.2f}%" if p.close_changed is not None else "N/A"
|
|
308
|
+
sector = f" [{p.sector}]" if p.sector else ""
|
|
309
|
+
lines.append(f" {i}. {p.symbol}: close={p.close:.2f} change={chg} vol={p.volume:,} value={p.value:,.0f}{sector}")
|
|
310
|
+
|
|
311
|
+
return "\n".join(lines)
|
|
312
|
+
|
|
313
|
+
return ToolDef(
|
|
314
|
+
tool=get_performers,
|
|
315
|
+
name="get_performers",
|
|
316
|
+
description="Rank top and worst performers by price change, volume, or MA scores.",
|
|
317
|
+
category="market_data",
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def create_volume_profile_tool(lang: str = "en") -> ToolDef:
|
|
322
|
+
"""Factory: creates the get_volume_profile tool."""
|
|
323
|
+
|
|
324
|
+
@tool
|
|
325
|
+
def get_volume_profile(
|
|
326
|
+
ticker: str,
|
|
327
|
+
date: str | None = None,
|
|
328
|
+
start_date: str | None = None,
|
|
329
|
+
end_date: str | None = None,
|
|
330
|
+
bins: int = 50,
|
|
331
|
+
value_area_pct: float = 70.0,
|
|
332
|
+
) -> str:
|
|
333
|
+
"""Compute volume-by-price histogram for a ticker using 1-minute data.
|
|
334
|
+
|
|
335
|
+
Returns the Point of Control (POC), Value Area, volume-weighted statistics,
|
|
336
|
+
and the binned profile. Useful for identifying key support/resistance levels
|
|
337
|
+
based on where the most volume traded.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
ticker: Ticker symbol (e.g. "VCB", "BTCUSDT").
|
|
341
|
+
date: Single date in YYYY-MM-DD format. Defaults to today.
|
|
342
|
+
start_date: Start date (YYYY-MM-DD). Alternative to --date.
|
|
343
|
+
end_date: End date (YYYY-MM-DD). Defaults to start_date.
|
|
344
|
+
bins: Number of price bins (default 50, range 2-200).
|
|
345
|
+
value_area_pct: Value area target percentage (default 70, range 60-90).
|
|
346
|
+
"""
|
|
347
|
+
from datetime import date as date_type
|
|
348
|
+
|
|
349
|
+
from aipriceaction.volume_profile import compute_volume_profile
|
|
350
|
+
|
|
351
|
+
client, _ = _ensure_clients(lang)
|
|
352
|
+
ticker = ticker.upper()
|
|
353
|
+
|
|
354
|
+
# Resolve date range
|
|
355
|
+
if date:
|
|
356
|
+
sd = ed = date
|
|
357
|
+
elif start_date:
|
|
358
|
+
sd = start_date
|
|
359
|
+
ed = end_date or start_date
|
|
360
|
+
else:
|
|
361
|
+
today = date_type.today().isoformat()
|
|
362
|
+
sd = ed = today
|
|
363
|
+
|
|
364
|
+
# Resolve source for tick size
|
|
365
|
+
source = "vn"
|
|
366
|
+
try:
|
|
367
|
+
tickers_meta = client.get_tickers()
|
|
368
|
+
for t in tickers_meta:
|
|
369
|
+
if t.ticker == ticker:
|
|
370
|
+
source = t.source or "vn"
|
|
371
|
+
break
|
|
372
|
+
except Exception:
|
|
373
|
+
pass
|
|
374
|
+
|
|
375
|
+
try:
|
|
376
|
+
df = client.get_ohlcv(ticker, interval="1m", start_date=sd, end_date=ed, ma=False)
|
|
377
|
+
except Exception as e:
|
|
378
|
+
return f"Error fetching 1m data for {ticker}: {e}"
|
|
379
|
+
|
|
380
|
+
if df is None or df.empty:
|
|
381
|
+
return f"No 1m data found for {ticker} on {sd}."
|
|
382
|
+
|
|
383
|
+
result = compute_volume_profile(df, ticker, source=source, bins=bins, value_area_pct=value_area_pct)
|
|
384
|
+
|
|
385
|
+
lines = [
|
|
386
|
+
f"Volume Profile: {result.symbol} ({sd})",
|
|
387
|
+
f"Volume: {result.total_volume:,} Minutes: {result.total_minutes}",
|
|
388
|
+
f"Range: {result.price_range.low:.2f} - {result.price_range.high:.2f}",
|
|
389
|
+
f"POC: {result.poc.price:.2f} ({result.poc.percentage:.1f}%)",
|
|
390
|
+
f"Value Area: {result.value_area.low:.2f} - {result.value_area.high:.2f} ({result.value_area.percentage:.1f}%)",
|
|
391
|
+
]
|
|
392
|
+
|
|
393
|
+
if result.statistics:
|
|
394
|
+
s = result.statistics
|
|
395
|
+
lines.append(f"Mean: {s.mean_price:.2f} Median: {s.median_price:.2f} "
|
|
396
|
+
f"StdDev: {s.std_deviation:.2f} Skew: {s.skewness:.4f}")
|
|
397
|
+
|
|
398
|
+
if result.profile:
|
|
399
|
+
lines.append(f"\nProfile ({len(result.profile)} bins):")
|
|
400
|
+
max_vol = max(p.volume for p in result.profile)
|
|
401
|
+
for level in result.profile:
|
|
402
|
+
bar_len = int(level.volume / max_vol * 25) if max_vol > 0 else 0
|
|
403
|
+
bar = "\u2588" * bar_len
|
|
404
|
+
lines.append(f" {level.price:>10.2f} vol={level.volume:>10,.0f} "
|
|
405
|
+
f"{level.percentage:>5.1f}% {bar}")
|
|
406
|
+
|
|
407
|
+
return "\n".join(lines)
|
|
408
|
+
|
|
409
|
+
return ToolDef(
|
|
410
|
+
tool=get_volume_profile,
|
|
411
|
+
name="get_volume_profile",
|
|
412
|
+
description="Volume-by-price histogram with POC, value area, and statistics.",
|
|
413
|
+
category="market_data",
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
|
|
241
417
|
def get_default_tools(lang: str = "en") -> ToolRegistry:
|
|
242
418
|
"""Return a ToolRegistry pre-loaded with the built-in market data tools."""
|
|
243
419
|
registry = ToolRegistry()
|
|
244
420
|
registry.register(create_ohlcv_tool(lang))
|
|
245
421
|
registry.register(create_ticker_list_tool(lang))
|
|
246
422
|
registry.register(create_live_data_tool(lang))
|
|
423
|
+
registry.register(create_performers_tool(lang))
|
|
424
|
+
registry.register(create_volume_profile_tool(lang))
|
|
247
425
|
return registry
|
|
@@ -74,6 +74,30 @@ def run():
|
|
|
74
74
|
p_tlist.add_argument("--group", default=None, help="Filter by group (e.g. NGAN_HANG, CHUNG_KHOAN)")
|
|
75
75
|
p_tlist.add_argument("--compact", action="store_true", help="Output symbols only, comma-separated")
|
|
76
76
|
|
|
77
|
+
# aipa performers [--sort-by close_changed] [--direction desc] [--limit 10]
|
|
78
|
+
# [--min-volume 10000] [--source vn] [--group NGAN_HANG]
|
|
79
|
+
p_perf = sub.add_parser("performers", help="Top/worst performers ranked by a chosen metric")
|
|
80
|
+
p_perf.add_argument("--sort-by", default="close_changed",
|
|
81
|
+
choices=["close_changed", "volume", "value", "volume_changed",
|
|
82
|
+
"ma10_score", "ma20_score", "ma50_score", "ma100_score", "ma200_score",
|
|
83
|
+
"total_money_changed"])
|
|
84
|
+
p_perf.add_argument("--direction", default="desc", choices=["desc", "asc"])
|
|
85
|
+
p_perf.add_argument("--limit", type=int, default=10)
|
|
86
|
+
p_perf.add_argument("--min-volume", type=int, default=10000)
|
|
87
|
+
p_perf.add_argument("--source", default="vn", choices=["vn", "crypto", "global", "yahoo", "sjc"])
|
|
88
|
+
p_perf.add_argument("--group", default=None, help="Filter by group/sector (e.g. NGAN_HANG, CHUNG_KHOAN, BAT_DONG_SAN)")
|
|
89
|
+
|
|
90
|
+
# aipa volume-profile TICKER [--date YYYY-MM-DD] [--start-date] [--end-date]
|
|
91
|
+
# [--source vn] [--bins 50] [--value-area-pct 70]
|
|
92
|
+
p_vp = sub.add_parser("volume-profile", help="Volume-by-price histogram analysis")
|
|
93
|
+
p_vp.add_argument("ticker", help="Ticker symbol")
|
|
94
|
+
p_vp.add_argument("--date", default=None, help="Single date (YYYY-MM-DD)")
|
|
95
|
+
p_vp.add_argument("--start-date", default=None, help="Start date (YYYY-MM-DD)")
|
|
96
|
+
p_vp.add_argument("--end-date", default=None, help="End date (YYYY-MM-DD)")
|
|
97
|
+
p_vp.add_argument("--source", default=None, choices=["vn", "crypto", "global", "yahoo", "sjc"])
|
|
98
|
+
p_vp.add_argument("--bins", type=int, default=50, help="Number of price bins (2-200)")
|
|
99
|
+
p_vp.add_argument("--value-area-pct", type=float, default=70.0, help="Value area target percentage (60-90)")
|
|
100
|
+
|
|
77
101
|
# aipa setup
|
|
78
102
|
sub.add_parser("setup", help="Interactive first-run setup")
|
|
79
103
|
|
|
@@ -100,6 +124,12 @@ def run():
|
|
|
100
124
|
elif args.command == "ticker-list":
|
|
101
125
|
from .cli_commands import cmd_ticker_list
|
|
102
126
|
cmd_ticker_list(args)
|
|
127
|
+
elif args.command == "performers":
|
|
128
|
+
from .cli_commands import cmd_performers
|
|
129
|
+
cmd_performers(args)
|
|
130
|
+
elif args.command == "volume-profile":
|
|
131
|
+
from .cli_commands import cmd_volume_profile
|
|
132
|
+
cmd_volume_profile(args)
|
|
103
133
|
elif args.command == "deep-research":
|
|
104
134
|
if getattr(args, "run", False):
|
|
105
135
|
_ensure_setup()
|
|
@@ -396,3 +396,148 @@ def _resolve_cli_question(builder, args) -> str:
|
|
|
396
396
|
return templates[0]["question"]
|
|
397
397
|
|
|
398
398
|
return ""
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def cmd_performers(args) -> None:
|
|
402
|
+
"""CLI handler: aipa performers."""
|
|
403
|
+
from aipriceaction import AIPriceAction
|
|
404
|
+
from aipriceaction.performers import build_performers
|
|
405
|
+
|
|
406
|
+
client = AIPriceAction()
|
|
407
|
+
source = _resolve_source(args.source)
|
|
408
|
+
|
|
409
|
+
# Fetch live 1D data with MA indicators
|
|
410
|
+
data = client.fetch_live_data("1D", ma=True)
|
|
411
|
+
if not data:
|
|
412
|
+
print("No live data available.", file=sys.stderr)
|
|
413
|
+
sys.exit(1)
|
|
414
|
+
|
|
415
|
+
# Build sector map from ticker metadata
|
|
416
|
+
sector_map: dict[str, str] = {}
|
|
417
|
+
if source:
|
|
418
|
+
tickers_meta = client.get_tickers(source=source)
|
|
419
|
+
sector_map = {t.ticker: t.group for t in tickers_meta if t.group}
|
|
420
|
+
# Filter live data to source tickers
|
|
421
|
+
source_symbols = {t.ticker for t in tickers_meta}
|
|
422
|
+
data = {k: v for k, v in data.items() if k in source_symbols}
|
|
423
|
+
|
|
424
|
+
# Filter by group/sector if specified
|
|
425
|
+
if args.group:
|
|
426
|
+
group_upper = args.group.upper()
|
|
427
|
+
data = {k: v for k, v in data.items() if sector_map.get(k, "").upper() == group_upper}
|
|
428
|
+
|
|
429
|
+
top, worst = build_performers(
|
|
430
|
+
data, sector_map,
|
|
431
|
+
sort_by=args.sort_by,
|
|
432
|
+
direction=args.direction,
|
|
433
|
+
limit=args.limit,
|
|
434
|
+
min_volume=args.min_volume,
|
|
435
|
+
source=source,
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
import pandas as pd
|
|
439
|
+
|
|
440
|
+
if top:
|
|
441
|
+
print(f"\n=== Top {len(top)} Performers (by {args.sort_by}, {args.direction}) ===")
|
|
442
|
+
df = pd.DataFrame([_performer_to_dict(p) for p in top])
|
|
443
|
+
print(df.to_string(index=False))
|
|
444
|
+
|
|
445
|
+
if worst:
|
|
446
|
+
print(f"\n=== Worst {len(worst)} Performers (by {args.sort_by}, {args.direction}) ===")
|
|
447
|
+
df = pd.DataFrame([_performer_to_dict(p) for p in worst])
|
|
448
|
+
print(df.to_string(index=False))
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _performer_to_dict(p) -> dict:
|
|
452
|
+
"""Convert PerformerInfo to a flat dict for display."""
|
|
453
|
+
return {
|
|
454
|
+
"ticker": p.symbol,
|
|
455
|
+
"close": p.close,
|
|
456
|
+
"volume": p.volume,
|
|
457
|
+
"value": f"{p.value:,.0f}",
|
|
458
|
+
"close_changed": f"{p.close_changed:+.2f}" if p.close_changed is not None else "",
|
|
459
|
+
"volume_changed": f"{p.volume_changed:+.2f}" if p.volume_changed is not None else "",
|
|
460
|
+
"ma10_score": f"{p.ma10_score:.1f}" if p.ma10_score is not None else "",
|
|
461
|
+
"ma50_score": f"{p.ma50_score:.1f}" if p.ma50_score is not None else "",
|
|
462
|
+
"ma200_score": f"{p.ma200_score:.1f}" if p.ma200_score is not None else "",
|
|
463
|
+
"sector": p.sector or "",
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def cmd_volume_profile(args) -> None:
|
|
468
|
+
"""CLI handler: aipa volume-profile."""
|
|
469
|
+
from datetime import date
|
|
470
|
+
|
|
471
|
+
from aipriceaction import AIPriceAction
|
|
472
|
+
from aipriceaction.volume_profile import compute_volume_profile
|
|
473
|
+
|
|
474
|
+
client = AIPriceAction()
|
|
475
|
+
ticker = args.ticker.upper()
|
|
476
|
+
|
|
477
|
+
# Resolve date range
|
|
478
|
+
if args.date:
|
|
479
|
+
start_date = end_date = args.date
|
|
480
|
+
elif args.start_date:
|
|
481
|
+
start_date = args.start_date
|
|
482
|
+
end_date = args.end_date or args.start_date
|
|
483
|
+
else:
|
|
484
|
+
today = date.today().isoformat()
|
|
485
|
+
start_date = end_date = today
|
|
486
|
+
|
|
487
|
+
# Resolve source for tick size
|
|
488
|
+
source = _resolve_source(args.source) or "vn"
|
|
489
|
+
# Auto-detect from ticker metadata if not specified
|
|
490
|
+
if args.source is None:
|
|
491
|
+
try:
|
|
492
|
+
tickers_meta = client.get_tickers()
|
|
493
|
+
for t in tickers_meta:
|
|
494
|
+
if t.ticker == ticker:
|
|
495
|
+
source = t.source or "vn"
|
|
496
|
+
break
|
|
497
|
+
except Exception:
|
|
498
|
+
pass
|
|
499
|
+
|
|
500
|
+
# Fetch 1m data
|
|
501
|
+
df = client.get_ohlcv(
|
|
502
|
+
ticker,
|
|
503
|
+
interval="1m",
|
|
504
|
+
start_date=start_date,
|
|
505
|
+
end_date=end_date,
|
|
506
|
+
ma=False,
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
if df is None or df.empty:
|
|
510
|
+
print(f"No 1m data found for {ticker} on {start_date}.", file=sys.stderr)
|
|
511
|
+
sys.exit(1)
|
|
512
|
+
|
|
513
|
+
result = compute_volume_profile(
|
|
514
|
+
df, ticker,
|
|
515
|
+
source=source,
|
|
516
|
+
bins=args.bins,
|
|
517
|
+
value_area_pct=args.value_area_pct,
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
print(f"\n=== Volume Profile: {result.symbol} ({start_date}) ===")
|
|
521
|
+
print(f"Total Volume: {result.total_volume:,} | Minutes: {result.total_minutes}")
|
|
522
|
+
print(f"Price Range: {result.price_range.low:.2f} - {result.price_range.high:.2f} "
|
|
523
|
+
f"(spread: {result.price_range.spread:.2f})")
|
|
524
|
+
print(f"\nPOC: {result.poc.price:.2f} volume={result.poc.volume:,.0f} "
|
|
525
|
+
f"({result.poc.percentage:.1f}%)")
|
|
526
|
+
print(f"Value Area: {result.value_area.low:.2f} - {result.value_area.high:.2f} "
|
|
527
|
+
f"volume={result.value_area.volume:,.0f} ({result.value_area.percentage:.1f}%)")
|
|
528
|
+
|
|
529
|
+
if result.statistics:
|
|
530
|
+
s = result.statistics
|
|
531
|
+
print("\nStatistics:")
|
|
532
|
+
print(f" Mean: {s.mean_price:.2f} Median: {s.median_price:.2f} "
|
|
533
|
+
f"StdDev: {s.std_deviation:.2f} Skewness: {s.skewness:.4f}")
|
|
534
|
+
|
|
535
|
+
if result.profile:
|
|
536
|
+
print(f"\n{'Price':>12} {'Volume':>12} {'%':>6} {'Cum%':>6} Bar")
|
|
537
|
+
print("-" * 60)
|
|
538
|
+
max_vol = max(p.volume for p in result.profile) if result.profile else 1.0
|
|
539
|
+
for level in result.profile:
|
|
540
|
+
bar_len = int(level.volume / max_vol * 30) if max_vol > 0 else 0
|
|
541
|
+
bar = "\u2588" * bar_len
|
|
542
|
+
print(f"{level.price:>12.2f} {level.volume:>12,.0f} {level.percentage:>5.1f}% "
|
|
543
|
+
f"{level.cumulative_percentage:>5.1f}% {bar}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|