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,217 @@
1
+ """
2
+ 持仓分析引擎
3
+
4
+ 提供基金持仓分析功能,包括行业配置、重仓股、集中度、变化追踪和风格分析。
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+
12
+ from fund_cli.core.analyzer import Analyzer
13
+
14
+
15
+ class HoldingAnalyzer(Analyzer):
16
+ """
17
+ 持仓分析引擎
18
+
19
+ 分析基金持仓数据,提供:
20
+ - 行业配置分析
21
+ - 重仓股分析
22
+ - 持仓集中度(HHI指数)
23
+ - 持仓变化追踪
24
+ - 风格分析(九宫格)
25
+ """
26
+
27
+ def analyze(self, data: pd.DataFrame, **kwargs: Any) -> dict[str, Any]:
28
+ """
29
+ 执行综合持仓分析
30
+
31
+ Args:
32
+ data: 持仓数据 DataFrame,需包含 stock_code, stock_name, weight, industry 列
33
+
34
+ Returns:
35
+ 综合分析结果字典
36
+ """
37
+ result = {}
38
+ if "industry" in data.columns:
39
+ result["industry_distribution"] = self.industry_distribution(data)
40
+ result["top_holdings"] = self.top_holdings(data)
41
+ result["concentration_hhi"] = self.concentration_hhi(data)
42
+ result["concentration_level"] = self._hhi_level(result["concentration_hhi"])
43
+ if "industry" in data.columns:
44
+ result["style_analysis"] = self.style_analysis(data)
45
+ return result
46
+
47
+ def industry_distribution(self, holdings: pd.DataFrame) -> dict[str, float]:
48
+ """
49
+ 行业配置分析 (FUND-HOLDING-002)
50
+
51
+ Args:
52
+ holdings: 持仓数据,需包含 industry 和 weight 列
53
+
54
+ Returns:
55
+ 行业分布字典 {行业名称: 占比(%)}
56
+
57
+ Raises:
58
+ ValueError: 数据缺少 industry 列
59
+ """
60
+ if "industry" not in holdings.columns:
61
+ raise ValueError("持仓数据缺少 industry 列")
62
+ distribution = holdings.groupby("industry")["weight"].sum()
63
+ return distribution.sort_values(ascending=False).to_dict()
64
+
65
+ def top_holdings(self, holdings: pd.DataFrame, top_n: int = 10) -> pd.DataFrame:
66
+ """
67
+ 重仓股分析 (FUND-HOLDING-003)
68
+
69
+ Args:
70
+ holdings: 持仓数据
71
+ top_n: 返回前N大持仓,默认10
72
+
73
+ Returns:
74
+ 前N大持仓 DataFrame
75
+ """
76
+ df = holdings.copy()
77
+ if "weight" in df.columns:
78
+ df = df.sort_values("weight", ascending=False)
79
+ return df.head(top_n).reset_index(drop=True)
80
+
81
+ def concentration_hhi(self, holdings: pd.DataFrame) -> float:
82
+ """
83
+ 持仓集中度 - HHI指数 (FUND-HOLDING-004)
84
+
85
+ HHI = sum(weight_i^2),其中 weight_i 为第i只股票占净值比例(小数形式)
86
+
87
+ Args:
88
+ holdings: 持仓数据,需包含 weight 列
89
+
90
+ Returns:
91
+ HHI指数值。>0.25 高度集中, 0.15~0.25 中度集中, <0.15 分散
92
+ """
93
+ if holdings.empty or "weight" not in holdings.columns:
94
+ return 0.0
95
+ weights = holdings["weight"].values / 100.0 # 转为小数
96
+ return float(np.sum(weights**2))
97
+
98
+ def track_changes(
99
+ self,
100
+ current: pd.DataFrame,
101
+ previous: pd.DataFrame,
102
+ ) -> pd.DataFrame:
103
+ """
104
+ 持仓变化追踪 (FUND-HOLDING-005)
105
+
106
+ 对比两期持仓数据,标识新增、删除、增持、减持的股票。
107
+
108
+ Args:
109
+ current: 当期持仓数据
110
+ previous: 上期持仓数据
111
+
112
+ Returns:
113
+ 变化分析 DataFrame,包含 change_type 列(新增/删除/增持/减持/不变)
114
+ """
115
+ if current.empty or previous.empty:
116
+ return pd.DataFrame()
117
+
118
+ curr = (
119
+ current[["stock_code", "stock_name", "weight"]].copy()
120
+ if "stock_code" in current.columns
121
+ else current.copy()
122
+ )
123
+ prev = (
124
+ previous[["stock_code", "weight"]].copy()
125
+ if "stock_code" in previous.columns
126
+ else previous.copy()
127
+ )
128
+
129
+ merged = curr.merge(prev, on="stock_code", how="outer", suffixes=("_curr", "_prev"))
130
+
131
+ def classify_change(row: pd.Series) -> str:
132
+ if pd.isna(row.get("weight_prev")):
133
+ return "新增"
134
+ if pd.isna(row.get("weight_curr")):
135
+ return "删除"
136
+ diff = row["weight_curr"] - row["weight_prev"]
137
+ if abs(diff) < 0.01:
138
+ return "不变"
139
+ return "增持" if diff > 0 else "减持"
140
+
141
+ merged["change_type"] = merged.apply(classify_change, axis=1)
142
+ merged["weight_change"] = merged.get("weight_curr", 0) - merged.get("weight_prev", 0)
143
+ return merged.sort_values("weight_change", ascending=False, na_position="last").reset_index(
144
+ drop=True
145
+ )
146
+
147
+ def style_analysis(self, holdings: pd.DataFrame) -> dict[str, Any]:
148
+ """
149
+ 风格分析 (FUND-HOLDING-006)
150
+
151
+ 基于持仓股票的行业分布进行风格分析,输出风格九宫格位置。
152
+ 简化实现:基于行业分布判断大盘/小盘和价值/成长倾向。
153
+
154
+ Args:
155
+ holdings: 持仓数据,需包含 industry 列
156
+
157
+ Returns:
158
+ 风格分析结果字典
159
+ """
160
+ if "industry" not in holdings.columns:
161
+ return {"market_cap_style": "未知", "investment_style": "未知", "grid_position": "中"}
162
+
163
+ # 基于行业分布判断风格
164
+ distribution = self.industry_distribution(holdings)
165
+ total = sum(distribution.values()) or 1.0
166
+
167
+ # 大盘行业权重
168
+ large_cap_industries = {"银行", "非银金融", "食品饮料", "医药生物", "电子", "电力设备"}
169
+ large_cap_weight = sum(distribution.get(ind, 0) for ind in large_cap_industries) / total
170
+
171
+ # 价值行业权重
172
+ value_industries = {
173
+ "银行",
174
+ "房地产",
175
+ "建筑装饰",
176
+ "公用事业",
177
+ "交通运输",
178
+ "煤炭",
179
+ "石油石化",
180
+ }
181
+ value_weight = sum(distribution.get(ind, 0) for ind in value_industries) / total
182
+
183
+ market_cap_style = (
184
+ "大盘" if large_cap_weight > 0.5 else ("小盘" if large_cap_weight < 0.3 else "中盘")
185
+ )
186
+ investment_style = (
187
+ "价值" if value_weight > 0.3 else ("成长" if value_weight < 0.15 else "平衡")
188
+ )
189
+
190
+ return {
191
+ "market_cap_style": market_cap_style,
192
+ "investment_style": investment_style,
193
+ "grid_position": f"{market_cap_style}{investment_style}",
194
+ "large_cap_weight": round(large_cap_weight * 100, 2),
195
+ "value_weight": round(value_weight * 100, 2),
196
+ "industry_distribution": distribution,
197
+ }
198
+
199
+ def get_metrics(self) -> list[str]:
200
+ """返回支持的指标列表"""
201
+ return [
202
+ "industry_distribution",
203
+ "top_holdings",
204
+ "concentration_hhi",
205
+ "concentration_level",
206
+ "style_analysis",
207
+ ]
208
+
209
+ @staticmethod
210
+ def _hhi_level(hhi: float) -> str:
211
+ """根据HHI值返回集中度等级"""
212
+ if hhi >= 0.25:
213
+ return "高度集中"
214
+ elif hhi >= 0.15:
215
+ return "中度集中"
216
+ else:
217
+ return "分散"
@@ -0,0 +1,133 @@
1
+ """
2
+ 基金经理分析引擎
3
+
4
+ 提供基金经理信息查询、业绩统计和稳定性分析功能。
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ from fund_cli.core.analyzer import Analyzer
10
+
11
+
12
+ class ManagerAnalyzer(Analyzer):
13
+ """
14
+ 基金经理分析引擎
15
+
16
+ 分析基金经理的综合信息,包括:
17
+ - 经理基本信息查询
18
+ - 业绩统计(管理基金数、平均收益率等)
19
+ - 稳定性分析(任职年限、管理规模变化)
20
+ """
21
+
22
+ def analyze(self, data: dict[str, Any], **kwargs: Any) -> dict[str, Any]:
23
+ """
24
+ 执行综合经理分析
25
+
26
+ Args:
27
+ data: 经理数据字典,需包含 name, fund_code, fund_name 等字段
28
+
29
+ Returns:
30
+ 综合分析结果
31
+ """
32
+ result = {}
33
+ result["info"] = self.manager_info(data)
34
+ result["performance"] = self.performance_stats(data)
35
+ result["stability"] = self.stability_analysis(data)
36
+ return result
37
+
38
+ def manager_info(self, data: dict[str, Any]) -> dict[str, Any]:
39
+ """
40
+ 经理信息查询 (FUND-MANAGER-001)
41
+
42
+ Args:
43
+ data: 经理数据字典
44
+
45
+ Returns:
46
+ 经理信息字典
47
+ """
48
+ return {
49
+ "name": data.get("name", ""),
50
+ "fund_code": data.get("fund_code", ""),
51
+ "fund_name": data.get("fund_name", ""),
52
+ "company": data.get("company", ""),
53
+ "start_date": str(data.get("start_date", "")),
54
+ "tenure_days": data.get("tenure_days", 0),
55
+ }
56
+
57
+ def performance_stats(self, data: dict[str, Any]) -> dict[str, Any]:
58
+ """
59
+ 经理业绩统计 (FUND-MANAGER-002)
60
+
61
+ Args:
62
+ data: 经理数据字典,可包含 total_return, annual_return, funds 列表
63
+
64
+ Returns:
65
+ 业绩统计字典
66
+ """
67
+ funds = data.get("funds", [])
68
+ if funds:
69
+ returns = [f.get("total_return", 0) for f in funds if f.get("total_return") is not None]
70
+ avg_return = sum(returns) / len(returns) if returns else 0
71
+ best_fund = max(funds, key=lambda f: f.get("total_return", float("-inf")), default=None)
72
+ worst_fund = min(funds, key=lambda f: f.get("total_return", float("inf")), default=None)
73
+ return {
74
+ "total_funds": len(funds),
75
+ "avg_return": round(avg_return, 2),
76
+ "best_fund": best_fund.get("fund_name", "") if best_fund else "",
77
+ "best_return": best_fund.get("total_return", 0) if best_fund else 0,
78
+ "worst_fund": worst_fund.get("fund_name", "") if worst_fund else "",
79
+ "worst_return": worst_fund.get("total_return", 0) if worst_fund else 0,
80
+ }
81
+ else:
82
+ return {
83
+ "total_funds": 1,
84
+ "avg_return": data.get("annual_return", 0),
85
+ "best_fund": data.get("fund_name", ""),
86
+ "best_return": data.get("total_return", 0),
87
+ "worst_fund": data.get("fund_name", ""),
88
+ "worst_return": data.get("total_return", 0),
89
+ }
90
+
91
+ def stability_analysis(self, data: dict[str, Any]) -> dict[str, Any]:
92
+ """
93
+ 经理稳定性分析 (FUND-MANAGER-003)
94
+
95
+ Args:
96
+ data: 经理数据字典,可包含 tenure_days, start_date, funds 列表
97
+
98
+ Returns:
99
+ 稳定性分析字典
100
+ """
101
+ tenure_days = data.get("tenure_days", 0)
102
+ tenure_years = tenure_days / 365.25 if tenure_days else 0
103
+
104
+ # 稳定性评级
105
+ if tenure_years >= 5:
106
+ stability_level = "非常稳定"
107
+ stability_score = 5
108
+ elif tenure_years >= 3:
109
+ stability_level = "稳定"
110
+ stability_score = 4
111
+ elif tenure_years >= 1:
112
+ stability_level = "一般"
113
+ stability_score = 3
114
+ else:
115
+ stability_level = "较新"
116
+ stability_score = 2
117
+
118
+ # 多基金管理分析
119
+ funds = data.get("funds", [])
120
+ multi_fund = len(funds) > 1
121
+
122
+ return {
123
+ "tenure_days": tenure_days,
124
+ "tenure_years": round(tenure_years, 1),
125
+ "stability_level": stability_level,
126
+ "stability_score": stability_score,
127
+ "multi_fund_manager": multi_fund,
128
+ "managed_fund_count": len(funds) if funds else 1,
129
+ }
130
+
131
+ def get_metrics(self) -> list[str]:
132
+ """返回支持的指标列表"""
133
+ return ["manager_info", "performance_stats", "stability_analysis"]