alphafeed 0.1.0.dev0__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.
- alphafeed-0.1.0.dev0/PKG-INFO +175 -0
- alphafeed-0.1.0.dev0/README.md +148 -0
- alphafeed-0.1.0.dev0/alphafeed/__init__.py +53 -0
- alphafeed-0.1.0.dev0/alphafeed/__version__.py +3 -0
- alphafeed-0.1.0.dev0/alphafeed/_base_client.py +305 -0
- alphafeed-0.1.0.dev0/alphafeed/_batch.py +87 -0
- alphafeed-0.1.0.dev0/alphafeed/_cache.py +130 -0
- alphafeed-0.1.0.dev0/alphafeed/_exceptions.py +140 -0
- alphafeed-0.1.0.dev0/alphafeed/_types.py +55 -0
- alphafeed-0.1.0.dev0/alphafeed/client.py +140 -0
- alphafeed-0.1.0.dev0/alphafeed/models.py +101 -0
- alphafeed-0.1.0.dev0/alphafeed/resources/__init__.py +13 -0
- alphafeed-0.1.0.dev0/alphafeed/resources/_base.py +17 -0
- alphafeed-0.1.0.dev0/alphafeed/resources/depth.py +44 -0
- alphafeed-0.1.0.dev0/alphafeed/resources/instruments.py +99 -0
- alphafeed-0.1.0.dev0/alphafeed/resources/klines.py +724 -0
- alphafeed-0.1.0.dev0/alphafeed/resources/quotes.py +257 -0
- alphafeed-0.1.0.dev0/alphafeed/utils.py +53 -0
- alphafeed-0.1.0.dev0/alphafeed.egg-info/PKG-INFO +175 -0
- alphafeed-0.1.0.dev0/alphafeed.egg-info/SOURCES.txt +23 -0
- alphafeed-0.1.0.dev0/alphafeed.egg-info/dependency_links.txt +1 -0
- alphafeed-0.1.0.dev0/alphafeed.egg-info/requires.txt +4 -0
- alphafeed-0.1.0.dev0/alphafeed.egg-info/top_level.txt +1 -0
- alphafeed-0.1.0.dev0/pyproject.toml +41 -0
- alphafeed-0.1.0.dev0/setup.cfg +4 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: alphafeed
|
|
3
|
+
Version: 0.1.0.dev0
|
|
4
|
+
Summary: AlphaFeed Python SDK
|
|
5
|
+
Author: AlphaFeed
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Documentation, https://docs.alphafeed.org
|
|
8
|
+
Keywords: finance,stock,market-data,quant,kline,alphafeed,a-share,us-stock,hk-stock,real-time-quotes
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Requires-Dist: httpx>=0.25.0
|
|
24
|
+
Requires-Dist: typing-extensions>=4.0.0
|
|
25
|
+
Requires-Dist: pandas>=1.5.0
|
|
26
|
+
Requires-Dist: tqdm>=4.60.0
|
|
27
|
+
|
|
28
|
+
# AlphaFeed Python SDK
|
|
29
|
+
|
|
30
|
+
AlphaFeed Python SDK 是 AlphaFeed 金融市场数据 API 的 Python 客户端,支持 A 股、ETF、美股、港股。
|
|
31
|
+
|
|
32
|
+
> **完整文档**:<https://docs.alphafeed.org>
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 安装
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install alphafeed
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
SDK 支持 Python 3.9+,推荐 3.10 或更高版本。内置 pandas 和 tqdm 支持。
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 初始化
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from alphafeed import AlphaFeed
|
|
50
|
+
|
|
51
|
+
af = AlphaFeed(api_key="your-api-key")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
也支持环境变量:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
export ALPHAFEED_API_KEY="your-api-key"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from alphafeed import AlphaFeed
|
|
62
|
+
af = AlphaFeed() # 自动读取 ALPHAFEED_API_KEY
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 标的代码格式
|
|
68
|
+
|
|
69
|
+
| 示例 | 说明 |
|
|
70
|
+
|------|------|
|
|
71
|
+
| `600000.SH` | 上交所 |
|
|
72
|
+
| `000001.SZ` | 深交所 |
|
|
73
|
+
| `AAPL.US` | 美股 |
|
|
74
|
+
| `00700.HK` | 港股 |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 基础用法
|
|
79
|
+
|
|
80
|
+
### K 线获取
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from alphafeed import AlphaFeed
|
|
84
|
+
|
|
85
|
+
af = AlphaFeed(api_key="your-api-key")
|
|
86
|
+
|
|
87
|
+
# 日 K 线,返回 DataFrame
|
|
88
|
+
df = af.klines.get("600000.SH", period="1d", count=100, to_dataframe=True)
|
|
89
|
+
print(df.tail())
|
|
90
|
+
|
|
91
|
+
# 分钟 K 线
|
|
92
|
+
df = af.klines.get("600000.SH", period="5m", count=100, to_dataframe=True)
|
|
93
|
+
|
|
94
|
+
# 前复权(默认)
|
|
95
|
+
df = af.klines.get("600519.SH", adjust="forward", to_dataframe=True)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**批量获取**:多只标的一次拉取:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
symbols = ["600000.SH", "000001.SZ", "600519.SH"]
|
|
102
|
+
dfs = af.klines.batch(symbols, period="1d", count=100, to_dataframe=True, show_progress=True)
|
|
103
|
+
print(dfs["600000.SH"].tail())
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 日内分时
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
# 当日分钟线
|
|
110
|
+
df = af.klines.intraday("600000.SH", to_dataframe=True)
|
|
111
|
+
print(df.tail())
|
|
112
|
+
|
|
113
|
+
# 批量日内分时
|
|
114
|
+
dfs = af.klines.intraday_batch(["600000.SH", "000001.SZ"], to_dataframe=True)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 实时行情
|
|
118
|
+
|
|
119
|
+
**按标的代码查询**
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
quotes = af.quotes.get(symbols=["600000.SH", "000001.SZ", "00700.HK"], to_dataframe=True)
|
|
123
|
+
print(quotes)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**按标的池查询**
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# 全部 A 股行情
|
|
130
|
+
quotes_a = af.quotes.get(universes=["CN_Stock"], to_dataframe=True)
|
|
131
|
+
print(quotes_a)
|
|
132
|
+
|
|
133
|
+
# 全部 ETF 行情
|
|
134
|
+
quotes_etf = af.quotes.get(universes=["CN_ETF"], to_dataframe=True)
|
|
135
|
+
print(quotes_etf)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
支持的标的池:`CN_Stock`(A股)、`US_Stock`(美股)、`HK_Stock`(港股)、`CN_ETF`(ETF)
|
|
139
|
+
|
|
140
|
+
### 五档盘口
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
depth = af.depth.get("600000.SH")
|
|
144
|
+
print("买盘:", depth["bids"][:5])
|
|
145
|
+
print("卖盘:", depth["asks"][:5])
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 标的信息
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
instruments = af.instruments.batch(symbols=["600000.SH", "000001.SZ", "00700.HK"])
|
|
152
|
+
for inst in instruments:
|
|
153
|
+
print(f"{inst['symbol']}: {inst['name']}")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 除权因子
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
factors = af.klines.ex_factors(symbols=["600000.SH", "600519.SH"], to_dataframe=True)
|
|
160
|
+
print(factors.tail())
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 更多文档
|
|
166
|
+
|
|
167
|
+
- [Python SDK 快速开始](https://docs.alphafeed.org/zh-Hans/sdk/python-quickstart)
|
|
168
|
+
- [使用示例](https://docs.alphafeed.org/zh-Hans/sdk/python-examples)
|
|
169
|
+
- [HTTP API 说明](https://docs.alphafeed.org/zh-Hans/api-reference/introduction)
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
MIT
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# AlphaFeed Python SDK
|
|
2
|
+
|
|
3
|
+
AlphaFeed Python SDK 是 AlphaFeed 金融市场数据 API 的 Python 客户端,支持 A 股、ETF、美股、港股。
|
|
4
|
+
|
|
5
|
+
> **完整文档**:<https://docs.alphafeed.org>
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install alphafeed
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
SDK 支持 Python 3.9+,推荐 3.10 或更高版本。内置 pandas 和 tqdm 支持。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 初始化
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from alphafeed import AlphaFeed
|
|
23
|
+
|
|
24
|
+
af = AlphaFeed(api_key="your-api-key")
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
也支持环境变量:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
export ALPHAFEED_API_KEY="your-api-key"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from alphafeed import AlphaFeed
|
|
35
|
+
af = AlphaFeed() # 自动读取 ALPHAFEED_API_KEY
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 标的代码格式
|
|
41
|
+
|
|
42
|
+
| 示例 | 说明 |
|
|
43
|
+
|------|------|
|
|
44
|
+
| `600000.SH` | 上交所 |
|
|
45
|
+
| `000001.SZ` | 深交所 |
|
|
46
|
+
| `AAPL.US` | 美股 |
|
|
47
|
+
| `00700.HK` | 港股 |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 基础用法
|
|
52
|
+
|
|
53
|
+
### K 线获取
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from alphafeed import AlphaFeed
|
|
57
|
+
|
|
58
|
+
af = AlphaFeed(api_key="your-api-key")
|
|
59
|
+
|
|
60
|
+
# 日 K 线,返回 DataFrame
|
|
61
|
+
df = af.klines.get("600000.SH", period="1d", count=100, to_dataframe=True)
|
|
62
|
+
print(df.tail())
|
|
63
|
+
|
|
64
|
+
# 分钟 K 线
|
|
65
|
+
df = af.klines.get("600000.SH", period="5m", count=100, to_dataframe=True)
|
|
66
|
+
|
|
67
|
+
# 前复权(默认)
|
|
68
|
+
df = af.klines.get("600519.SH", adjust="forward", to_dataframe=True)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**批量获取**:多只标的一次拉取:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
symbols = ["600000.SH", "000001.SZ", "600519.SH"]
|
|
75
|
+
dfs = af.klines.batch(symbols, period="1d", count=100, to_dataframe=True, show_progress=True)
|
|
76
|
+
print(dfs["600000.SH"].tail())
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 日内分时
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
# 当日分钟线
|
|
83
|
+
df = af.klines.intraday("600000.SH", to_dataframe=True)
|
|
84
|
+
print(df.tail())
|
|
85
|
+
|
|
86
|
+
# 批量日内分时
|
|
87
|
+
dfs = af.klines.intraday_batch(["600000.SH", "000001.SZ"], to_dataframe=True)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 实时行情
|
|
91
|
+
|
|
92
|
+
**按标的代码查询**
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
quotes = af.quotes.get(symbols=["600000.SH", "000001.SZ", "00700.HK"], to_dataframe=True)
|
|
96
|
+
print(quotes)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**按标的池查询**
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
# 全部 A 股行情
|
|
103
|
+
quotes_a = af.quotes.get(universes=["CN_Stock"], to_dataframe=True)
|
|
104
|
+
print(quotes_a)
|
|
105
|
+
|
|
106
|
+
# 全部 ETF 行情
|
|
107
|
+
quotes_etf = af.quotes.get(universes=["CN_ETF"], to_dataframe=True)
|
|
108
|
+
print(quotes_etf)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
支持的标的池:`CN_Stock`(A股)、`US_Stock`(美股)、`HK_Stock`(港股)、`CN_ETF`(ETF)
|
|
112
|
+
|
|
113
|
+
### 五档盘口
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
depth = af.depth.get("600000.SH")
|
|
117
|
+
print("买盘:", depth["bids"][:5])
|
|
118
|
+
print("卖盘:", depth["asks"][:5])
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 标的信息
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
instruments = af.instruments.batch(symbols=["600000.SH", "000001.SZ", "00700.HK"])
|
|
125
|
+
for inst in instruments:
|
|
126
|
+
print(f"{inst['symbol']}: {inst['name']}")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 除权因子
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
factors = af.klines.ex_factors(symbols=["600000.SH", "600519.SH"], to_dataframe=True)
|
|
133
|
+
print(factors.tail())
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 更多文档
|
|
139
|
+
|
|
140
|
+
- [Python SDK 快速开始](https://docs.alphafeed.org/zh-Hans/sdk/python-quickstart)
|
|
141
|
+
- [使用示例](https://docs.alphafeed.org/zh-Hans/sdk/python-examples)
|
|
142
|
+
- [HTTP API 说明](https://docs.alphafeed.org/zh-Hans/api-reference/introduction)
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""AlphaFeed Python SDK - 高性能行情数据客户端。
|
|
2
|
+
|
|
3
|
+
支持 A股、ETF、美股、港股的行情数据查询。
|
|
4
|
+
|
|
5
|
+
Examples
|
|
6
|
+
--------
|
|
7
|
+
>>> from alphafeed import AlphaFeed
|
|
8
|
+
>>> client = AlphaFeed(api_key="your-api-key")
|
|
9
|
+
>>> df = client.klines.get("600000.SH", to_dataframe=True)
|
|
10
|
+
>>> print(df.tail())
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .__version__ import __version__
|
|
14
|
+
from ._exceptions import (
|
|
15
|
+
AlphaFeedError,
|
|
16
|
+
APIError,
|
|
17
|
+
AuthenticationError,
|
|
18
|
+
BadRequestError,
|
|
19
|
+
ConnectionError,
|
|
20
|
+
InternalServerError,
|
|
21
|
+
NotFoundError,
|
|
22
|
+
PermissionError,
|
|
23
|
+
RateLimitError,
|
|
24
|
+
TimeoutError,
|
|
25
|
+
)
|
|
26
|
+
from .client import AlphaFeed
|
|
27
|
+
from .models import (
|
|
28
|
+
AdjustType,
|
|
29
|
+
CompactKlineData,
|
|
30
|
+
Instrument,
|
|
31
|
+
Period,
|
|
32
|
+
Quote,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"__version__",
|
|
37
|
+
"AlphaFeed",
|
|
38
|
+
"AlphaFeedError",
|
|
39
|
+
"APIError",
|
|
40
|
+
"AuthenticationError",
|
|
41
|
+
"PermissionError",
|
|
42
|
+
"NotFoundError",
|
|
43
|
+
"BadRequestError",
|
|
44
|
+
"RateLimitError",
|
|
45
|
+
"InternalServerError",
|
|
46
|
+
"ConnectionError",
|
|
47
|
+
"TimeoutError",
|
|
48
|
+
"AdjustType",
|
|
49
|
+
"CompactKlineData",
|
|
50
|
+
"Instrument",
|
|
51
|
+
"Period",
|
|
52
|
+
"Quote",
|
|
53
|
+
]
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""Base HTTP client implementation with retry support."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import random
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Optional, Union
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from . import __version__
|
|
13
|
+
from ._exceptions import (
|
|
14
|
+
APIError,
|
|
15
|
+
ConnectionError,
|
|
16
|
+
InternalServerError,
|
|
17
|
+
RateLimitError,
|
|
18
|
+
TimeoutError,
|
|
19
|
+
raise_for_status,
|
|
20
|
+
)
|
|
21
|
+
from ._types import NOT_GIVEN, Headers, NotGiven, Query, Timeout
|
|
22
|
+
|
|
23
|
+
__all__ = ["SyncAPIClient"]
|
|
24
|
+
|
|
25
|
+
DEFAULT_BASE_URL = "https://api.alphafeed.org"
|
|
26
|
+
DEFAULT_TIMEOUT = 30.0
|
|
27
|
+
DEFAULT_MAX_RETRIES = 3
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _should_retry(exception: Exception) -> bool:
|
|
31
|
+
"""Determine if an exception is retryable.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
exception : Exception
|
|
36
|
+
The exception to check.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
bool
|
|
41
|
+
True if the request should be retried.
|
|
42
|
+
"""
|
|
43
|
+
if isinstance(exception, (ConnectionError, TimeoutError)):
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
if isinstance(exception, (InternalServerError, RateLimitError)):
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _calculate_retry_delay(
|
|
53
|
+
attempt: int, base_delay: float = 1.0, max_delay: float = 30.0
|
|
54
|
+
) -> float:
|
|
55
|
+
"""Calculate exponential backoff delay with jitter.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
attempt : int
|
|
60
|
+
Current attempt number (0-indexed).
|
|
61
|
+
base_delay : float
|
|
62
|
+
Base delay in seconds.
|
|
63
|
+
max_delay : float
|
|
64
|
+
Maximum delay in seconds.
|
|
65
|
+
|
|
66
|
+
Returns
|
|
67
|
+
-------
|
|
68
|
+
float
|
|
69
|
+
Delay in seconds.
|
|
70
|
+
"""
|
|
71
|
+
delay = base_delay * (2**attempt)
|
|
72
|
+
jitter = delay * 0.25 * (2 * random.random() - 1)
|
|
73
|
+
delay = delay + jitter
|
|
74
|
+
return min(delay, max_delay)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class BaseClient:
|
|
78
|
+
"""Base class with shared configuration for API clients."""
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
api_key: Optional[str] = None,
|
|
83
|
+
base_url: Optional[str] = None,
|
|
84
|
+
timeout: Timeout = DEFAULT_TIMEOUT,
|
|
85
|
+
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
86
|
+
default_headers: Optional[Headers] = None,
|
|
87
|
+
) -> None:
|
|
88
|
+
if api_key is None:
|
|
89
|
+
self.api_key = os.environ.get("ALPHAFEED_API_KEY")
|
|
90
|
+
else:
|
|
91
|
+
self.api_key = api_key
|
|
92
|
+
|
|
93
|
+
if not self.api_key:
|
|
94
|
+
effective_base_url = base_url or os.environ.get("ALPHAFEED_BASE_URL")
|
|
95
|
+
if effective_base_url is None or effective_base_url == DEFAULT_BASE_URL:
|
|
96
|
+
raise ValueError(
|
|
97
|
+
"API key is required. Pass `api_key` or set ALPHAFEED_API_KEY environment variable."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
self.base_url = (
|
|
101
|
+
base_url or os.environ.get("ALPHAFEED_BASE_URL") or DEFAULT_BASE_URL
|
|
102
|
+
).rstrip("/")
|
|
103
|
+
self.timeout = timeout
|
|
104
|
+
self.max_retries = max_retries
|
|
105
|
+
self._default_headers = dict(default_headers) if default_headers else {}
|
|
106
|
+
|
|
107
|
+
def _build_headers(self, extra_headers: Optional[Headers] = None) -> dict[str, str]:
|
|
108
|
+
"""Build request headers with authentication."""
|
|
109
|
+
headers = {
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
"Accept": "application/json",
|
|
112
|
+
"User-Agent": f"alphafeed-python/{__version__}",
|
|
113
|
+
**self._default_headers,
|
|
114
|
+
}
|
|
115
|
+
if self.api_key:
|
|
116
|
+
headers["x-api-key"] = self.api_key
|
|
117
|
+
if extra_headers:
|
|
118
|
+
headers.update(extra_headers)
|
|
119
|
+
return headers
|
|
120
|
+
|
|
121
|
+
def _build_url(self, path: str) -> str:
|
|
122
|
+
"""Build full URL from path."""
|
|
123
|
+
return f"{self.base_url}{path}"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class SyncAPIClient(BaseClient):
|
|
127
|
+
"""Synchronous HTTP client for AlphaFeed API with automatic retry.
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
----------
|
|
131
|
+
api_key : str, optional
|
|
132
|
+
API key for authentication. If not provided, reads from ALPHAFEED_API_KEY
|
|
133
|
+
environment variable.
|
|
134
|
+
base_url : str, optional
|
|
135
|
+
Base URL for the API. Defaults to https://api.alphafeed.org.
|
|
136
|
+
timeout : float, optional
|
|
137
|
+
Request timeout in seconds. Defaults to 30.0.
|
|
138
|
+
max_retries : int, optional
|
|
139
|
+
Maximum number of retry attempts for failed requests. Defaults to 3.
|
|
140
|
+
Retries occur on connection errors, timeouts, server errors (5xx),
|
|
141
|
+
and rate limits (429).
|
|
142
|
+
default_headers : dict, optional
|
|
143
|
+
Default headers to include in all requests.
|
|
144
|
+
|
|
145
|
+
Examples
|
|
146
|
+
--------
|
|
147
|
+
>>> client = SyncAPIClient(api_key="your-api-key")
|
|
148
|
+
>>> response = client.get("/v1/klines", params={"symbol": "600000.SH"})
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def __init__(
|
|
152
|
+
self,
|
|
153
|
+
api_key: Optional[str] = None,
|
|
154
|
+
base_url: Optional[str] = None,
|
|
155
|
+
timeout: Timeout = DEFAULT_TIMEOUT,
|
|
156
|
+
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
157
|
+
default_headers: Optional[Headers] = None,
|
|
158
|
+
) -> None:
|
|
159
|
+
super().__init__(api_key, base_url, timeout, max_retries, default_headers)
|
|
160
|
+
self._client = httpx.Client(timeout=timeout)
|
|
161
|
+
|
|
162
|
+
def __enter__(self) -> "SyncAPIClient":
|
|
163
|
+
return self
|
|
164
|
+
|
|
165
|
+
def __exit__(self, *args: Any) -> None:
|
|
166
|
+
self.close()
|
|
167
|
+
|
|
168
|
+
def close(self) -> None:
|
|
169
|
+
"""Close the underlying HTTP client."""
|
|
170
|
+
self._client.close()
|
|
171
|
+
|
|
172
|
+
def _request(
|
|
173
|
+
self,
|
|
174
|
+
method: str,
|
|
175
|
+
path: str,
|
|
176
|
+
*,
|
|
177
|
+
params: Optional[Query] = None,
|
|
178
|
+
json: Optional[dict[str, Any]] = None,
|
|
179
|
+
extra_headers: Optional[Headers] = None,
|
|
180
|
+
timeout: Union[Timeout, NotGiven] = NOT_GIVEN,
|
|
181
|
+
max_retries: Union[int, NotGiven] = NOT_GIVEN,
|
|
182
|
+
) -> Any:
|
|
183
|
+
"""Make an HTTP request with automatic retry on failures.
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
method : str
|
|
188
|
+
HTTP method (GET, POST, etc.).
|
|
189
|
+
path : str
|
|
190
|
+
API endpoint path.
|
|
191
|
+
params : dict, optional
|
|
192
|
+
Query parameters.
|
|
193
|
+
json : dict, optional
|
|
194
|
+
JSON request body.
|
|
195
|
+
extra_headers : dict, optional
|
|
196
|
+
Additional headers for this request.
|
|
197
|
+
timeout : float, optional
|
|
198
|
+
Override timeout for this request.
|
|
199
|
+
max_retries : int, optional
|
|
200
|
+
Override max retries for this request.
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
Any
|
|
205
|
+
Parsed JSON response.
|
|
206
|
+
|
|
207
|
+
Raises
|
|
208
|
+
------
|
|
209
|
+
APIError
|
|
210
|
+
If the API returns an error response after all retries.
|
|
211
|
+
ConnectionError
|
|
212
|
+
If there's a network connection issue after all retries.
|
|
213
|
+
TimeoutError
|
|
214
|
+
If the request times out after all retries.
|
|
215
|
+
"""
|
|
216
|
+
url = self._build_url(path)
|
|
217
|
+
headers = self._build_headers(extra_headers)
|
|
218
|
+
request_timeout = timeout if not isinstance(timeout, NotGiven) else self.timeout
|
|
219
|
+
retries = (
|
|
220
|
+
max_retries if not isinstance(max_retries, NotGiven) else self.max_retries
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
if params:
|
|
224
|
+
params = {k: v for k, v in params.items() if v is not None}
|
|
225
|
+
|
|
226
|
+
last_exception: Optional[Exception] = None
|
|
227
|
+
|
|
228
|
+
for attempt in range(retries + 1):
|
|
229
|
+
try:
|
|
230
|
+
response = self._client.request(
|
|
231
|
+
method,
|
|
232
|
+
url,
|
|
233
|
+
params=params,
|
|
234
|
+
json=json,
|
|
235
|
+
headers=headers,
|
|
236
|
+
timeout=request_timeout,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
response_body = response.json()
|
|
241
|
+
except Exception:
|
|
242
|
+
response_body = {"message": response.text, "code": "PARSE_ERROR"}
|
|
243
|
+
|
|
244
|
+
raise_for_status(response.status_code, response_body)
|
|
245
|
+
|
|
246
|
+
return response_body
|
|
247
|
+
|
|
248
|
+
except httpx.ConnectError as e:
|
|
249
|
+
last_exception = ConnectionError(f"Failed to connect to {url}: {e}")
|
|
250
|
+
except httpx.TimeoutException as e:
|
|
251
|
+
last_exception = TimeoutError(f"Request to {url} timed out")
|
|
252
|
+
except APIError as e:
|
|
253
|
+
last_exception = e
|
|
254
|
+
if not _should_retry(e):
|
|
255
|
+
raise
|
|
256
|
+
|
|
257
|
+
if attempt < retries and _should_retry(last_exception):
|
|
258
|
+
delay = _calculate_retry_delay(attempt)
|
|
259
|
+
time.sleep(delay)
|
|
260
|
+
else:
|
|
261
|
+
break
|
|
262
|
+
|
|
263
|
+
if last_exception:
|
|
264
|
+
raise last_exception
|
|
265
|
+
raise RuntimeError("Unexpected state: no exception but request failed")
|
|
266
|
+
|
|
267
|
+
def get(
|
|
268
|
+
self,
|
|
269
|
+
path: str,
|
|
270
|
+
*,
|
|
271
|
+
params: Optional[Query] = None,
|
|
272
|
+
extra_headers: Optional[Headers] = None,
|
|
273
|
+
timeout: Union[Timeout, NotGiven] = NOT_GIVEN,
|
|
274
|
+
max_retries: Union[int, NotGiven] = NOT_GIVEN,
|
|
275
|
+
) -> Any:
|
|
276
|
+
"""Make a GET request with automatic retry."""
|
|
277
|
+
return self._request(
|
|
278
|
+
"GET",
|
|
279
|
+
path,
|
|
280
|
+
params=params,
|
|
281
|
+
extra_headers=extra_headers,
|
|
282
|
+
timeout=timeout,
|
|
283
|
+
max_retries=max_retries,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
def post(
|
|
287
|
+
self,
|
|
288
|
+
path: str,
|
|
289
|
+
*,
|
|
290
|
+
json: Optional[dict[str, Any]] = None,
|
|
291
|
+
params: Optional[Query] = None,
|
|
292
|
+
extra_headers: Optional[Headers] = None,
|
|
293
|
+
timeout: Union[Timeout, NotGiven] = NOT_GIVEN,
|
|
294
|
+
max_retries: Union[int, NotGiven] = NOT_GIVEN,
|
|
295
|
+
) -> Any:
|
|
296
|
+
"""Make a POST request with automatic retry."""
|
|
297
|
+
return self._request(
|
|
298
|
+
"POST",
|
|
299
|
+
path,
|
|
300
|
+
json=json,
|
|
301
|
+
params=params,
|
|
302
|
+
extra_headers=extra_headers,
|
|
303
|
+
timeout=timeout,
|
|
304
|
+
max_retries=max_retries,
|
|
305
|
+
)
|