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.
- fund_cli/__init__.py +13 -0
- fund_cli/__main__.py +10 -0
- fund_cli/ai/__init__.py +21 -0
- fund_cli/ai/analyzer.py +360 -0
- fund_cli/ai/prompts.py +244 -0
- fund_cli/ai/providers.py +286 -0
- fund_cli/analysis/__init__.py +17 -0
- fund_cli/analysis/attribution.py +161 -0
- fund_cli/analysis/backtest.py +75 -0
- fund_cli/analysis/holding.py +217 -0
- fund_cli/analysis/manager.py +133 -0
- fund_cli/analysis/performance.py +440 -0
- fund_cli/analysis/portfolio.py +152 -0
- fund_cli/analysis/risk.py +300 -0
- fund_cli/cli.py +98 -0
- fund_cli/commands/__init__.py +9 -0
- fund_cli/commands/ai_cmd.py +464 -0
- fund_cli/commands/analyze_cmd.py +418 -0
- fund_cli/commands/compare_cmd.py +264 -0
- fund_cli/commands/config_cmd.py +97 -0
- fund_cli/commands/data_cmd.py +106 -0
- fund_cli/commands/filter_cmd.py +286 -0
- fund_cli/commands/holding_cmd.py +140 -0
- fund_cli/commands/interactive_cmd.py +84 -0
- fund_cli/commands/main.py +17 -0
- fund_cli/commands/manager_cmd.py +74 -0
- fund_cli/commands/monitor_cmd.py +113 -0
- fund_cli/commands/optimize_cmd.py +192 -0
- fund_cli/config.py +163 -0
- fund_cli/core/__init__.py +8 -0
- fund_cli/core/analyzer.py +46 -0
- fund_cli/core/data_manager.py +231 -0
- fund_cli/core/data_quality.py +162 -0
- fund_cli/core/monitor.py +230 -0
- fund_cli/core/optimizer.py +50 -0
- fund_cli/core/optimizers/__init__.py +13 -0
- fund_cli/core/optimizers/efficient_frontier.py +91 -0
- fund_cli/core/optimizers/max_sharpe.py +54 -0
- fund_cli/core/optimizers/mean_variance.py +84 -0
- fund_cli/core/optimizers/risk_parity.py +60 -0
- fund_cli/core/reporter.py +67 -0
- fund_cli/core/reporters/__init__.py +6 -0
- fund_cli/core/reporters/html_reporter.py +62 -0
- fund_cli/core/reporters/markdown_reporter.py +40 -0
- fund_cli/core/screener.py +142 -0
- fund_cli/data/__init__.py +6 -0
- fund_cli/data/adapters/__init__.py +7 -0
- fund_cli/data/adapters/akshare_adapter.py +442 -0
- fund_cli/data/adapters/tushare_adapter.py +254 -0
- fund_cli/data/adapters/wind_adapter.py +78 -0
- fund_cli/data/base.py +209 -0
- fund_cli/data/cache.py +192 -0
- fund_cli/data/models.py +248 -0
- fund_cli/utils/__init__.py +6 -0
- fund_cli/utils/decorators.py +88 -0
- fund_cli/utils/helpers.py +127 -0
- fund_cli/utils/validators.py +77 -0
- fund_cli/views/__init__.py +6 -0
- fund_cli/views/charts.py +120 -0
- fund_cli/views/reports.py +82 -0
- fund_cli/views/tables.py +124 -0
- fund_cli-2.0.0.dist-info/METADATA +183 -0
- fund_cli-2.0.0.dist-info/RECORD +66 -0
- fund_cli-2.0.0.dist-info/WHEEL +4 -0
- fund_cli-2.0.0.dist-info/entry_points.txt +3 -0
- fund_cli-2.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,300 @@
|
|
|
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 RiskAnalyzer(Analyzer):
|
|
16
|
+
"""
|
|
17
|
+
风险分析引擎
|
|
18
|
+
|
|
19
|
+
计算各类风险指标,包括:
|
|
20
|
+
- 波动率风险:年化波动率、下行波动率
|
|
21
|
+
- 回撤风险:最大回撤、平均回撤、回撤持续时间
|
|
22
|
+
- 尾部风险:VaR、CVaR、偏度、峰度
|
|
23
|
+
- 相关性风险:相关性矩阵、Beta
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
confidence_level: float = 0.95,
|
|
29
|
+
periods_per_year: int = 252,
|
|
30
|
+
):
|
|
31
|
+
"""
|
|
32
|
+
初始化风险分析引擎
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
confidence_level: VaR 置信水平
|
|
36
|
+
periods_per_year: 年交易日数
|
|
37
|
+
"""
|
|
38
|
+
self.confidence_level = confidence_level
|
|
39
|
+
self.periods_per_year = periods_per_year
|
|
40
|
+
|
|
41
|
+
def analyze(
|
|
42
|
+
self,
|
|
43
|
+
returns: pd.Series,
|
|
44
|
+
benchmark: pd.Series | None = None,
|
|
45
|
+
**kwargs,
|
|
46
|
+
) -> dict[str, Any]:
|
|
47
|
+
"""
|
|
48
|
+
执行风险分析
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
returns: 收益率序列
|
|
52
|
+
benchmark: 基准收益率序列(可选)
|
|
53
|
+
**kwargs: 额外参数
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
风险分析结果字典
|
|
57
|
+
"""
|
|
58
|
+
# 确保输入为 Series
|
|
59
|
+
if isinstance(returns, pd.DataFrame):
|
|
60
|
+
returns = returns.iloc[:, 0]
|
|
61
|
+
|
|
62
|
+
returns = returns.dropna()
|
|
63
|
+
|
|
64
|
+
metrics = {
|
|
65
|
+
# 波动率风险
|
|
66
|
+
"volatility_annual": self.annualized_volatility(returns) * 100,
|
|
67
|
+
"downside_volatility": self.downside_volatility(returns) * 100,
|
|
68
|
+
# 回撤风险
|
|
69
|
+
"max_drawdown": self.max_drawdown(returns) * 100,
|
|
70
|
+
"avg_drawdown": self.avg_drawdown(returns) * 100,
|
|
71
|
+
"max_drawdown_duration": self.max_drawdown_duration(returns),
|
|
72
|
+
# 尾部风险
|
|
73
|
+
"var_95": self.var(returns, 0.95) * 100,
|
|
74
|
+
"var_99": self.var(returns, 0.99) * 100,
|
|
75
|
+
"cvar_95": self.cvar(returns, 0.95) * 100,
|
|
76
|
+
"skewness": self.skewness(returns),
|
|
77
|
+
"kurtosis": self.kurtosis(returns),
|
|
78
|
+
# 分布特征
|
|
79
|
+
"best_day": returns.max() * 100 if not returns.empty else 0,
|
|
80
|
+
"worst_day": returns.min() * 100 if not returns.empty else 0,
|
|
81
|
+
"std_dev": returns.std() * 100 if not returns.empty else 0,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# 相对风险指标
|
|
85
|
+
if benchmark is not None:
|
|
86
|
+
if isinstance(benchmark, pd.DataFrame):
|
|
87
|
+
benchmark = benchmark.iloc[:, 0]
|
|
88
|
+
benchmark = benchmark.dropna()
|
|
89
|
+
|
|
90
|
+
common_dates = returns.index.intersection(benchmark.index)
|
|
91
|
+
if len(common_dates) > 0:
|
|
92
|
+
returns_aligned = returns.loc[common_dates]
|
|
93
|
+
benchmark_aligned = benchmark.loc[common_dates]
|
|
94
|
+
|
|
95
|
+
metrics["beta"] = self.beta(returns_aligned, benchmark_aligned)
|
|
96
|
+
metrics["correlation"] = self.correlation(returns_aligned, benchmark_aligned)
|
|
97
|
+
metrics["tracking_error"] = (
|
|
98
|
+
self.tracking_error(returns_aligned, benchmark_aligned) * 100
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return metrics
|
|
102
|
+
|
|
103
|
+
def get_metrics(self) -> list[str]:
|
|
104
|
+
"""获取可计算的指标列表"""
|
|
105
|
+
return [
|
|
106
|
+
"volatility_annual",
|
|
107
|
+
"downside_volatility",
|
|
108
|
+
"max_drawdown",
|
|
109
|
+
"var_95",
|
|
110
|
+
"var_99",
|
|
111
|
+
"cvar_95",
|
|
112
|
+
"skewness",
|
|
113
|
+
"kurtosis",
|
|
114
|
+
"beta",
|
|
115
|
+
"correlation",
|
|
116
|
+
"tracking_error",
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
# ========== 波动率计算 ==========
|
|
120
|
+
|
|
121
|
+
def annualized_volatility(self, returns: pd.Series) -> float:
|
|
122
|
+
"""计算年化波动率"""
|
|
123
|
+
if returns.empty:
|
|
124
|
+
return 0.0
|
|
125
|
+
return returns.std() * np.sqrt(self.periods_per_year)
|
|
126
|
+
|
|
127
|
+
def downside_volatility(
|
|
128
|
+
self,
|
|
129
|
+
returns: pd.Series,
|
|
130
|
+
mar: float = 0.0,
|
|
131
|
+
) -> float:
|
|
132
|
+
"""
|
|
133
|
+
计算下行波动率
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
returns: 收益率序列
|
|
137
|
+
mar: 最低可接受收益率
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
下行波动率
|
|
141
|
+
"""
|
|
142
|
+
if returns.empty:
|
|
143
|
+
return 0.0
|
|
144
|
+
|
|
145
|
+
downside_returns = returns[returns < mar] - mar
|
|
146
|
+
return np.sqrt((downside_returns**2).mean()) * np.sqrt(self.periods_per_year)
|
|
147
|
+
|
|
148
|
+
# ========== 回撤计算 ==========
|
|
149
|
+
|
|
150
|
+
def max_drawdown(self, returns: pd.Series) -> float:
|
|
151
|
+
"""计算最大回撤"""
|
|
152
|
+
if returns.empty:
|
|
153
|
+
return 0.0
|
|
154
|
+
|
|
155
|
+
cumulative = (1 + returns).cumprod()
|
|
156
|
+
running_max = cumulative.cummax()
|
|
157
|
+
drawdown = (cumulative - running_max) / running_max
|
|
158
|
+
return drawdown.min()
|
|
159
|
+
|
|
160
|
+
def avg_drawdown(self, returns: pd.Series) -> float:
|
|
161
|
+
"""计算平均回撤"""
|
|
162
|
+
if returns.empty:
|
|
163
|
+
return 0.0
|
|
164
|
+
|
|
165
|
+
cumulative = (1 + returns).cumprod()
|
|
166
|
+
running_max = cumulative.cummax()
|
|
167
|
+
drawdown = (cumulative - running_max) / running_max
|
|
168
|
+
return drawdown.mean()
|
|
169
|
+
|
|
170
|
+
def max_drawdown_duration(self, returns: pd.Series) -> int:
|
|
171
|
+
"""
|
|
172
|
+
计算最大回撤持续天数
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
最大回撤持续天数
|
|
176
|
+
"""
|
|
177
|
+
if returns.empty:
|
|
178
|
+
return 0
|
|
179
|
+
|
|
180
|
+
cumulative = (1 + returns).cumprod()
|
|
181
|
+
running_max = cumulative.cummax()
|
|
182
|
+
|
|
183
|
+
# 找到回撤期间
|
|
184
|
+
in_drawdown = cumulative < running_max
|
|
185
|
+
|
|
186
|
+
if not in_drawdown.any():
|
|
187
|
+
return 0
|
|
188
|
+
|
|
189
|
+
# 计算最长回撤期
|
|
190
|
+
drawdown_periods = []
|
|
191
|
+
current_period = 0
|
|
192
|
+
|
|
193
|
+
for is_dd in in_drawdown:
|
|
194
|
+
if is_dd:
|
|
195
|
+
current_period += 1
|
|
196
|
+
else:
|
|
197
|
+
if current_period > 0:
|
|
198
|
+
drawdown_periods.append(current_period)
|
|
199
|
+
current_period = 0
|
|
200
|
+
|
|
201
|
+
if current_period > 0:
|
|
202
|
+
drawdown_periods.append(current_period)
|
|
203
|
+
|
|
204
|
+
return max(drawdown_periods) if drawdown_periods else 0
|
|
205
|
+
|
|
206
|
+
# ========== 尾部风险计算 ==========
|
|
207
|
+
|
|
208
|
+
def var(
|
|
209
|
+
self,
|
|
210
|
+
returns: pd.Series,
|
|
211
|
+
confidence: float = 0.95,
|
|
212
|
+
) -> float:
|
|
213
|
+
"""
|
|
214
|
+
计算风险价值 (Value at Risk)
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
returns: 收益率序列
|
|
218
|
+
confidence: 置信水平
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
VaR 值(负数表示损失)
|
|
222
|
+
"""
|
|
223
|
+
if returns.empty:
|
|
224
|
+
return 0.0
|
|
225
|
+
|
|
226
|
+
return np.percentile(returns, (1 - confidence) * 100)
|
|
227
|
+
|
|
228
|
+
def cvar(
|
|
229
|
+
self,
|
|
230
|
+
returns: pd.Series,
|
|
231
|
+
confidence: float = 0.95,
|
|
232
|
+
) -> float:
|
|
233
|
+
"""
|
|
234
|
+
计算条件风险价值 (Conditional VaR / Expected Shortfall)
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
returns: 收益率序列
|
|
238
|
+
confidence: 置信水平
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
CVaR 值
|
|
242
|
+
"""
|
|
243
|
+
if returns.empty:
|
|
244
|
+
return 0.0
|
|
245
|
+
|
|
246
|
+
var = self.var(returns, confidence)
|
|
247
|
+
return returns[returns <= var].mean()
|
|
248
|
+
|
|
249
|
+
def skewness(self, returns: pd.Series) -> float:
|
|
250
|
+
"""计算偏度"""
|
|
251
|
+
if returns.empty:
|
|
252
|
+
return 0.0
|
|
253
|
+
return returns.skew()
|
|
254
|
+
|
|
255
|
+
def kurtosis(self, returns: pd.Series) -> float:
|
|
256
|
+
"""计算峰度"""
|
|
257
|
+
if returns.empty:
|
|
258
|
+
return 0.0
|
|
259
|
+
return returns.kurtosis()
|
|
260
|
+
|
|
261
|
+
# ========== 相对风险计算 ==========
|
|
262
|
+
|
|
263
|
+
def beta(
|
|
264
|
+
self,
|
|
265
|
+
returns: pd.Series,
|
|
266
|
+
benchmark: pd.Series,
|
|
267
|
+
) -> float:
|
|
268
|
+
"""计算 Beta"""
|
|
269
|
+
if returns.empty or benchmark.empty:
|
|
270
|
+
return 0.0
|
|
271
|
+
|
|
272
|
+
covariance = returns.cov(benchmark)
|
|
273
|
+
variance = benchmark.var()
|
|
274
|
+
|
|
275
|
+
if variance == 0:
|
|
276
|
+
return 0.0
|
|
277
|
+
|
|
278
|
+
return covariance / variance
|
|
279
|
+
|
|
280
|
+
def correlation(
|
|
281
|
+
self,
|
|
282
|
+
returns: pd.Series,
|
|
283
|
+
benchmark: pd.Series,
|
|
284
|
+
) -> float:
|
|
285
|
+
"""计算相关系数"""
|
|
286
|
+
if returns.empty or benchmark.empty:
|
|
287
|
+
return 0.0
|
|
288
|
+
return returns.corr(benchmark)
|
|
289
|
+
|
|
290
|
+
def tracking_error(
|
|
291
|
+
self,
|
|
292
|
+
returns: pd.Series,
|
|
293
|
+
benchmark: pd.Series,
|
|
294
|
+
) -> float:
|
|
295
|
+
"""计算跟踪误差"""
|
|
296
|
+
if returns.empty or benchmark.empty:
|
|
297
|
+
return 0.0
|
|
298
|
+
|
|
299
|
+
excess_returns = returns - benchmark
|
|
300
|
+
return excess_returns.std() * np.sqrt(self.periods_per_year)
|
fund_cli/cli.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Fund CLI 主入口
|
|
3
|
+
|
|
4
|
+
专业基金分析命令行工具的主程序入口。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from fund_cli import __version__
|
|
11
|
+
|
|
12
|
+
# 创建主应用
|
|
13
|
+
app = typer.Typer(
|
|
14
|
+
name="fund",
|
|
15
|
+
help="专业基金分析CLI工具 - 面向机构客户",
|
|
16
|
+
add_completion=True,
|
|
17
|
+
rich_markup_mode="rich",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# 全局控制台实例
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def version_callback(value: bool) -> None:
|
|
25
|
+
"""显示版本信息"""
|
|
26
|
+
if value:
|
|
27
|
+
console.print(f"[bold blue]Fund CLI[/bold blue] 版本: {__version__}")
|
|
28
|
+
raise typer.Exit()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.callback()
|
|
32
|
+
def main(
|
|
33
|
+
version: bool | None = typer.Option(
|
|
34
|
+
None,
|
|
35
|
+
"--version",
|
|
36
|
+
"-v",
|
|
37
|
+
help="显示版本信息",
|
|
38
|
+
callback=version_callback,
|
|
39
|
+
is_eager=True,
|
|
40
|
+
),
|
|
41
|
+
) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Fund CLI - 专业基金分析命令行工具
|
|
44
|
+
|
|
45
|
+
面向机构客户的基金分析工具,支持基金筛选、业绩分析、组合优化等功能。
|
|
46
|
+
|
|
47
|
+
使用 --help 查看各子命令的详细帮助。
|
|
48
|
+
"""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# 导入子命令模块
|
|
53
|
+
from fund_cli.commands import ( # noqa: E402
|
|
54
|
+
ai_cmd,
|
|
55
|
+
analyze_cmd,
|
|
56
|
+
compare_cmd,
|
|
57
|
+
config_cmd,
|
|
58
|
+
data_cmd,
|
|
59
|
+
filter_cmd,
|
|
60
|
+
holding_cmd,
|
|
61
|
+
interactive_cmd,
|
|
62
|
+
manager_cmd,
|
|
63
|
+
monitor_cmd,
|
|
64
|
+
optimize_cmd,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# 注册子命令
|
|
68
|
+
app.add_typer(filter_cmd.app, name="filter")
|
|
69
|
+
app.add_typer(analyze_cmd.app, name="analyze")
|
|
70
|
+
app.add_typer(compare_cmd.app, name="compare")
|
|
71
|
+
app.add_typer(optimize_cmd.app, name="optimize")
|
|
72
|
+
app.add_typer(monitor_cmd.app, name="monitor")
|
|
73
|
+
app.add_typer(data_cmd.app, name="data")
|
|
74
|
+
app.add_typer(config_cmd.app, name="config")
|
|
75
|
+
app.add_typer(ai_cmd.app, name="ai")
|
|
76
|
+
app.add_typer(holding_cmd.app, name="holding")
|
|
77
|
+
app.add_typer(manager_cmd.app, name="manager")
|
|
78
|
+
app.add_typer(interactive_cmd.app, name="interactive")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# 直接调用的命令
|
|
82
|
+
@app.command("info")
|
|
83
|
+
def info(
|
|
84
|
+
fund_code: str = typer.Argument(..., help="基金代码(6位数字)"),
|
|
85
|
+
) -> None:
|
|
86
|
+
"""
|
|
87
|
+
查看基金基础信息。
|
|
88
|
+
|
|
89
|
+
示例:
|
|
90
|
+
fund info 000001
|
|
91
|
+
"""
|
|
92
|
+
from fund_cli.commands.analyze_cmd import info_fund
|
|
93
|
+
|
|
94
|
+
info_fund(fund_code)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
app()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""CLI命令模块 - 筛选、分析、对比、优化、监控、数据、配置、AI"""
|
|
2
|
+
|
|
3
|
+
from fund_cli.commands.analyze_cmd import app as analyze_app
|
|
4
|
+
from fund_cli.commands.compare_cmd import app as compare_app
|
|
5
|
+
from fund_cli.commands.config_cmd import app as config_app
|
|
6
|
+
from fund_cli.commands.data_cmd import app as data_app
|
|
7
|
+
from fund_cli.commands.filter_cmd import app as filter_app
|
|
8
|
+
|
|
9
|
+
__all__ = ["filter_app", "analyze_app", "compare_app", "data_app", "config_app"]
|