libfinance 0.1.1__tar.gz → 0.1.2__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.
Files changed (43) hide show
  1. {libfinance-0.1.1 → libfinance-0.1.2}/PKG-INFO +5 -4
  2. {libfinance-0.1.1 → libfinance-0.1.2}/README.md +4 -3
  3. libfinance-0.1.2/VERSION +1 -0
  4. libfinance-0.1.2/libfinance/api/factors.py +85 -0
  5. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/api/financials.py +1 -1
  6. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/api/get_price.py +2 -2
  7. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/api/live_quote.py +1 -1
  8. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/api/theme.py +1 -1
  9. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/subscribe/md_protocol.py +2 -2
  10. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/subscribe/quote_api.py +1 -1
  11. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/utils/cache.py +16 -0
  12. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance.egg-info/PKG-INFO +5 -4
  13. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance.egg-info/SOURCES.txt +1 -0
  14. {libfinance-0.1.1 → libfinance-0.1.2}/test/test_client_api_contract.py +56 -2
  15. libfinance-0.1.1/VERSION +0 -1
  16. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/__init__.py +0 -0
  17. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/api/__init__.py +0 -0
  18. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/api/calendar.py +0 -0
  19. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/api/corporate_actions.py +0 -0
  20. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/api/exfactor.py +0 -0
  21. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/api/index_components.py +0 -0
  22. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/api/industry.py +0 -0
  23. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/api/instrument.py +0 -0
  24. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/api/shares.py +0 -0
  25. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/client.py +0 -0
  26. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/subscribe/__init__.py +0 -0
  27. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/utils/__init__.py +0 -0
  28. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/utils/datetime_func.py +0 -0
  29. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/utils/decorators.py +0 -0
  30. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/utils/utils.py +0 -0
  31. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance/utils/validators.py +0 -0
  32. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance.egg-info/dependency_links.txt +0 -0
  33. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance.egg-info/requires.txt +0 -0
  34. {libfinance-0.1.1 → libfinance-0.1.2}/libfinance.egg-info/top_level.txt +0 -0
  35. {libfinance-0.1.1 → libfinance-0.1.2}/pyproject.toml +0 -0
  36. {libfinance-0.1.1 → libfinance-0.1.2}/setup.cfg +0 -0
  37. {libfinance-0.1.1 → libfinance-0.1.2}/setup.py +0 -0
  38. {libfinance-0.1.1 → libfinance-0.1.2}/test/test_client_reconnect.py +0 -0
  39. {libfinance-0.1.1 → libfinance-0.1.2}/test/test_contract.py +0 -0
  40. {libfinance-0.1.1 → libfinance-0.1.2}/test/test_docs_contract.py +0 -0
  41. {libfinance-0.1.1 → libfinance-0.1.2}/test/test_rpc_contract.py +0 -0
  42. {libfinance-0.1.1 → libfinance-0.1.2}/test/test_subscribe_live.py +0 -0
  43. {libfinance-0.1.1 → libfinance-0.1.2}/test/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: libfinance
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: libfinance is a modern finance API
5
5
  Description-Content-Type: text/markdown
6
6
  Requires-Dist: lz4
@@ -122,10 +122,11 @@ get_trading_dates("2024-01-01", "2024-01-31", market="us") # 不涉及具体
122
122
  | 分红 / 拆股 / 配股 | CN · US | `get_dividends` | 除权事件,价格跳空的来源 |
123
123
  | 分拆 | US | `get_spinoffs` | A 股不产生这类事件 |
124
124
  | 财务报表(PIT) | CN | `get_pit_financials_ex` | 按季度取,带修订历史 |
125
- | 财务衍生指标 | CN、US | `get_financial_metrics` | 按交易日的财务衍生指标(RQData get_factor 同形) |
125
+ | 财务衍生指标 | CN、US | `get_financial_metrics` | 按交易日的财务衍生指标 |
126
+ | 因子 | CN | `get_factor_exposure`、`list_factor_libraries`、`list_factors` | qlib、alpha158、Barra CNE5 风格 / 行业因子与描述符 |
126
127
  | 行业分类 | CN / US | `get_instrument_industry`、`get_industry_constituents`、`get_industry_weights` | 申万、GICS 等分类体系 |
