fund-cli 2.0.0__py3-none-any.whl

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.
Files changed (66) hide show
  1. fund_cli/__init__.py +13 -0
  2. fund_cli/__main__.py +10 -0
  3. fund_cli/ai/__init__.py +21 -0
  4. fund_cli/ai/analyzer.py +360 -0
  5. fund_cli/ai/prompts.py +244 -0
  6. fund_cli/ai/providers.py +286 -0
  7. fund_cli/analysis/__init__.py +17 -0
  8. fund_cli/analysis/attribution.py +161 -0
  9. fund_cli/analysis/backtest.py +75 -0
  10. fund_cli/analysis/holding.py +217 -0
  11. fund_cli/analysis/manager.py +133 -0
  12. fund_cli/analysis/performance.py +440 -0
  13. fund_cli/analysis/portfolio.py +152 -0
  14. fund_cli/analysis/risk.py +300 -0
  15. fund_cli/cli.py +98 -0
  16. fund_cli/commands/__init__.py +9 -0
  17. fund_cli/commands/ai_cmd.py +464 -0
  18. fund_cli/commands/analyze_cmd.py +418 -0
  19. fund_cli/commands/compare_cmd.py +264 -0
  20. fund_cli/commands/config_cmd.py +97 -0
  21. fund_cli/commands/data_cmd.py +106 -0
  22. fund_cli/commands/filter_cmd.py +286 -0
  23. fund_cli/commands/holding_cmd.py +140 -0
  24. fund_cli/commands/interactive_cmd.py +84 -0
  25. fund_cli/commands/main.py +17 -0
  26. fund_cli/commands/manager_cmd.py +74 -0
  27. fund_cli/commands/monitor_cmd.py +113 -0
  28. fund_cli/commands/optimize_cmd.py +192 -0
  29. fund_cli/config.py +163 -0
  30. fund_cli/core/__init__.py +8 -0
  31. fund_cli/core/analyzer.py +46 -0
  32. fund_cli/core/data_manager.py +231 -0
  33. fund_cli/core/data_quality.py +162 -0
  34. fund_cli/core/monitor.py +230 -0
  35. fund_cli/core/optimizer.py +50 -0
  36. fund_cli/core/optimizers/__init__.py +13 -0
  37. fund_cli/core/optimizers/efficient_frontier.py +91 -0
  38. fund_cli/core/optimizers/max_sharpe.py +54 -0
  39. fund_cli/core/optimizers/mean_variance.py +84 -0
  40. fund_cli/core/optimizers/risk_parity.py +60 -0
  41. fund_cli/core/reporter.py +67 -0
  42. fund_cli/core/reporters/__init__.py +6 -0
  43. fund_cli/core/reporters/html_reporter.py +62 -0
  44. fund_cli/core/reporters/markdown_reporter.py +40 -0
  45. fund_cli/core/screener.py +142 -0
  46. fund_cli/data/__init__.py +6 -0
  47. fund_cli/data/adapters/__init__.py +7 -0
  48. fund_cli/data/adapters/akshare_adapter.py +442 -0
  49. fund_cli/data/adapters/tushare_adapter.py +254 -0
  50. fund_cli/data/adapters/wind_adapter.py +78 -0
  51. fund_cli/data/base.py +209 -0
  52. fund_cli/data/cache.py +192 -0
  53. fund_cli/data/models.py +248 -0
  54. fund_cli/utils/__init__.py +6 -0
  55. fund_cli/utils/decorators.py +88 -0
  56. fund_cli/utils/helpers.py +127 -0
  57. fund_cli/utils/validators.py +77 -0
  58. fund_cli/views/__init__.py +6 -0
  59. fund_cli/views/charts.py +120 -0
  60. fund_cli/views/reports.py +82 -0
  61. fund_cli/views/tables.py +124 -0
  62. fund_cli-2.0.0.dist-info/METADATA +183 -0
  63. fund_cli-2.0.0.dist-info/RECORD +66 -0
  64. fund_cli-2.0.0.dist-info/WHEEL +4 -0
  65. fund_cli-2.0.0.dist-info/entry_points.txt +3 -0
  66. fund_cli-2.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,78 @@
