aipa-cli 0.1.43__tar.gz → 0.1.45__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.43 → aipa_cli-0.1.45}/CHANGELOG.md +19 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/PKG-INFO +2 -2
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/pyproject.toml +1 -1
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/__init__.py +1 -1
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/cli.py +5 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/cli_commands.py +37 -5
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/.gitignore +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/LICENSE +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/README.md +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/__main__.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/actions.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/agents/__init__.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/agents/agent.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/agents/callbacks.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/agents/config.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/agents/personas.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/agents/tools.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/analyze.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/app.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/bindings.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/chart.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/chat.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/cli_setup.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/deep_research.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/predefined_watchlists.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/session.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/settings_tab.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/theme.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/ticker_data.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/user_settings.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/user_watchlist.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/utils.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/verbose.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/widgets/__init__.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/widgets/chat_input.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/widgets/safe_rich_log.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/widgets/ticker_select.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/src/aipriceaction_terminal/workflows.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/tests/conftest.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/tests/openrouter_responses.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/tests/test_app.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/tests/test_chat.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/tests/test_integration.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/tests/test_settings_api.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/tests/test_thinking.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/tests/test_tool_call_streaming.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/tests/test_tools.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/tests/test_utils.py +0 -0
- {aipa_cli-0.1.43 → aipa_cli-0.1.45}/tests/test_workflows.py +0 -0
|
@@ -5,6 +5,25 @@ 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.45] - 2026-05-31
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `--period` flag to `fundamentals ratios` for quarter-specific queries (e.g. `"2024 Q2"`)
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Updated all skill docs: added "report"/"báo cáo" fundamentals hint, added `--period` docs for `ratios`
|
|
15
|
+
|
|
16
|
+
## [0.1.44] - 2026-05-31
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- `--year` flag to `fundamentals rank` and `fundamentals screen` for historical year filtering
|
|
20
|
+
- `--period` flag to `fundamentals rank` and `fundamentals screen` for quarter-specific queries (e.g. `"2024 Q2"`)
|
|
21
|
+
- Reporting period (e.g. `period=2025`, `period=2026 Q1`) in headers of `fundamentals ratios`, `rank`, and `screen`
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Updated docs (AGENTS.md, skill files) with new flags and version gate >=0.1.44
|
|
25
|
+
- Bump SDK dependency to >=0.1.23
|
|
26
|
+
|
|
8
27
|
## [0.1.43] - 2026-05-31
|
|
9
28
|
|
|
10
29
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aipa-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.45
|
|
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.23
|
|
19
19
|
Requires-Dist: langchain-core
|
|
20
20
|
Requires-Dist: langchain-openai
|
|
21
21
|
Requires-Dist: langgraph
|
|
@@ -148,6 +148,7 @@ def run():
|
|
|
148
148
|
p_fund_ratios.add_argument("--latest", action="store_true", help="Show only latest period (quarterly or yearly)")
|
|
149
149
|
p_fund_ratios.add_argument("--yearly", action="store_true", help="Show only yearly reports")
|
|
150
150
|
p_fund_ratios.add_argument("--year", type=int, default=None, help="Show specific year")
|
|
151
|
+
p_fund_ratios.add_argument("--period", type=str, default=None, help="Specific period like '2024' or '2024 Q2'")
|
|
151
152
|
p_fund_ratios.add_argument("--no-yearly", action="store_true", help="Include quarterly reports (default, same as omitting the flag)")
|
|
152
153
|
p_fund_ratios.add_argument("--category", default=None,
|
|
153
154
|
choices=["valuation", "profitability", "leverage", "liquidity", "bank", "efficiency"],
|
|
@@ -157,6 +158,8 @@ def run():
|
|
|
157
158
|
p_fund_rank = fund_sub.add_parser("rank", help="Rank tickers by a fundamental field")
|
|
158
159
|
p_fund_rank.add_argument("--latest", action="store_true", help="Use latest period (quarterly or yearly) instead of yearly only")
|
|
159
160
|
p_fund_rank.add_argument("--yearly", action="store_true", help="Use yearly reports only (default)")
|
|
161
|
+
p_fund_rank.add_argument("--year", type=int, default=None, help="Specific year (e.g. 2024)")
|
|
162
|
+
p_fund_rank.add_argument("--period", type=str, default=None, help="Specific period like '2024' or '2024 Q2'")
|
|
160
163
|
p_fund_rank.add_argument("--sort-by", default="roe", choices=_fund_sort_choices)
|
|
161
164
|
p_fund_rank.add_argument("--direction", default="desc", choices=["desc", "asc"])
|
|
162
165
|
p_fund_rank.add_argument("--limit", type=int, default=10)
|
|
@@ -167,6 +170,8 @@ def run():
|
|
|
167
170
|
p_fund_screen = fund_sub.add_parser("screen", help="Screen tickers by fundamental criteria")
|
|
168
171
|
p_fund_screen.add_argument("--latest", action="store_true", help="Use latest period (quarterly or yearly) instead of yearly only")
|
|
169
172
|
p_fund_screen.add_argument("--yearly", action="store_true", help="Use yearly reports only (default)")
|
|
173
|
+
p_fund_screen.add_argument("--year", type=int, default=None, help="Specific year (e.g. 2024)")
|
|
174
|
+
p_fund_screen.add_argument("--period", type=str, default=None, help="Specific period like '2024' or '2024 Q2'")
|
|
170
175
|
p_fund_screen.add_argument("tickers", nargs="*", default=[], help="Ticker symbols (default: all VN)")
|
|
171
176
|
p_fund_screen.add_argument("--watchlist", default=None, help="Use watchlist as ticker source")
|
|
172
177
|
p_fund_screen.add_argument("--source", default=None)
|
|
@@ -793,7 +793,14 @@ def _fund_ratios(args) -> None:
|
|
|
793
793
|
return
|
|
794
794
|
|
|
795
795
|
entries = fr.ratios
|
|
796
|
-
if args.
|
|
796
|
+
if args.period is not None:
|
|
797
|
+
from aipriceaction.fundamental_ranking import _parse_period
|
|
798
|
+
p_year, p_q = _parse_period(args.period)
|
|
799
|
+
if p_q is not None:
|
|
800
|
+
entries = [r for r in entries if r.year_report == p_year and r.length_report == p_q]
|
|
801
|
+
else:
|
|
802
|
+
entries = [r for r in entries if r.year_report == p_year]
|
|
803
|
+
elif args.year is not None:
|
|
797
804
|
entries = [r for r in entries if r.year_report == args.year]
|
|
798
805
|
elif args.latest:
|
|
799
806
|
entries = entries[:1]
|
|
@@ -811,8 +818,7 @@ def _fund_ratios(args) -> None:
|
|
|
811
818
|
categories = [args.category] if args.category else list(_CATEGORY_FIELDS.keys())
|
|
812
819
|
|
|
813
820
|
for entry in entries:
|
|
814
|
-
|
|
815
|
-
print(f"\n=== {ticker} Financial Ratios (year={entry.year_report}, {period_type}) ===")
|
|
821
|
+
print(f"\n=== {ticker} Financial Ratios (period={_period_str(entry)}) ===")
|
|
816
822
|
|
|
817
823
|
for cat_name in categories:
|
|
818
824
|
fields = _CATEGORY_FIELDS[cat_name]
|
|
@@ -827,6 +833,16 @@ def _fund_ratios(args) -> None:
|
|
|
827
833
|
print(f"\n Total entries: {fr.count} | Showing: {len(entries)}")
|
|
828
834
|
|
|
829
835
|
|
|
836
|
+
def _period_str(entry) -> str:
|
|
837
|
+
if entry is None:
|
|
838
|
+
return "N/A"
|
|
839
|
+
length = entry.length_report
|
|
840
|
+
year = entry.year_report
|
|
841
|
+
if length in (5, 12) or entry.ratio_type == "RATIO_YEAR":
|
|
842
|
+
return str(year)
|
|
843
|
+
return f"{year} Q{length}"
|
|
844
|
+
|
|
845
|
+
|
|
830
846
|
def _fund_rank(args) -> None:
|
|
831
847
|
import pandas as pd
|
|
832
848
|
|
|
@@ -847,6 +863,8 @@ def _fund_rank(args) -> None:
|
|
|
847
863
|
limit=args.limit,
|
|
848
864
|
source=_resolve_source(args.source),
|
|
849
865
|
yearly_only=not args.latest,
|
|
866
|
+
year=args.year,
|
|
867
|
+
period=args.period,
|
|
850
868
|
)
|
|
851
869
|
|
|
852
870
|
if not entries:
|
|
@@ -874,8 +892,14 @@ def _fund_rank(args) -> None:
|
|
|
874
892
|
"industry": (e.industry or "")[:25],
|
|
875
893
|
})
|
|
876
894
|
|
|
895
|
+
if args.period:
|
|
896
|
+
period = args.period
|
|
897
|
+
elif entries:
|
|
898
|
+
period = _period_str(entries[0].latest_ratio)
|
|
899
|
+
else:
|
|
900
|
+
period = "latest" if args.latest else "yearly"
|
|
877
901
|
df = pd.DataFrame(rows)
|
|
878
|
-
print(f"\n=== Top {len(entries)} by {sort_field} ({args.direction}) ===")
|
|
902
|
+
print(f"\n=== Top {len(entries)} by {sort_field} ({args.direction}, period={period}) ===")
|
|
879
903
|
print(df.to_string(index=False))
|
|
880
904
|
|
|
881
905
|
|
|
@@ -899,6 +923,8 @@ def _fund_screen(args) -> None:
|
|
|
899
923
|
limit=args.limit,
|
|
900
924
|
source=_resolve_source(args.source),
|
|
901
925
|
yearly_only=not args.latest,
|
|
926
|
+
year=args.year,
|
|
927
|
+
period=args.period,
|
|
902
928
|
pe_min=args.pe_min, pe_max=args.pe_max,
|
|
903
929
|
pb_min=args.pb_min, pb_max=args.pb_max,
|
|
904
930
|
roe_min=args.roe_min, roe_max=args.roe_max,
|
|
@@ -949,8 +975,14 @@ def _fund_screen(args) -> None:
|
|
|
949
975
|
filters.append(f"{attr_name}={v}")
|
|
950
976
|
filter_desc = ", ".join(filters) if filters else "no filters"
|
|
951
977
|
|
|
978
|
+
if args.period:
|
|
979
|
+
period = args.period
|
|
980
|
+
elif entries:
|
|
981
|
+
period = _period_str(entries[0].latest_ratio)
|
|
982
|
+
else:
|
|
983
|
+
period = "latest" if args.latest else "yearly"
|
|
952
984
|
df = pd.DataFrame(rows)
|
|
953
|
-
print(f"\n=== Screened: {filter_desc} ({len(entries)} match, by {sort_field} {args.direction}) ===")
|
|
985
|
+
print(f"\n=== Screened: {filter_desc} ({len(entries)} match, by {sort_field} {args.direction}, period={period}) ===")
|
|
954
986
|
print(df.to_string(index=False))
|
|
955
987
|
|
|
956
988
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|