127
128
  | 指数成分与权重 | CN | `get_index_weights` | 任意交易日的成分权重 |
128
- | 概念板块成分 | CN | `get_concept_weights` | 同花顺概念分类 |
129
+ | 概念板块成分 | CN | `get_concept_weights` | THS 概念分类 |
129
130
  | 实时行情 | CN | `QuoteApi` | 订阅推送 |
130
131
 
131
132
  覆盖到哪一年、更新到哪一天,取决于你连的那个服务。**不要照抄文档里的日期**,用[数据新鲜度](https://libfinance.readthedocs.io/zh-cn/latest/data/freshness.html)里的两个函数自己查。
@@ -163,7 +164,7 @@ API 参考按合约信息和交易日历、行情信息、基本面信息、行
163
164
  | 目录 | 内容 |
164
165
  | --- | --- |
165
166
  | `contract/` | 各语言客户端共同遵守的契约:公开函数(名称、参数顺序与默认值、返回)与它们调用的服务端函数 |
167
+ | `contract/conformance/` | 同一组调用分别经 Python 与 C++ 客户端发给同一个服务端,逐项比对答案 |
166
168
  | `python/` | Python 客户端(PyPI 上的 `libfinance`) |
167
169
  | `cpp/` | C++ 客户端 |
168
- | `conformance/` | 同一组调用分别经 Python 与 C++ 客户端发给同一个服务端,逐项比对答案 |
169
170
  | `docs/` | 文档(中英双语) |
@@ -109,10 +109,11 @@ get_trading_dates("2024-01-01", "2024-01-31", market="us") # 不涉及具体
109
109
  | 分红 / 拆股 / 配股 | CN · US | `get_dividends` | 除权事件,价格跳空的来源 |
110
110
  | 分拆 | US | `get_spinoffs` | A 股不产生这类事件 |
111
111
  | 财务报表(PIT) | CN | `get_pit_financials_ex` | 按季度取,带修订历史 |
112
- | 财务衍生指标 | CN、US | `get_financial_metrics` | 按交易日的财务衍生指标(RQData get_factor 同形) |
112
+ | 财务衍生指标 | CN、US | `get_financial_metrics` | 按交易日的财务衍生指标 |
113
+ | 因子 | CN | `get_factor_exposure`、`list_factor_libraries`、`list_factors` | qlib、alpha158、Barra CNE5 风格 / 行业因子与描述符 |
113
114
  | 行业分类 | CN / US | `get_instrument_industry`、`get_industry_constituents`、`get_industry_weights` | 申万、GICS 等分类体系 |
114
115
  | 指数成分与权重 | CN | `get_index_weights` | 任意交易日的成分权重 |
115
- | 概念板块成分 | CN | `get_concept_weights` | 同花顺概念分类 |
116
+ | 概念板块成分 | CN | `get_concept_weights` | THS 概念分类 |
116
117
  | 实时行情 | CN | `QuoteApi` | 订阅推送 |
117
118
 
118
119
  覆盖到哪一年、更新到哪一天,取决于你连的那个服务。**不要照抄文档里的日期**,用[数据新鲜度](https://libfinance.readthedocs.io/zh-cn/latest/data/freshness.html)里的两个函数自己查。
@@ -150,7 +151,7 @@ API 参考按合约信息和交易日历、行情信息、基本面信息、行
150
151
  | 目录 | 内容 |
151
152
  | --- | --- |
152
153
  | `contract/` | 各语言客户端共同遵守的契约:公开函数(名称、参数顺序与默认值、返回)与它们调用的服务端函数 |
154
+ | `contract/conformance/` | 同一组调用分别经 Python 与 C++ 客户端发给同一个服务端,逐项比对答案 |
153
155
  | `python/` | Python 客户端(PyPI 上的 `libfinance`) |
154
156
  | `cpp/` | C++ 客户端 |
155
- | `conformance/` | 同一组调用分别经 Python 与 C++ 客户端发给同一个服务端,逐项比对答案 |
156
157
  | `docs/` | 文档(中英双语) |
@@ -0,0 +1 @@
1
+ 0.1.2
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ r"""日频因子:qlib、alpha158、Barra CNE5 风格 / 行业因子与描述符(factors-daybar)。
4
+
5
+ 因子名写全名:库 ``owner/library[@rev]``\ (展开为它的全部因子)或因子 ``owner/library/factor[@rev]``\ ,
6
+ 可以混在一次请求里;\ ``list_factor_libraries``\ 、\ ``list_factors`` 列出可用的名字。值为 float32,NaN 表示
7
+ 在 universe 中但无法计算。
8
+
9
+ 截面因子(如 ``system/barra-cne5`` 的风格因子)的值取决于截面里有哪些股票,由服务端在读取时计算:默认是
10
+ 当天全部 A 股,传 ``universe`` 时在这批代码上重算。时序因子(qlib、alpha158、Barra 描述符、行业哑变量)
11
+ 不受 ``universe`` 影响。
12
+ """
13
+ from typing import List, Optional, Union
14
+
15
+ import pandas as pd
16
+
17
+ from libfinance.client import get_client
18
+ from libfinance.utils.cache import warn_if_clamped, warn_if_truncated
19
+ from libfinance.utils.decorators import export_as_api
20
+ from libfinance.utils.utils import to_date_str
21
+ from libfinance.utils.validators import ensure_list_of_string, ensure_string
22
+
23
+
24
+ @export_as_api
25
+ def get_factor_exposure(
26
+ order_book_ids: Union[str, List[str]],
27
+ factor_names: Union[str, List[str]],
28
+ start_date,
29
+ end_date,
30
+ universe: Optional[Union[str, List[str]]] = None,
31
+ ) -> pd.DataFrame:
32
+ r"""获取因子暴露。
33
+
34
+ :param order_book_ids: 单个代码或代码列表
35
+ :param factor_names: 库名(如 ``"system/barra-cne5"``\ )或因子全名(如 ``"system/qlib/MA5"``\ ),
36
+ 一个或一组,可以混合
37
+ :param start_date: 起始日期;与 ``end_date`` 相同时是当天的截面
38
+ :param end_date: 结束日期
39
+ :param universe: 截面因子的计算范围(代码列表);省略为当天全部 A 股。只影响截面因子
40
+
41
+ :returns: pandas.DataFrame,以 (order_book_id, date) 为索引,每个因子一列,列名为因子全名
42
+ (库展开为 ``owner/library/factor``\ )。
43
+ """
44
+ ids = ensure_list_of_string(order_book_ids, "order_book_ids")
45
+ if not ids:
46
+ raise ValueError("order_book_ids: at least one order book id expected")
47
+ names = ensure_list_of_string(factor_names, "factor_names")
48
+ if not names:
49
+ raise ValueError("factor_names: at least one factor name expected")
50
+ start, end = to_date_str(start_date), to_date_str(end_date)
51
+ if start > end:
52
+ raise ValueError("start_date {} is after end_date {}".format(start, end))
53
+ members = None if universe is None else ensure_list_of_string(universe, "universe")
54
+ warn_if_clamped("get_factor_exposure", start)
55
+ warn_if_truncated("get_factor_exposure", len(ids))
56
+ frame = get_client().get_factor_exposure(order_book_ids=ids, factor_names=names, start_date=start,
57
+ end_date=end, universe=members)
58
+ if not isinstance(frame, pd.DataFrame) or not {"order_book_id", "date"} <= set(frame.columns):
59
+ return frame
60
+ frame = frame.copy()
61
+ frame["date"] = pd.to_datetime(frame["date"])
62
+ return frame.set_index(["order_book_id", "date"])
63
+
64
+
65
+ @export_as_api
66
+ def list_factor_libraries() -> pd.DataFrame:
67
+ r"""列出因子库。
68
+
69
+ :returns: pandas.DataFrame,列为 name(库名)、version(当前版本)、factors(因子数)、description。
70
+ """
71
+ rows = get_client().list_factor_libraries()
72
+ return pd.DataFrame(list(rows or []), columns=["name", "version", "factors", "description"])
73
+
74
+
75
+ @export_as_api
76
+ def list_factors(library: Optional[str] = None) -> List[str]:
77
+ r"""列出因子全名。
78
+
79
+ :param library: 库名,如 ``"system/barra-cne5"``\ ;省略为全部库
80
+
81
+ :returns: 带版本的因子全名列表,如 ``["system/barra-cne5/SIZE@v2.0.0", ...]``\ ;去掉 ``@版本`` 即读取当前版本
82
+ """
83
+ if library is not None:
84
+ library = ensure_string(library, "library")
85
+ return list(get_client().list_factors(library=library) or [])
@@ -74,7 +74,7 @@ def get_financial_metrics(
74
74
  start_date=None,
75
75
  end_date=None,
76
76
  ) -> pd.DataFrame:
77
- r"""获取财务衍生指标(RQData ``get_factor`` 同形),CN 与 US 同一套公式。
77
+ r"""获取财务衍生指标,CN 与 US 同一套公式。
78
78
 
79
79
  每个交易日的值来自那一天收盘后能看到的最新报告:数值在公告日跳变、其间持平;最新报告算不出的为 NaN,
80
80
  不退回更早的报告。
@@ -124,14 +124,14 @@ def get_price(
124
124
  字段由服务端核对。
125
125
  :param skip_suspended: 是否去掉无成交的日子(成交量为 0),默认 False。只发布收盘点位、没有成交量
126
126
  的指数日不算停牌。
127
- :param include_now: 对日线没有影响,保留与 rqdata 的 ``get_price`` 对齐
127
+ :param include_now: 对日线没有影响,保留以兼容常见的 ``get_price`` 签名
128
128
  :param adjust_type: 股票的复权方式:\ ``"pre"`` 前复权(默认)、\ ``"post"`` 后复权或 ``"none"`` 原始价;
129
129
  指数原样返回
130
130
  :param adjust_orig: 前复权的基准日;省略时为 ``end_date``\ :价格换算到查询区间最后一天的口径,
131
131
  区间之后的除权不影响结果(point-in-time)
132
132
  :returns: 以 ``(order_book_id, datetime)`` 为索引的 DataFrame,列为 ``permanent_id`` 与所选字段。
133
133
 
134
- 规则同 rqalpha:成交量随复权反向缩放,成交额 ``turnover`` 不受复权影响。
134
+ 复权规则:成交量随复权反向缩放,成交额 ``turnover`` 不受复权影响。
135
135
  """
136
136
  ids = ensure_list_of_string(order_book_ids, "order_book_ids")
137
137
  if not ids:
@@ -8,7 +8,7 @@ from libfinance.subscribe.md_protocol import Quote
8
8
  Quote 的字段集与订阅路径统一(定义见 libfinance/subscribe/md_protocol.py,对齐 dynamics):
9
9
 
10
10
  data_time: int
11
- instrument_id: str exchange_id: str # rqdata 风格,如 600000 / XSHG
11
+ instrument_id: str exchange_id: str # 代码与交易所后缀,如 600000 / XSHG
12
12
  instrument_type: int # InstrumentType 枚举
13
13
  pre_close_price / pre_settlement_price / last_price / volume / turnover
14
14
  pre_open_interest / open_interest
@@ -48,7 +48,7 @@ def get_instrument_themes(order_book_ids, source: Optional[str] = None, as_of=No
48
48
  def get_theme_weights(order_book_id: str, as_of=None) -> pd.DataFrame:
49
49
  r"""获取主题在指定日期的成分权重。
50
50
 
51
- 每一行都带 ``methodology``\ :同花顺主题的 ``derived_equal_weight`` 是由成分名单推出的等权,
51
+ 每一行都带 ``methodology``\ :THS 主题的 ``derived_equal_weight`` 是由成分名单推出的等权,
52
52
  不是供应商权重。
53
53
 
54
54
  :param order_book_id: 主题代码,如 ``"885311.THS"``
@@ -147,7 +147,7 @@ class InstrumentType(IntEnum):
147
147
  CryptoUFuture = 11
148
148
 
149
149
 
150
- # 交易所标识:全系统统一 rqdata 风格(order_book_id 后缀)。
150
+ # 交易所标识:全系统统一用 order_book_id 的后缀。
151
151
  EXCHANGE_OF_MARKET = {
152
152
  MarketType.SSE: "XSHG",
153
153
  MarketType.SZE: "XSHE",
@@ -185,7 +185,7 @@ class _Coded:
185
185
 
186
186
  @property
187
187
  def order_book_id(self) -> str:
188
- """rqdata 风格标识,如 600519.XSHG。"""
188
+ """order_book_id,如 600519.XSHG。"""
189
189
  return "{}.{}".format(self.instrument_id, self.exchange_id) if self.instrument_id else ""
190
190
 
191
191
 
@@ -20,7 +20,7 @@ quote_api.py — dynamics 实时行情订阅(纯 Python,XTP 风格,协议
20
20
  也可以 `login(token)` / `login(provider)` 自己提供票据,或 `connect("host:port,host:port")` 指定网关地址。
21
21
 
22
22
  代码是 libfinance 统一的 order_book_id(``600519.XSHG``),与 get_price、instruments 等函数同一种写法;
23
- 一次订阅可混合交易所。后缀即交易所(rqdata 风格):XSHG(上交所) / XSHE(深交所) / XBSE(北交所) 及期货交易所,
23
+ 一次订阅可混合交易所。后缀即交易所(order_book_id 的后缀):XSHG(上交所) / XSHE(深交所) / XBSE(北交所) 及期货交易所,
24
24
  如 000001.XSHG 是上证指数、000001.XSHE 是平安银行。行情、回执与缺口通知都带 ``order_book_id``。
25
25
 
26
26
  断线自愈(7×24):连接断开后自动重连(多个网关地址时立即切到下一个),自动重新登录,并按 seq
@@ -159,3 +159,19 @@ def warn_if_clamped(api_name, start_date):
159
159
  stacklevel=3,
160
160
  )
161
161
  return boundary
162
+
163
+
164
+ def warn_if_truncated(api_name, count):
165
+ """当前权限一次只查前 max_count 个代码时给一句警告(服务端截断,不报错)。返回上限,没有限制则返回 None。"""
166
+ import warnings
167
+
168
+ limit = (limits_for(api_name) or {}).get("clamp_instrument_count") or {}
169
+ max_count = limit.get("max_count")
170
+ if not isinstance(max_count, int) or max_count <= 0 or count <= max_count:
171
+ return None
172
+ warnings.warn(
173
+ "{}: 当前权限一次最多 {} 个代码,传入的 {} 个只有前 {} 个会被查询;请分批调用。"
174
+ .format(api_name, max_count, count, max_count),
175
+ stacklevel=3,
176
+ )
177
+ return max_count
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: libfinance
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: libfinance is a modern finance API
5
5
  Description-Content-Type: text/markdown
6
6
  Requires-Dist: lz4
@@ -122,10 +122,11 @@ get_trading_dates("2024-01-01", "2024-01-31", market="us") # 不涉及具体
122
122
  | 分红 / 拆股 / 配股 | CN · US | `get_dividends` | 除权事件,价格跳空的来源 |
123
123
  | 分拆 | US | `get_spinoffs` | A 股不产生这类事件 |
124
124
  | 财务报表(PIT) | CN | `get_pit_financials_ex` | 按季度取,带修订历史 |
125
- | 财务衍生指标 | CN、US | `get_financial_metrics` | 按交易日的财务衍生指标(RQData get_factor 同形) |
125
+ | 财务衍生指标 | CN、US | `get_financial_metrics` | 按交易日的财务衍生指标 |
126
+ | 因子 | CN | `get_factor_exposure`、`list_factor_libraries`、`list_factors` | qlib、alpha158、Barra CNE5 风格 / 行业因子与描述符 |
126
127
  | 行业分类 | CN / US | `get_instrument_industry`、`get_industry_constituents`、`get_industry_weights` | 申万、GICS 等分类体系 |
127
128
  | 指数成分与权重 | CN | `get_index_weights` | 任意交易日的成分权重 |
128
- | 概念板块成分 | CN | `get_concept_weights` | 同花顺概念分类 |
129
+ | 概念板块成分 | CN | `get_concept_weights` | THS 概念分类 |
129
130
  | 实时行情 | CN | `QuoteApi` | 订阅推送 |
130
131
 
131
132
  覆盖到哪一年、更新到哪一天,取决于你连的那个服务。**不要照抄文档里的日期**,用[数据新鲜度](https://libfinance.readthedocs.io/zh-cn/latest/data/freshness.html)里的两个函数自己查。
@@ -163,7 +164,7 @@ API 参考按合约信息和交易日历、行情信息、基本面信息、行
163
164
  | 目录 | 内容 |
164
165
  | --- | --- |
165
166
  | `contract/` | 各语言客户端共同遵守的契约:公开函数(名称、参数顺序与默认值、返回)与它们调用的服务端函数 |
167
+ | `contract/conformance/` | 同一组调用分别经 Python 与 C++ 客户端发给同一个服务端,逐项比对答案 |
166
168
  | `python/` | Python 客户端(PyPI 上的 `libfinance`) |
167
169
  | `cpp/` | C++ 客户端 |
168
- | `conformance/` | 同一组调用分别经 Python 与 C++ 客户端发给同一个服务端,逐项比对答案 |
169
170
  | `docs/` | 文档(中英双语) |
@@ -13,6 +13,7 @@ libfinance/api/__init__.py
13
13
  libfinance/api/calendar.py
14
14
  libfinance/api/corporate_actions.py
15
15
  libfinance/api/exfactor.py
16
+ libfinance/api/factors.py
16
17
  libfinance/api/financials.py
17
18
  libfinance/api/get_price.py
18
19
  libfinance/api/index_components.py
@@ -213,6 +213,10 @@ def test_all_instruments_takes_the_backend_types_and_sources(monkeypatch):
213
213
  ("get_financial_metrics", {"order_book_ids": "600000.XSHG", "fields": "roe_lf",
214
214
  "start_date": "2024-11-01", "end_date": 20241105},
215
215
  {"order_book_ids": ["600000.XSHG"], "fields": ["roe_lf"], "start_date": "2024-11-01", "end_date": "2024-11-05"}),
216
+ ("get_factor_exposure", {"order_book_ids": "600000.XSHG", "factor_names": "system/barra-cne5",
217
+ "start_date": "2026-09-01", "end_date": 20260910},
218
+ {"order_book_ids": ["600000.XSHG"], "factor_names": ["system/barra-cne5"], "start_date": "2026-09-01",
219
+ "end_date": "2026-09-10", "universe": None}),
216
220
  ("get_instrument_industry", {"order_book_ids": ["600000.XSHG"], "level": 3, "as_of": "2024-06-28"},
217
221
  {"order_book_ids": ["600000.XSHG"], "level": 3, "as_of": "2024-06-28"}),
218
222
  ("get_instrument_indices", {"order_book_ids": "600000.XSHG", "source": "CSI"},
@@ -350,8 +354,8 @@ def test_as_of_keeps_a_timestamp(monkeypatch):
350
354
 
351
355
 
352
356
 
353
- def test_financial_frames_get_rqdatas_index_whichever_server_answers(monkeypatch):
354
- """The C++ server answers flat tables; the client indexes them as RQData does."""
357
+ def test_financial_frames_are_indexed_whichever_server_answers(monkeypatch):
358
+ """The C++ server answers flat tables; the client indexes them by (order_book_id, date | quarter)."""
355
359
  import pandas as pd
356
360
  from libfinance.api import financials
357
361
 
@@ -369,3 +373,53 @@ def test_financial_frames_get_rqdatas_index_whichever_server_answers(monkeypatch
369
373
  assert metrics.index.names == ["order_book_id", "date"] and list(metrics.roe_lf) == [0.1, 0.2]
370
374
  pit = financials.get_pit_financials_ex("A", "assets", "2024q1", "2024q1")
371
375
  assert pit.index.names == ["order_book_id", "quarter"] and list(pit.columns) == ["info_date", "assets", "if_adjusted"]
376
+
377
+
378
+ def test_factor_exposure_is_indexed_and_says_when_the_tier_cuts_it(monkeypatch):
379
+ """服务端回扁平表;客户端按 (order_book_id, date) 建索引、保持服务端的行序。免费层夹日期窗口、截断代码数时
380
+ 服务端不报错,客户端各给一句警告——否则"没数据"和"档位不够"分不出来。"""
381
+ import pandas as pd
382
+ from libfinance.api import factors
383
+ from libfinance.utils import cache
384
+
385
+ calls = []
386
+
387
+ class Client:
388
+ def get_factor_exposure(self, **kwargs):
389
+ calls.append(kwargs)
390
+ return pd.DataFrame([{"order_book_id": "B", "date": "2026-09-10", "system/barra-cne5/SIZE": 1.4},
391
+ {"order_book_id": "A", "date": "2026-09-10", "system/barra-cne5/SIZE": 1.5}])
392
+
393
+ monkeypatch.setattr(factors, "get_client", lambda: Client())
394
+ monkeypatch.setattr(cache, "limits_for", lambda api: {"clamp_date_window": {"years": 1},
395
+ "clamp_instrument_count": {"max_count": 2}})
396
+ with pytest.warns(UserWarning) as caught:
397
+ frame = factors.get_factor_exposure(["B", "A", "C"], ["system/barra-cne5"], "2000-01-04", "2026-09-10",
398
+ universe=["A", "B", "C"])
399
+ messages = [str(w.message) for w in caught]
400
+ assert any("可查区间的起点" in m for m in messages) and any("最多 2 个代码" in m for m in messages)
401
+ assert calls[0]["universe"] == ["A", "B", "C"]
402
+ assert frame.index.names == ["order_book_id", "date"]
403
+ assert list(frame.index.get_level_values(0)) == ["B", "A"]
404
+ assert list(frame.columns) == ["system/barra-cne5/SIZE"]
405
+ with pytest.raises(ValueError, match="after end_date"):
406
+ factors.get_factor_exposure("A", "system/qlib", "2026-09-10", "2026-09-01")
407
+ with pytest.raises(ValueError, match="factor_names"):
408
+ factors.get_factor_exposure("A", [], "2026-09-10", "2026-09-10")
409
+
410
+
411
+ def test_factor_listings(monkeypatch):
412
+ from libfinance.api import factors
413
+
414
+ class Client:
415
+ def list_factor_libraries(self):
416
+ return [{"name": "system/barra-cne5", "version": "v2.0.0", "factors": 42, "description": ""}]
417
+
418
+ def list_factors(self, library=None):
419
+ return ["system/barra-cne5/SIZE"] if library == "system/barra-cne5" else []
420
+
421
+ monkeypatch.setattr(factors, "get_client", lambda: Client())
422
+ libraries = factors.list_factor_libraries()
423
+ assert list(libraries.columns) == ["name", "version", "factors", "description"]
424
+ assert libraries.factors.tolist() == [42]
425
+ assert factors.list_factors("system/barra-cne5") == ["system/barra-cne5/SIZE"]
libfinance-0.1.1/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.1.1
File without changes
File without changes
File without changes