1
+ """
2
+ Wind 数据源适配器(占位)
3
+
4
+ Wind 是商业数据源,需要授权使用。
5
+ 此文件为接口预留,实际实现需要 Wind Python API。
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ import pandas as pd
11
+
12
+ from fund_cli.data.base import (
13
+ DataSourceAdapter,
14
+ DataSourceError,
15
+ )
16
+ from fund_cli.data.cache import DataCache
17
+
18
+
19
+ class WindAdapter(DataSourceAdapter):
20
+ """
21
+ Wind 数据源适配器(占位实现)
22
+
23
+ Wind 是商业数据源,需要授权和 Wind Python API。
24
+ 当前为接口预留,实际使用需要:
25
+ 1. 安装 Wind Python API
26
+ 2. 配置有效的 Wind 账号
27
+ """
28
+
29
+ def __init__(self, cache: DataCache | None = None):
30
+ super().__init__("wind")
31
+ self._cache = cache
32
+
33
+ def is_available(self) -> bool:
34
+ """检查 Wind 是否可用"""
35
+ try:
36
+ from WindPy import w # noqa: F401
37
+
38
+ return w.isconnected()
39
+ except Exception:
40
+ return False
41
+
42
+ def get_fund_info(self, fund_code: str) -> dict[str, Any]:
43
+ """获取基金基础信息"""
44
+ raise DataSourceError("Wind 数据源暂未实现,请使用 AKShare 或 Tushare 数据源")
45
+
46
+ def get_fund_nav(
47
+ self,
48
+ fund_code: str,
49
+ start_date: Any | None = None,
50
+ end_date: Any | None = None,
51
+ ) -> pd.DataFrame:
52
+ """获取基金净值数据"""
53
+ raise DataSourceError("Wind 数据源暂未实现,请使用 AKShare 或 Tushare 数据源")
54
+
55
+ def search_funds(
56
+ self,
57
+ fund_type: str | None = None,
58
+ company: str | None = None,
59
+ min_scale: float | None = None,
60
+ max_scale: float | None = None,
61
+ keyword: str | None = None,
62
+ limit: int = 100,
63
+ ) -> pd.DataFrame:
64
+ """搜索基金"""
65
+ raise DataSourceError("Wind 数据源暂未实现,请使用 AKShare 或 Tushare 数据源")
66
+
67
+ def get_fund_list(self, fund_type: str | None = None) -> pd.DataFrame:
68
+ """获取基金列表"""
69
+ raise DataSourceError("Wind 数据源暂未实现,请使用 AKShare 或 Tushare 数据源")
70
+
71
+ def get_benchmark_nav(
72
+ self,
73
+ benchmark_code: str,
74
+ start_date: Any | None = None,
75
+ end_date: Any | None = None,
76
+ ) -> pd.DataFrame:
77
+ """获取基准指数数据"""
78
+ raise DataSourceError("Wind 数据源暂未实现,请使用 AKShare 或 Tushare 数据源")
fund_cli/data/base.py ADDED
@@ -0,0 +1,209 @@
1
+ """
2
+ 数据源适配器基类
3
+
4
+ 定义数据源适配器的标准接口,支持多数据源扩展。
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from datetime import date
9
+ from typing import Any
10
+
11
+ import pandas as pd
12
+
13
+
14
+ class DataSourceAdapter(ABC):
15
+ """
16
+ 数据源适配器基类
17
+
18
+ 所有数据源适配器必须继承此类并实现所有抽象方法。
19
+ """
20
+
21
+ def __init__(self, name: str):
22
+ """
23
+ 初始化数据源适配器
24
+
25
+ Args:
26
+ name: 数据源名称
27
+ """
28
+ self._name = name
29
+
30
+ @property
31
+ def name(self) -> str:
32
+ """数据源名称"""
33
+ return self._name
34
+
35
+ @abstractmethod
36
+ def is_available(self) -> bool:
37
+ """
38
+ 检查数据源是否可用
39
+
40
+ Returns:
41
+ 数据源是否可用
42
+ """
43
+ pass
44
+
45
+ @abstractmethod
46
+ def get_fund_info(self, fund_code: str) -> dict[str, Any]:
47
+ """
48
+ 获取基金基础信息
49
+
50
+ Args:
51
+ fund_code: 基金代码(6位数字)
52
+
53
+ Returns:
54
+ 基金基础信息字典,包含:
55
+ - code: 基金代码
56
+ - name: 基金名称
57
+ - type: 基金类型
58
+ - establish_date: 成立日期
59
+ - manager: 基金经理
60
+ - company: 基金公司
61
+ - scale: 规模(亿元)
62
+
63
+ Raises:
64
+ DataNotFoundError: 基金不存在
65
+ DataSourceError: 数据源错误
66
+ """
67
+ pass
68
+
69
+ @abstractmethod
70
+ def get_fund_nav(
71
+ self,
72
+ fund_code: str,
73
+ start_date: date | None = None,
74
+ end_date: date | None = None,
75
+ ) -> pd.DataFrame:
76
+ """
77
+ 获取基金净值数据
78
+
79
+ Args:
80
+ fund_code: 基金代码
81
+ start_date: 开始日期,默认为基金成立日
82
+ end_date: 结束日期,默认为最新日期
83
+
84
+ Returns:
85
+ 净值数据DataFrame,包含列:
86
+ - nav_date: 净值日期
87
+ - unit_nav: 单位净值
88
+ - accumulated_nav: 累计净值
89
+ - daily_return: 日收益率(可选)
90
+
91
+ Raises:
92
+ DataNotFoundError: 基金不存在
93
+ DataSourceError: 数据源错误
94
+ """
95
+ pass
96
+
97
+ @abstractmethod
98
+ def search_funds(
99
+ self,
100
+ fund_type: str | None = None,
101
+ company: str | None = None,
102
+ min_scale: float | None = None,
103
+ max_scale: float | None = None,
104
+ keyword: str | None = None,
105
+ limit: int = 100,
106
+ ) -> pd.DataFrame:
107
+ """
108
+ 搜索/筛选基金
109
+
110
+ Args:
111
+ fund_type: 基金类型
112
+ company: 基金公司
113
+ min_scale: 最小规模(亿元)
114
+ max_scale: 最大规模(亿元)
115
+ keyword: 关键词搜索
116
+ limit: 返回数量限制
117
+
118
+ Returns:
119
+ 基金列表DataFrame
120
+ """
121
+ pass
122
+
123
+ @abstractmethod
124
+ def get_fund_list(self, fund_type: str | None = None) -> pd.DataFrame:
125
+ """
126
+ 获取基金列表
127
+
128
+ Args:
129
+ fund_type: 基金类型筛选
130
+
131
+ Returns:
132
+ 基金列表DataFrame
133
+ """
134
+ pass
135
+
136
+ @abstractmethod
137
+ def get_benchmark_nav(
138
+ self,
139
+ benchmark_code: str,
140
+ start_date: date | None = None,
141
+ end_date: date | None = None,
142
+ ) -> pd.DataFrame:
143
+ """
144
+ 获取基准指数净值数据
145
+
146
+ Args:
147
+ benchmark_code: 基准指数代码
148
+ start_date: 开始日期
149
+ end_date: 结束日期
150
+
151
+ Returns:
152
+ 基准净值数据DataFrame
153
+ """
154
+ pass
155
+
156
+ @abstractmethod
157
+ def get_fund_holdings(
158
+ self,
159
+ fund_code: str,
160
+ report_date: date | None = None,
161
+ ) -> pd.DataFrame:
162
+ """获取基金持仓数据"""
163
+ pass
164
+
165
+ @abstractmethod
166
+ def get_fund_manager(self, fund_code: str) -> dict[str, Any]:
167
+ """获取基金经理信息"""
168
+ pass
169
+
170
+ @abstractmethod
171
+ def get_fund_fee(self, fund_code: str) -> dict[str, Any]:
172
+ """获取基金费率信息"""
173
+ pass
174
+
175
+ @abstractmethod
176
+ def get_fund_rating(self, fund_code: str) -> int | None:
177
+ """获取基金评级"""
178
+ pass
179
+
180
+ @abstractmethod
181
+ def batch_get_fund_nav(
182
+ self,
183
+ fund_codes: list[str],
184
+ start_date: date | None = None,
185
+ end_date: date | None = None,
186
+ ) -> dict[str, pd.DataFrame]:
187
+ """批量获取基金净值数据"""
188
+ pass
189
+
190
+ def __repr__(self) -> str:
191
+ return f"{self.__class__.__name__}(name={self._name!r})"
192
+
193
+
194
+ class DataSourceError(Exception):
195
+ """数据源错误"""
196
+
197
+ pass
198
+
199
+
200
+ class DataNotFoundError(Exception):
201
+ """数据未找到错误"""
202
+
203
+ pass
204
+
205
+
206
+ class NetworkError(Exception):
207
+ """网络错误"""
208
+
209
+ pass
fund_cli/data/cache.py ADDED
@@ -0,0 +1,192 @@
1
+ """
2
+ 数据缓存管理
3
+
4
+ 使用 DiskCache 实现数据缓存,支持过期时间和持久化。
5
+ """
6
+
7
+ import hashlib
8
+ import json
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ import pandas as pd
13
+ from diskcache import Cache
14
+
15
+
16
+ class DataCache:
17
+ """
18
+ 数据缓存管理器
19
+
20
+ 使用 DiskCache 实现数据缓存,支持:
21
+ - 过期时间设置
22
+ - 持久化存储
23
+ - 自动序列化/反序列化
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ cache_dir: str = "~/.fund_cli/cache",
29
+ default_ttl: int = 3600,
30
+ ):
31
+ """
32
+ 初始化缓存管理器
33
+
34
+ Args:
35
+ cache_dir: 缓存目录
36
+ default_ttl: 默认过期时间(秒)
37
+ """
38
+ self.cache_dir = Path(cache_dir).expanduser()
39
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
40
+ self.default_ttl = default_ttl
41
+ self._cache = Cache(str(self.cache_dir))
42
+
43
+ def _generate_key(self, prefix: str, *args, **kwargs) -> str:
44
+ """
45
+ 生成缓存键
46
+
47
+ Args:
48
+ prefix: 键前缀
49
+ *args: 位置参数
50
+ **kwargs: 关键字参数
51
+
52
+ Returns:
53
+ 缓存键字符串
54
+ """
55
+ # 将参数序列化为字符串
56
+ key_data = json.dumps({"args": args, "kwargs": kwargs}, sort_keys=True, default=str)
57
+ key_hash = hashlib.md5(key_data.encode()).hexdigest()[:8]
58
+ return f"{prefix}:{key_hash}"
59
+
60
+ def get(self, key: str) -> Any | None:
61
+ """
62
+ 获取缓存值
63
+
64
+ Args:
65
+ key: 缓存键
66
+
67
+ Returns:
68
+ 缓存值,不存在返回 None
69
+ """
70
+ return self._cache.get(key)
71
+
72
+ def set(
73
+ self,
74
+ key: str,
75
+ value: Any,
76
+ ttl: int | None = None,
77
+ ) -> None:
78
+ """
79
+ 设置缓存值
80
+
81
+ Args:
82
+ key: 缓存键
83
+ value: 缓存值
84
+ ttl: 过期时间(秒),默认使用 default_ttl
85
+ """
86
+ expire = ttl if ttl is not None else self.default_ttl
87
+ self._cache.set(key, value, expire=expire)
88
+
89
+ def delete(self, key: str) -> bool:
90
+ """
91
+ 删除缓存
92
+
93
+ Args:
94
+ key: 缓存键
95
+
96
+ Returns:
97
+ 是否删除成功
98
+ """
99
+ return self._cache.delete(key)
100
+
101
+ def exists(self, key: str) -> bool:
102
+ """
103
+ 检查缓存是否存在
104
+
105
+ Args:
106
+ key: 缓存键
107
+
108
+ Returns:
109
+ 缓存是否存在
110
+ """
111
+ return key in self._cache
112
+
113
+ def clear(self) -> None:
114
+ """清空所有缓存"""
115
+ self._cache.clear()
116
+
117
+ def get_stats(self) -> dict:
118
+ """
119
+ 获取缓存统计信息
120
+
121
+ Returns:
122
+ 统计信息字典
123
+ """
124
+ return {
125
+ "size": len(self._cache),
126
+ "volume": self._cache.volume(),
127
+ "directory": str(self.cache_dir),
128
+ }
129
+
130
+ # ========== 便捷方法 ==========
131
+
132
+ def get_fund_info(self, fund_code: str) -> dict | None:
133
+ """获取缓存的基金信息"""
134
+ key = f"fund_info:{fund_code}"
135
+ return self.get(key)
136
+
137
+ def set_fund_info(self, fund_code: str, info: dict, ttl: int | None = None) -> None:
138
+ """缓存基金信息"""
139
+ key = f"fund_info:{fund_code}"
140
+ self.set(key, info, ttl)
141
+
142
+ def get_fund_nav(self, fund_code: str, start_date: str, end_date: str) -> pd.DataFrame | None:
143
+ """获取缓存的净值数据"""
144
+ key = f"fund_nav:{fund_code}:{start_date}:{end_date}"
145
+ return self.get(key)
146
+
147
+ def set_fund_nav(
148
+ self,
149
+ fund_code: str,
150
+ start_date: str,
151
+ end_date: str,
152
+ nav_data: pd.DataFrame,
153
+ ttl: int | None = None,
154
+ ) -> None:
155
+ """缓存净值数据"""
156
+ key = f"fund_nav:{fund_code}:{start_date}:{end_date}"
157
+ self.set(key, nav_data, ttl)
158
+
159
+ def get_fund_holdings(self, fund_code: str, report_date: str = "latest") -> pd.DataFrame | None:
160
+ """获取缓存的持仓数据"""
161
+ key = f"fund_holdings:{fund_code}:{report_date}"
162
+ return self.get(key)
163
+
164
+ def set_fund_holdings(
165
+ self,
166
+ fund_code: str,
167
+ report_date: str,
168
+ data: pd.DataFrame,
169
+ ttl: int | None = None,
170
+ ) -> None:
171
+ """缓存持仓数据"""
172
+ key = f"fund_holdings:{fund_code}:{report_date}"
173
+ self.set(key, data, ttl)
174
+
175
+ def get_fund_manager(self, fund_code: str) -> dict | None:
176
+ """获取缓存的经理信息"""
177
+ key = f"fund_manager:{fund_code}"
178
+ return self.get(key)
179
+
180
+ def set_fund_manager(self, fund_code: str, info: dict, ttl: int | None = None) -> None:
181
+ """缓存经理信息"""
182
+ key = f"fund_manager:{fund_code}"
183
+ self.set(key, info, ttl)
184
+
185
+ def __enter__(self) -> "DataCache":
186
+ return self
187
+
188
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
189
+ pass
190
+
191
+ def __repr__(self) -> str:
192
+ return f"DataCache(directory={self.cache_dir}, size={len(self._cache)})"