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,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)})"
|