infra-data-client 0.2.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.
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"""
|
|
2
|
+
InfraX data-service 官方 Python SDK(DS-14)
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from infra_data_client import InfraDataClient
|
|
6
|
+
|
|
7
|
+
client = InfraDataClient(
|
|
8
|
+
base_url="http://127.0.0.1:9112",
|
|
9
|
+
api_key="infrax-bridge-...", # X-Service-Key 自动携带(DS-12 契约)
|
|
10
|
+
verify=False, # 当前生产证书不可信时可关(B 端决策)
|
|
11
|
+
)
|
|
12
|
+
bars = client.get_bars("BTC/USDT", timeframe="1h", limit=10)
|
|
13
|
+
factors = client.get_current_factors("BTC,ETH")
|
|
14
|
+
|
|
15
|
+
设计对齐 DS-14 要求 + 既有 lightrag_client 先例:
|
|
16
|
+
- 鉴权内置:api_key → X-Service-Key 请求头,调用方零配置
|
|
17
|
+
- TLS 可配置:verify 参数(默认 True)
|
|
18
|
+
- 限流配套:429 自动重试/退避(Retry-After 优先,指数退避兜底)
|
|
19
|
+
- fail-silent:默认返回 None/空值不抛错,业务不中断;fail_silent=False 抛 InfraDataError
|
|
20
|
+
- 时间单位归一化:start/end 秒↔毫秒自动识别换算
|
|
21
|
+
- 类型注解:方法与返回类型标注
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import time
|
|
26
|
+
from typing import Any, Optional, Union
|
|
27
|
+
|
|
28
|
+
import requests
|
|
29
|
+
import urllib3
|
|
30
|
+
|
|
31
|
+
__version__ = "0.2.0"
|
|
32
|
+
|
|
33
|
+
# 平台统一鉴权头(data-service 接受 X-Service-Key / Bearer / X-API-Key 任一)
|
|
34
|
+
_API_KEY_HEADER = "X-Service-Key"
|
|
35
|
+
|
|
36
|
+
# 429 重试上限保护(Retry-After 恶意/异常大值时封顶)
|
|
37
|
+
_MAX_RETRY_AFTER = 60
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class InfraDataError(Exception):
|
|
41
|
+
"""API 层失败(连接错误 / HTTP 非 2xx)。fail_silent=False 时抛出。"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, status: int, message: str):
|
|
44
|
+
self.status = status
|
|
45
|
+
self.message = message
|
|
46
|
+
super().__init__(f"[{status}] {message}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _to_ms(ts: Optional[Union[int, float]]) -> Optional[int]:
|
|
50
|
+
"""秒↔毫秒归一化:>= 10^12 视为毫秒原样返回;否则按秒转毫秒。"""
|
|
51
|
+
if ts is None:
|
|
52
|
+
return None
|
|
53
|
+
ts = int(ts)
|
|
54
|
+
if abs(ts) >= 10**12:
|
|
55
|
+
return ts
|
|
56
|
+
return ts * 1000
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class InfraDataClient:
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
base_url: str,
|
|
63
|
+
api_key: Optional[str] = None,
|
|
64
|
+
verify: bool = True,
|
|
65
|
+
timeout: float = 30.0,
|
|
66
|
+
max_retries: int = 3,
|
|
67
|
+
retry_backoff: float = 1.0,
|
|
68
|
+
fail_silent: bool = True,
|
|
69
|
+
):
|
|
70
|
+
"""
|
|
71
|
+
base_url data-service 根地址,如 http://127.0.0.1:9112(生产经 nginx 前缀如
|
|
72
|
+
https://host/api/data 亦可,SDK 只做路径拼接)
|
|
73
|
+
api_key X-Service-Key 值;None 时按服务端配置可能 401(无鉴权环境可留空)
|
|
74
|
+
verify TLS 校验;生产证书暂不可信时传 False
|
|
75
|
+
timeout 单请求超时(秒)
|
|
76
|
+
max_retries 429 最大重试次数(0 = 不重试)
|
|
77
|
+
retry_backoff 429 退避基数(秒,指数:backoff * 2**attempt;Retry-After 头优先)
|
|
78
|
+
fail_silent 网络/HTTP 错误时返回 None 不抛错(True,默认);False 抛 InfraDataError
|
|
79
|
+
"""
|
|
80
|
+
self.base_url = str(base_url).rstrip("/")
|
|
81
|
+
self.verify = verify
|
|
82
|
+
self.timeout = timeout
|
|
83
|
+
self.max_retries = max_retries
|
|
84
|
+
self.retry_backoff = retry_backoff
|
|
85
|
+
self.fail_silent = fail_silent
|
|
86
|
+
|
|
87
|
+
self._session = requests.Session()
|
|
88
|
+
self._session.verify = verify
|
|
89
|
+
self._session.headers.update({"Content-Type": "application/json"})
|
|
90
|
+
if api_key:
|
|
91
|
+
self._session.headers[_API_KEY_HEADER] = api_key
|
|
92
|
+
if not verify:
|
|
93
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
94
|
+
|
|
95
|
+
# ── HTTP 核心 ─────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
def _extract_message(self, resp: requests.Response) -> str:
|
|
98
|
+
try:
|
|
99
|
+
body = resp.json()
|
|
100
|
+
except ValueError:
|
|
101
|
+
return f"HTTP {resp.status_code}"
|
|
102
|
+
if isinstance(body, dict):
|
|
103
|
+
msg = body.get("message")
|
|
104
|
+
if msg:
|
|
105
|
+
return str(msg)
|
|
106
|
+
detail = body.get("detail")
|
|
107
|
+
if detail:
|
|
108
|
+
return str(detail)
|
|
109
|
+
return f"HTTP {resp.status_code}"
|
|
110
|
+
|
|
111
|
+
def _request(
|
|
112
|
+
self,
|
|
113
|
+
method: str,
|
|
114
|
+
path: str,
|
|
115
|
+
params: Optional[dict] = None,
|
|
116
|
+
**kwargs: Any,
|
|
117
|
+
) -> Any:
|
|
118
|
+
"""发送请求。成功返回响应体(envelope=1 时解包 data);失败按 fail_silent
|
|
119
|
+
返回 None 或抛 InfraDataError。429 自动重试。"""
|
|
120
|
+
url = f"{self.base_url}{path}"
|
|
121
|
+
attempt = 0
|
|
122
|
+
while True:
|
|
123
|
+
try:
|
|
124
|
+
resp = self._session.request(method, url, params=params, timeout=self.timeout, **kwargs)
|
|
125
|
+
except requests.RequestException as exc:
|
|
126
|
+
if self.fail_silent:
|
|
127
|
+
return None
|
|
128
|
+
raise InfraDataError(0, f"CONNECTION_ERROR: {exc}") from exc
|
|
129
|
+
|
|
130
|
+
if resp.status_code == 429 and attempt < self.max_retries:
|
|
131
|
+
retry_after = resp.headers.get("Retry-After")
|
|
132
|
+
try:
|
|
133
|
+
delay = min(float(retry_after), _MAX_RETRY_AFTER)
|
|
134
|
+
except (TypeError, ValueError):
|
|
135
|
+
delay = self.retry_backoff * (2**attempt)
|
|
136
|
+
time.sleep(delay)
|
|
137
|
+
attempt += 1
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
if not resp.ok:
|
|
141
|
+
if self.fail_silent:
|
|
142
|
+
return None
|
|
143
|
+
raise InfraDataError(resp.status_code, self._extract_message(resp))
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
body = resp.json()
|
|
147
|
+
except ValueError:
|
|
148
|
+
body = {}
|
|
149
|
+
# 统一信封(?envelope=1 或后续版本默认信封)兼容解包
|
|
150
|
+
if isinstance(body, dict) and body.get("code") == 0 and "data" in body:
|
|
151
|
+
return body["data"]
|
|
152
|
+
return body
|
|
153
|
+
|
|
154
|
+
# ── Bars(K 线)───────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
def get_bars(
|
|
157
|
+
self,
|
|
158
|
+
symbol: str,
|
|
159
|
+
timeframe: str = "1m",
|
|
160
|
+
market_type: Optional[str] = None,
|
|
161
|
+
start: Optional[Union[int, float]] = None,
|
|
162
|
+
end: Optional[Union[int, float]] = None,
|
|
163
|
+
limit: int = 500,
|
|
164
|
+
) -> Optional[dict]:
|
|
165
|
+
"""OHLCV + 指标 + 外部因子 K 线(服务端按 ts 升序)。
|
|
166
|
+
|
|
167
|
+
market_type: spot | swap(None 自动判定:symbol 含 ``:`` → swap)
|
|
168
|
+
start/end: 秒或毫秒均可,SDK 自动归一化为毫秒
|
|
169
|
+
返回 {symbol, timeframe, market_type, count, bars:[{ts, open, high, low,
|
|
170
|
+
close, volume, rsi_14, ...}]}
|
|
171
|
+
"""
|
|
172
|
+
params = {
|
|
173
|
+
"symbol": symbol,
|
|
174
|
+
"timeframe": timeframe,
|
|
175
|
+
"limit": limit,
|
|
176
|
+
}
|
|
177
|
+
if market_type is not None:
|
|
178
|
+
params["market_type"] = market_type
|
|
179
|
+
if start is not None:
|
|
180
|
+
params["start"] = _to_ms(start)
|
|
181
|
+
if end is not None:
|
|
182
|
+
params["end"] = _to_ms(end)
|
|
183
|
+
return self._request("GET", "/bars", params=params)
|
|
184
|
+
|
|
185
|
+
# ── Factors ───────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
def get_factor_catalog(self) -> Optional[list]:
|
|
188
|
+
"""全部可用因子目录(含 DS-13 ML 因子 category="ml")。
|
|
189
|
+
|
|
190
|
+
返回 [{id, name, category, type, range}, ...](已解包 factors 列表)。
|
|
191
|
+
"""
|
|
192
|
+
body = self._request("GET", "/factors/catalog")
|
|
193
|
+
if body is None:
|
|
194
|
+
return None
|
|
195
|
+
return body.get("factors") if isinstance(body, dict) else body
|
|
196
|
+
|
|
197
|
+
def get_current_factors(
|
|
198
|
+
self,
|
|
199
|
+
symbols: Union[str, list] = "BTC",
|
|
200
|
+
category: Optional[str] = None,
|
|
201
|
+
) -> Optional[dict]:
|
|
202
|
+
"""最新因子值(实盘/AI 分析用)。
|
|
203
|
+
|
|
204
|
+
symbols: "BTC,ETH" 或 ["BTC", "ETH"]
|
|
205
|
+
category: external/sentiment/news/opportunities/heatmap/calendar/snapshot/ml 等
|
|
206
|
+
返回 {ts, factors: {SYMBOL: {fid: val}, "_complex": {...}}}
|
|
207
|
+
"""
|
|
208
|
+
if isinstance(symbols, (list, tuple)):
|
|
209
|
+
symbols = ",".join(str(s) for s in symbols)
|
|
210
|
+
params: dict[str, Any] = {"symbols": symbols}
|
|
211
|
+
if category is not None:
|
|
212
|
+
params["category"] = category
|
|
213
|
+
body = self._request("GET", "/factors/current", params=params)
|
|
214
|
+
if body is None:
|
|
215
|
+
return None
|
|
216
|
+
return body.get("factors") if isinstance(body, dict) and "factors" in body else body
|
|
217
|
+
|
|
218
|
+
def get_history_factors(
|
|
219
|
+
self,
|
|
220
|
+
symbol: str,
|
|
221
|
+
timeframe: str = "1m",
|
|
222
|
+
ids: Optional[Union[str, list]] = None,
|
|
223
|
+
start: Optional[Union[int, float]] = None,
|
|
224
|
+
end: Optional[Union[int, float]] = None,
|
|
225
|
+
limit: int = 500,
|
|
226
|
+
) -> Optional[dict]:
|
|
227
|
+
"""逐 bar 因子历史(回测用,asof 对齐无未来函数)。
|
|
228
|
+
|
|
229
|
+
symbol 需为交易对形式(如 BTC/USDT 或 BTCUSDT;裸符号如 BTC 服务端不匹配)。
|
|
230
|
+
ids: "rsi_14,ma_5" 或 ["rsi_14", "ma_5"](None = 全部)
|
|
231
|
+
start/end: 秒或毫秒均可,自动归一化为毫秒
|
|
232
|
+
返回 {symbol, timeframe, count, series: [{ts, <fid>: val, ...}, ...]}
|
|
233
|
+
"""
|
|
234
|
+
if isinstance(ids, (list, tuple)):
|
|
235
|
+
ids = ",".join(str(i) for i in ids)
|
|
236
|
+
params: dict[str, Any] = {
|
|
237
|
+
"symbol": symbol,
|
|
238
|
+
"timeframe": timeframe,
|
|
239
|
+
"limit": limit,
|
|
240
|
+
}
|
|
241
|
+
if ids is not None:
|
|
242
|
+
params["ids"] = ids
|
|
243
|
+
if start is not None:
|
|
244
|
+
params["start"] = _to_ms(start)
|
|
245
|
+
if end is not None:
|
|
246
|
+
params["end"] = _to_ms(end)
|
|
247
|
+
return self._request("GET", "/factors/history", params=params)
|
|
248
|
+
|
|
249
|
+
# ── Snapshots(复杂快照)──────────────────────────────
|
|
250
|
+
|
|
251
|
+
def get_snapshots(self, snapshot_type: Optional[str] = None) -> Optional[dict]:
|
|
252
|
+
"""板块快照(heatmap/calendar/crypto_prices/indices/tvl/volatility/
|
|
253
|
+
us_indicators/earnings/onchain/commodities/forex_pairs/market_overview)。
|
|
254
|
+
|
|
255
|
+
返回 {ts, snapshots: {<type>: ...}}
|
|
256
|
+
"""
|
|
257
|
+
params = {}
|
|
258
|
+
if snapshot_type is not None:
|
|
259
|
+
params["type"] = snapshot_type
|
|
260
|
+
return self._request("GET", "/snapshots", params=params)
|
|
261
|
+
|
|
262
|
+
# ── Ticker(实时报价)─────────────────────────────────
|
|
263
|
+
|
|
264
|
+
def get_ticker(
|
|
265
|
+
self,
|
|
266
|
+
symbol: str,
|
|
267
|
+
market_type: Optional[str] = None,
|
|
268
|
+
exchange_id: Optional[str] = None,
|
|
269
|
+
market: Optional[str] = None,
|
|
270
|
+
) -> Optional[dict]:
|
|
271
|
+
"""实时报价(持仓现价/告警轮询)。
|
|
272
|
+
|
|
273
|
+
market_type: spot | swap(None 自动判定)
|
|
274
|
+
exchange_id: 默认 binance
|
|
275
|
+
market: crypto/usstock/forex/futures/cnstock/hkstock
|
|
276
|
+
返回 {symbol, price, change, changePercent, high, low, open,
|
|
277
|
+
previousClose, ts, market_type};无数据 404 → None(fail_silent)
|
|
278
|
+
"""
|
|
279
|
+
params: dict[str, Any] = {"symbol": symbol}
|
|
280
|
+
if market_type is not None:
|
|
281
|
+
params["market_type"] = market_type
|
|
282
|
+
if exchange_id is not None:
|
|
283
|
+
params["exchange_id"] = exchange_id
|
|
284
|
+
if market is not None:
|
|
285
|
+
params["market"] = market
|
|
286
|
+
return self._request("GET", "/ticker", params=params)
|
|
287
|
+
|
|
288
|
+
# ── Symbols ───────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
def resolve_symbol(self, symbol: str, market: str = "crypto") -> Optional[dict]:
|
|
291
|
+
"""符号解析:BTC → {query: "BTC", resolved: "BTCUSDT", market: "crypto"}。
|
|
292
|
+
|
|
293
|
+
未解析 404 → None(fail_silent)。
|
|
294
|
+
"""
|
|
295
|
+
return self._request(
|
|
296
|
+
"GET", "/symbol/resolve", params={"symbol": symbol, "market": market}
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
def search_symbols(self, keyword: str, market: str = "crypto", limit: int = 20) -> Optional[dict]:
|
|
300
|
+
"""符号模糊搜索(添加自选/搜索交易对)。
|
|
301
|
+
|
|
302
|
+
market: crypto/usstock/forex/futures/cnstock/hkstock
|
|
303
|
+
返回 {keyword, symbols: [{symbol, market, market_type, exchange, active}, ...]}
|
|
304
|
+
"""
|
|
305
|
+
return self._request(
|
|
306
|
+
"GET",
|
|
307
|
+
"/symbols/search",
|
|
308
|
+
params={"keyword": keyword, "market": market, "limit": limit},
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# ── ML 预测(快照,P2 模型)───────────────────────────
|
|
312
|
+
|
|
313
|
+
def get_ml_predictions(
|
|
314
|
+
self,
|
|
315
|
+
model: str,
|
|
316
|
+
symbol: str,
|
|
317
|
+
start: Optional[Union[int, float]] = None,
|
|
318
|
+
end: Optional[Union[int, float]] = None,
|
|
319
|
+
limit: int = 500,
|
|
320
|
+
) -> Optional[dict]:
|
|
321
|
+
"""P2 单模型预测历史(**data 侧采集快照,优先路径**,30min 落库稳定可查)。
|
|
322
|
+
|
|
323
|
+
model: bolt | moirai | timesfm(正则校验,其他值 422)
|
|
324
|
+
symbol: BTC / BTC/USDT / EURUSD=X 等(服务端归一化,大小写不敏感)
|
|
325
|
+
start/end: 秒或毫秒均可,自动归一化为毫秒
|
|
326
|
+
返回 {model, symbol, count, predictions:[{generated_at, direction,
|
|
327
|
+
prob_up, uncertainty, point_forecast, quantiles}, ...]}(generated_at 升序);
|
|
328
|
+
该符号尚无快照 → 404 → None(fail_silent)。
|
|
329
|
+
|
|
330
|
+
实时性优先时请直连 ml-service `/ml/*`(缓存 miss 返回 data=null,
|
|
331
|
+
需按 TTL 轮询,见 examples/ml_predictions_integration.py)。
|
|
332
|
+
"""
|
|
333
|
+
params: dict[str, Any] = {"model": model, "symbol": symbol, "limit": limit}
|
|
334
|
+
if start is not None:
|
|
335
|
+
params["start"] = _to_ms(start)
|
|
336
|
+
if end is not None:
|
|
337
|
+
params["end"] = _to_ms(end)
|
|
338
|
+
return self._request("GET", "/ml/predictions", params=params)
|
|
339
|
+
|
|
340
|
+
# ── Policy / Stats / Health ───────────────────────────
|
|
341
|
+
|
|
342
|
+
def get_broker_market_policy(self) -> Optional[dict]:
|
|
343
|
+
"""券商市场策略:{crypto: {exchanges: [...], default: "Binance"}}。"""
|
|
344
|
+
return self._request("GET", "/policy/broker-market")
|
|
345
|
+
|
|
346
|
+
def get_stats(self) -> Optional[dict]:
|
|
347
|
+
"""数据库统计:{kline_rows, snapshot_rows, symbols, time_start, time_end}。"""
|
|
348
|
+
return self._request("GET", "/stats")
|
|
349
|
+
|
|
350
|
+
def health(self) -> Optional[dict]:
|
|
351
|
+
"""健康检查(免鉴权)。返回 {code, message, data:{service, version}}。"""
|
|
352
|
+
return self._request("GET", "/health")
|
|
353
|
+
|
|
354
|
+
# ── Session ───────────────────────────────────────────
|
|
355
|
+
|
|
356
|
+
def close(self) -> None:
|
|
357
|
+
"""关闭底层连接池。"""
|
|
358
|
+
self._session.close()
|
|
359
|
+
|
|
360
|
+
def __enter__(self) -> "InfraDataClient":
|
|
361
|
+
return self
|
|
362
|
+
|
|
363
|
+
def __exit__(self, *exc: Any) -> None:
|
|
364
|
+
self.close()
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: infra-data-client
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Official Python SDK for InfraX data-service — unified market data & factors (bars / ticker / factors / snapshots / symbols / stats)
|
|
5
|
+
Author: InfraX
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://infrax.io
|
|
8
|
+
Keywords: market-data,factors,bars,ticker,infrax,data-service,crypto,ohlcv
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: requests>=2.25
|
|
16
|
+
|
|
17
|
+
# infra-data-client — InfraX data-service 官方 Python SDK
|
|
18
|
+
|
|
19
|
+
InfraX **data-service**(统一市场数据微服务,`/bars` / `/factors/*` / `/snapshots` / `/ticker` / `/symbols/*` / `/stats` / `/health`)的官方 Python 客户端。B 端(AItrader 侧)用这一个类替换各自重复实现的 `data_client.py` / `factor_client.py`,收敛鉴权、TLS、限流、时间换算口径。
|
|
20
|
+
|
|
21
|
+
- 版本:**0.2.0**(SemVer;契约变更走 minor,bug 修复走 patch,AItrader 侧升级 SDK 即可,无需改业务代码)
|
|
22
|
+
- 依赖:`requests>=2.25`(Python ≥ 3.9)
|
|
23
|
+
|
|
24
|
+
## 安装
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 本地构建安装
|
|
28
|
+
cd projects/data/sdk/python
|
|
29
|
+
pip install .
|
|
30
|
+
|
|
31
|
+
# 或直接 git 引用(发布到私有源后改用 pip install infra-data-client)
|
|
32
|
+
pip install "git+https://<你的仓库>/infraX-1.git#subdirectory=projects/data/sdk/python"
|
|
33
|
+
|
|
34
|
+
# 开发模式(改代码即生效)
|
|
35
|
+
pip install -e .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 快速开始
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from infra_data_client import InfraDataClient
|
|
42
|
+
|
|
43
|
+
client = InfraDataClient(
|
|
44
|
+
base_url="http://127.0.0.1:9112", # 生产经 nginx 前缀如 https://host/api/data 亦可
|
|
45
|
+
api_key="infrax-bridge-...", # X-Service-Key 自动携带(DS-12)
|
|
46
|
+
verify=False, # 生产证书暂不可信时关闭校验
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# K 线(start/end 秒或毫秒均可,自动归一化)
|
|
50
|
+
bars = client.get_bars("BTC/USDT", timeframe="1h", limit=10)
|
|
51
|
+
|
|
52
|
+
# 最新因子(含 DS-13 ML 因子)
|
|
53
|
+
factors = client.get_current_factors("BTC,ETH")
|
|
54
|
+
|
|
55
|
+
# 实时报价
|
|
56
|
+
ticker = client.get_ticker("BTC/USDT", market_type="swap")
|
|
57
|
+
|
|
58
|
+
# 符号解析 / 搜索
|
|
59
|
+
resolved = client.resolve_symbol("BTC")
|
|
60
|
+
hits = client.search_symbols("btc", market="crypto", limit=10)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## API 一览
|
|
64
|
+
|
|
65
|
+
| 方法 | 端点 | 说明 |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `get_bars(symbol, timeframe, market_type, start, end, limit)` | `GET /bars` | OHLCV + 指标 + 外部因子,ts 毫秒升序 |
|
|
68
|
+
| `get_factor_catalog()` | `GET /factors/catalog` | 因子目录(含 ML category="ml") |
|
|
69
|
+
| `get_current_factors(symbols, category)` | `GET /factors/current` | 最新因子(symbols 支持 `"BTC,ETH"` 或 `["BTC","ETH"]`) |
|
|
70
|
+
| `get_history_factors(symbol, timeframe, ids, start, end, limit)` | `GET /factors/history` | 逐 bar 因子历史(asof 对齐,回测无未来函数) |
|
|
71
|
+
| `get_snapshots(snapshot_type)` | `GET /snapshots` | 板块快照(heatmap/calendar/crypto_prices/indices/tvl/volatility/us_indicators/earnings/onchain/commodities/forex_pairs/market_overview) |
|
|
72
|
+
| `get_ticker(symbol, market_type, exchange_id, market)` | `GET /ticker` | 实时报价 |
|
|
73
|
+
| `get_ml_predictions(model, symbol, start, end, limit)` | `GET /ml/predictions` | **P2 模型预测快照(优先路径,30min 落库)**;model=bolt\|moirai\|timesfm,无快照返回 None |
|
|
74
|
+
| `resolve_symbol(symbol, market)` | `GET /symbol/resolve` | 符号解析(全市场,DS-11) |
|
|
75
|
+
| `search_symbols(keyword, market, limit)` | `GET /symbols/search` | 符号模糊搜索(DS-9) |
|
|
76
|
+
| `get_broker_market_policy()` | `GET /policy/broker-market` | 券商市场策略 |
|
|
77
|
+
| `get_stats()` | `GET /stats` | 数据库统计 |
|
|
78
|
+
| `health()` | `GET /health` | 健康检查(免鉴权) |
|
|
79
|
+
|
|
80
|
+
## 核心能力
|
|
81
|
+
|
|
82
|
+
- **鉴权内置**:`api_key` 自动写入 `X-Service-Key` 请求头(DS-12 契约),调用方零配置。
|
|
83
|
+
- **TLS 可配置**:`verify=False` 应对生产证书不可信现状(同时抑制 InsecureRequestWarning)。
|
|
84
|
+
- **429 限流重试**:命中 429 自动重试,优先 `Retry-After` 头,无则指数退避 `backoff * 2^attempt`;`max_retries=0` 关闭重试。
|
|
85
|
+
- **fail-silent**(默认开):网络错误 / 非 2xx(401/404/500…)返回 `None`,业务不中断;`fail_silent=False` 时抛 `InfraDataError(status, message)` 便于排查。
|
|
86
|
+
- **时间单位归一化**:`start`/`end` 传秒或毫秒均可(≥10^12 视为毫秒),SDK 统一换算后上送。
|
|
87
|
+
- **类型注解**:全部方法与返回类型标注,IDE 与静态检查友好;`Client` 支持上下文管理器(自动关闭连接池)。
|
|
88
|
+
|
|
89
|
+
## 示例
|
|
90
|
+
|
|
91
|
+
- [examples/quickstart.py](examples/quickstart.py) — 全端点走查
|
|
92
|
+
- [examples/ml_predictions_integration.py](examples/ml_predictions_integration.py) — **ML 预测集成**:快照优先 + ml-service 直连 `data=null` 兜底(含 `/ml/cache/stats` 就绪判断)
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
cd projects/data/sdk/python
|
|
96
|
+
python examples/quickstart.py --base-url http://127.0.0.1:9112 --api-key <KEY>
|
|
97
|
+
python examples/ml_predictions_integration.py --symbol BTC/USDT --model bolt \
|
|
98
|
+
--data-url http://127.0.0.1:9112 --data-key <KEY> --ml-url http://43.156.25.197:9120
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 发布(SemVer)
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
cd projects/data/sdk/python
|
|
105
|
+
pip install build
|
|
106
|
+
python -m build # 产出 dist/infra_data_client-0.2.0-*.whl / .tar.gz
|
|
107
|
+
# 上传到私有 pip 源,或直接分发 wheel
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
契约变更规范:新增端点/参数 → minor;修复行为 → patch;破坏性变更 → major。
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
infra_data_client/__init__.py,sha256=24wIUN-X4swHF7vUslHDZRLEGHcmbXaFWsUA3uhTrKQ,14782
|
|
2
|
+
infra_data_client-0.2.0.dist-info/METADATA,sha256=QFKY0HfNR8_mZzbqI1S8cIK8YgSJM0chfDaUWkE6ipM,5534
|
|
3
|
+
infra_data_client-0.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
4
|
+
infra_data_client-0.2.0.dist-info/top_level.txt,sha256=v-D7n0yp4Q6z9ET6AWoC-OrHE6d7s2TXzUb7ts9B9yM,18
|
|
5
|
+
infra_data_client-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
infra_data_client
|