panda-data 0.1.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.
panda_data/__init__.py ADDED
@@ -0,0 +1,68 @@
1
+ """panda_data SDK public interface."""
2
+
3
+ from .client import get_client, get_factory
4
+ from .readers import (
5
+ financial_and_factors_reader,
6
+ future_reader,
7
+ market_reader,
8
+ market_reference_reader,
9
+ trading_tools_reader,
10
+ init_token
11
+ )
12
+
13
+ init_token = init_token.init_token
14
+
15
+ # market
16
+ get_market_data = market_reader.get_market_data
17
+ get_market_min_data = market_reader.get_market_min_data
18
+ get_future_tick = market_reader.get_future_tick
19
+ get_hk_daily = market_reader.get_hk_daily
20
+ get_us_daily = market_reader.get_us_daily
21
+ get_hk_transaction = market_reader.get_hk_transaction
22
+
23
+ # market_reference
24
+ get_stock_detail = market_reference_reader.get_stock_detail
25
+ get_index_detail = market_reference_reader.get_index_detail
26
+ get_concept_list = market_reference_reader.get_concept_list
27
+ get_concept_constituents = market_reference_reader.get_concept_constituents
28
+ get_industry_detail = market_reference_reader.get_industry_detail
29
+ get_industry_constituents = market_reference_reader.get_industry_constituents
30
+ get_stock_industry = market_reference_reader.get_stock_industry
31
+ get_index_indicator = market_reference_reader.get_index_indicator
32
+ get_index_weights = market_reference_reader.get_index_weights
33
+ get_lhb_list = market_reference_reader.get_lhb_list
34
+ get_lhb_detail = market_reference_reader.get_lhb_detail
35
+ get_repurchase = market_reference_reader.get_repurchase
36
+ get_margin = market_reference_reader.get_margin
37
+ get_hsgt_hold = market_reference_reader.get_hsgt_hold
38
+ get_investor_activity = market_reference_reader.get_investor_activity
39
+ get_restricted_list = market_reference_reader.get_restricted_list
40
+ get_holder_count = market_reference_reader.get_holder_count
41
+ get_top_holders = market_reference_reader.get_top_holders
42
+ get_block_trade = market_reference_reader.get_block_trade
43
+ get_share_float = market_reference_reader.get_share_float
44
+
45
+ # financial_and_factors
46
+ get_fina_forecast = financial_and_factors_reader.get_fina_forecast
47
+ get_fina_performance = financial_and_factors_reader.get_fina_performance
48
+ get_fina_reports = financial_and_factors_reader.get_fina_reports
49
+ get_financial_statement = financial_and_factors_reader.get_financial_statement
50
+ get_financial_statement_daily = financial_and_factors_reader.get_financial_statement_daily
51
+ get_audit_opinion = financial_and_factors_reader.get_audit_opinion
52
+ get_factor = financial_and_factors_reader.get_factor
53
+ get_adj_factor = financial_and_factors_reader.get_adj_factor
54
+
55
+ # future
56
+ get_future_detail = future_reader.get_future_detail
57
+ get_future_market_post = future_reader.get_future_market_post
58
+ get_future_dominant = future_reader.get_future_dominant
59
+
60
+ # trading_tools
61
+ get_trade_cal = trading_tools_reader.get_trade_cal
62
+ get_prev_trade_date = trading_tools_reader.get_prev_trade_date
63
+ get_last_trade_date = trading_tools_reader.get_last_trade_date
64
+ get_stock_status_change = trading_tools_reader.get_stock_status_change
65
+ get_trade_list = trading_tools_reader.get_trade_list
66
+
67
+
68
+ __all__ = [name for name in globals().keys() if name.startswith('get_')] + ['init_token', 'get_client', 'get_factory']
panda_data/client.py ADDED
@@ -0,0 +1,372 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass, field
5
+ from threading import Lock
6
+ from typing import Any, Dict, Iterable, Optional, Type, TypeVar
7
+ from urllib.parse import urlparse
8
+
9
+ import pandas as pd
10
+
11
+
12
+ # 模块级函数,用于多进程处理(必须定义在类外部)
13
+ def _build_dataframe_chunk(chunk_data):
14
+ """在子进程中构建 DataFrame(模块级函数,可被多进程序列化)"""
15
+ return pd.DataFrame(chunk_data)
16
+
17
+
18
+ from panda_data.config import get_config
19
+ from panda_data.exceptions import ServiceError
20
+ from panda_data.transport.http import (
21
+ HTTPClient,
22
+ HTTPClientConfig,
23
+ )
24
+ from panda_data.utils.common_utils import find_project_root
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class ClientConfig:
29
+ base_url: str # HTTP base URL, 例如: "http://localhost:8080"
30
+ username: str = field(default="")
31
+ password: str = field(default="")
32
+ timeout: float = 30.0
33
+ retry: int = 3
34
+ verify_ssl: bool = True
35
+ proxy_type: Optional[str] = None
36
+ proxy_host: Optional[str] = None
37
+ proxy_port: Optional[int] = None
38
+ proxy_username: Optional[str] = None
39
+ proxy_password: Optional[str] = None
40
+ use_gzip: bool = False
41
+ data_field: Iterable[str] | None = field(default_factory=lambda: ("data",))
42
+ abnormal_endpoint: str = field(default="/abnormal")
43
+
44
+
45
+ def _extract_nested(data: Any, path: Optional[Iterable[str]]) -> Any:
46
+ """Utility to extract a nested value from a dictionary-like payload."""
47
+ if data is None or path is None:
48
+ return data
49
+
50
+ # 如果 data 是列表,说明可能是流式响应直接返回的数据列表
51
+ # 这种情况下,直接返回列表,不进行嵌套提取
52
+ if isinstance(data, list):
53
+ return data
54
+
55
+ # 确保 current 是字典类型
56
+ if not isinstance(data, dict):
57
+ # 如果不是字典也不是列表,可能是其他类型,直接返回
58
+ return data
59
+
60
+ current = data
61
+ # 检查错误码(只有当 current 是字典时才检查)
62
+ if isinstance(current, dict):
63
+ code = current.get("code")
64
+ if code is not None and code != 200 and code != '200':
65
+ raise ServiceError(f"服务返回错误:[错误码 {code} :{current.get('message', '未知错误')}]")
66
+
67
+ for key in path:
68
+ if not isinstance(current, dict):
69
+ return None
70
+ current = current.get(key)
71
+ if current is None:
72
+ return None
73
+ return current
74
+
75
+
76
+ class PandaServiceClient:
77
+ """Client responsible for communicating with the Java service via HTTP."""
78
+
79
+ def __init__(self, config: ClientConfig) -> None:
80
+ self._config = config
81
+ http_config = HTTPClientConfig(
82
+ base_url=config.base_url,
83
+ username=config.username,
84
+ password=config.password,
85
+ timeout=config.timeout,
86
+ max_retries=config.retry,
87
+ verify_ssl=config.verify_ssl,
88
+ proxy_type=config.proxy_type,
89
+ proxy_host=config.proxy_host,
90
+ proxy_port=config.proxy_port,
91
+ proxy_username=config.proxy_username,
92
+ proxy_password=config.proxy_password,
93
+ use_gzip=config.use_gzip,
94
+ )
95
+ self._http_client = HTTPClient(http_config)
96
+
97
+ @property
98
+ def config(self) -> ClientConfig:
99
+ return self._config
100
+
101
+ def request(
102
+ self,
103
+ *,
104
+ endpoint: str,
105
+ payload: Optional[Dict[str, Any]] = None,
106
+ method: str = "POST",
107
+ params: Optional[Dict[str, Any]] = None,
108
+ headers: Optional[Dict[str, str]] = None,
109
+ data_path: Optional[Iterable[str]] = None,
110
+ ) -> Any:
111
+ result = self._http_client.request(
112
+ method=method,
113
+ endpoint=endpoint,
114
+ payload=payload,
115
+ params=params,
116
+ headers=headers,
117
+ )
118
+ data = _extract_nested(result, data_path or self._config.data_field)
119
+ return data
120
+
121
+ def fetch_dataframe(
122
+ self,
123
+ *,
124
+ endpoint: str,
125
+ payload: Optional[Dict[str, Any]] = None,
126
+ method: str = "POST",
127
+ params: Optional[Dict[str, Any]] = None,
128
+ headers: Optional[Dict[str, str]] = None,
129
+ data_path: Optional[Iterable[str]] = None,
130
+ ) -> pd.DataFrame:
131
+ data = self.request(
132
+ endpoint=endpoint,
133
+ payload=payload,
134
+ method=method,
135
+ params=params,
136
+ headers=headers,
137
+ data_path=data_path,
138
+ )
139
+
140
+ if data is None:
141
+ return pd.DataFrame()
142
+
143
+ # 检查是否直接是 DataFrame(Parquet 返回的情况,经过 _extract_nested 后)
144
+ if isinstance(data, pd.DataFrame):
145
+ return data
146
+
147
+ # 检查是否是 Parquet 格式返回的 DataFrame(特殊标记)
148
+ if isinstance(data, dict) and "__dataframe__" in data:
149
+ return data["__dataframe__"]
150
+
151
+ if isinstance(data, dict):
152
+ # 如果数据是字典,检查是否有 'data' 字段
153
+ if 'data' in data:
154
+ # 检查 data 字段是否是 DataFrame(Parquet 返回的情况)
155
+ if isinstance(data['data'], pd.DataFrame):
156
+ return data['data']
157
+ elif isinstance(data['data'], list):
158
+ # 如果 data 字段是列表,直接使用它
159
+ data_list = data['data']
160
+ else:
161
+ # 其他类型,转换为列表
162
+ data_list = [data['data']]
163
+ else:
164
+ # 否则将整个字典作为一行
165
+ data_list = [data]
166
+ elif isinstance(data, (list, tuple)):
167
+ data_list = data
168
+ else:
169
+ raise ServiceError(
170
+ f"Unexpected payload format returned by the service. Type: {type(data)}, Value: {str(data)[:200]}")
171
+
172
+ # 对于大数据量,使用分块处理以避免内存峰值和提升性能
173
+ if len(data_list) > 100000: # 超过 10 万条记录时使用分块处理
174
+ total_rows = len(data_list)
175
+ chunk_size = 100000 # 增大块大小到 10 万条,减少合并次数
176
+
177
+ # 对于超大数据量,使用优化的单进程分块处理
178
+ # 注意:多进程的数据序列化开销很大,对于 Python 对象列表,单进程可能更快
179
+ # 如果未来需要多进程,建议使用共享内存或 Arrow 格式
180
+ chunks = []
181
+ for i in range(0, total_rows, chunk_size):
182
+ chunk_end = min(i + chunk_size, total_rows)
183
+ chunk = data_list[i:chunk_end]
184
+ df_chunk = pd.DataFrame(chunk)
185
+ chunks.append(df_chunk)
186
+ if chunks:
187
+ df = pd.concat(chunks, ignore_index=True)
188
+ del chunks
189
+ import gc
190
+ gc.collect()
191
+ else:
192
+ df = pd.DataFrame()
193
+ else:
194
+ df = pd.DataFrame(data_list)
195
+ return df
196
+
197
+ def close(self) -> None:
198
+ self._http_client.close()
199
+
200
+
201
+ ReaderT = TypeVar("ReaderT")
202
+
203
+
204
+ class ClientFactory:
205
+ """Factory responsible for creating readers bound to a shared client instance."""
206
+
207
+ def __init__(self, client: PandaServiceClient) -> None:
208
+ self._client = client
209
+ self._instances: Dict[Type[Any], Any] = {}
210
+ self._lock = Lock()
211
+
212
+ @property
213
+ def client(self) -> PandaServiceClient:
214
+ return self._client
215
+
216
+ def get_reader(self, reader_cls: Type[ReaderT]) -> ReaderT:
217
+ with self._lock:
218
+ if reader_cls not in self._instances:
219
+ self._instances[reader_cls] = reader_cls(self._client)
220
+ return self._instances[reader_cls]
221
+
222
+
223
+ _factory: Optional[ClientFactory] = None
224
+ _factory_lock = Lock()
225
+
226
+
227
+ def _parse_address(address: str) -> tuple:
228
+ """解析地址字符串为(host, port)"""
229
+ # 支持格式: "host:port", "tcp://host:port", "host"
230
+ if "://" in address:
231
+ parsed = urlparse(address)
232
+ host = parsed.hostname or parsed.path.split(":")[0]
233
+ port = parsed.port or (int(parsed.path.split(":")[-1]) if ":" in parsed.path else 8080)
234
+ elif ":" in address:
235
+ parts = address.rsplit(":", 1)
236
+ host = parts[0]
237
+ port = int(parts[1])
238
+ else:
239
+ host = address
240
+ port = 8080 # 默认端口
241
+
242
+ if not host:
243
+ raise ServiceError(f"Invalid address format: {address}")
244
+
245
+ return host, port
246
+
247
+
248
+ def init(
249
+ *,
250
+ username: Optional[str] = None,
251
+ password: Optional[str] = None,
252
+ base_url: Optional[str] = None, # HTTP base URL, 例如: "http://localhost:8080"
253
+ address: Optional[str] = None, # 兼容旧接口,格式: "host:port" 或 "http://host:port"
254
+ timeout: Optional[float] = None,
255
+ max_retries: Optional[int] = None,
256
+ verify_ssl: Optional[bool] = None,
257
+ proxy_type: Optional[str] = None,
258
+ proxy_host: Optional[str] = None,
259
+ proxy_port: Optional[int] = None,
260
+ proxy_username: Optional[str] = None,
261
+ proxy_password: Optional[str] = None,
262
+ use_gzip: Optional[bool] = None,
263
+ data_field: Optional[Iterable[str]] = None,
264
+ abnormal_endpoint: Optional[str] = None,
265
+ ) -> PandaServiceClient:
266
+ """
267
+ 初始化客户端。需先调用 init_token() 登录后使用。
268
+ """
269
+ global _factory
270
+ service_config = get_config()
271
+
272
+ if not username:
273
+ username = service_config.get("DEFAULT_USERNAME", "")
274
+ if not password:
275
+ password = service_config.get("DEFAULT_PASSWORD", "")
276
+
277
+ # 解析 base_url
278
+ if base_url:
279
+ http_base_url = base_url
280
+ elif address:
281
+ # 兼容旧的 address 格式
282
+ if address.startswith("http://") or address.startswith("https://"):
283
+ http_base_url = address
284
+ else:
285
+ # 格式: "host:port" 或 "tcp://host:port"
286
+ parsed_host, parsed_port = _parse_address(address)
287
+ http_base_url = f"http://{parsed_host}:{parsed_port}"
288
+ else:
289
+ # 从配置中获取
290
+ http_base_url = (
291
+ service_config.get("HTTP_SERVICE_BASE_URL")
292
+ or service_config.get("JAVA_SERVICE_BASE_URL")
293
+ )
294
+ if http_base_url:
295
+ # 如果配置中没有协议,添加 http://
296
+ if not http_base_url.startswith(("http://", "https://")):
297
+ parsed_host, parsed_port = _parse_address(http_base_url)
298
+ http_base_url = f"http://{parsed_host}:{parsed_port}"
299
+ else:
300
+ http_base_url = "http://localhost:8080"
301
+
302
+ config_kwargs: Dict[str, Any] = {
303
+ "base_url": http_base_url + "/pandaData",
304
+ "username": username,
305
+ "password": password,
306
+ "timeout": timeout or float(
307
+ service_config.get("HTTP_TIMEOUT") or service_config.get("JAVA_SERVICE_TIMEOUT", 30.0)),
308
+ "retry": max_retries
309
+ if max_retries is not None
310
+ else int(service_config.get("HTTP_MAX_RETRIES") or service_config.get("JAVA_SERVICE_MAX_RETRIES", 3)),
311
+ "verify_ssl": (
312
+ verify_ssl
313
+ if verify_ssl is not None
314
+ else service_config.get("HTTP_VERIFY_SSL",
315
+ service_config.get("JAVA_SERVICE_VERIFY_SSL", "true")).lower() in {"true", "1",
316
+ "yes"}
317
+ ),
318
+ "proxy_type": proxy_type or service_config.get("HTTP_PROXY_TYPE"),
319
+ "proxy_host": proxy_host or service_config.get("HTTP_PROXY_HOST"),
320
+ "proxy_port": (
321
+ proxy_port
322
+ if proxy_port is not None
323
+ else (
324
+ int(service_config["HTTP_PROXY_PORT"])
325
+ if service_config.get("HTTP_PROXY_PORT")
326
+ else None
327
+ )
328
+ ),
329
+ "proxy_username": proxy_username or service_config.get("HTTP_PROXY_USERNAME"),
330
+ "proxy_password": proxy_password or service_config.get("HTTP_PROXY_PASSWORD"),
331
+ "use_gzip": (
332
+ use_gzip
333
+ if use_gzip is not None
334
+ else service_config.get("HTTP_USE_GZIP", "false").lower() in {"true", "1", "yes"}
335
+ ),
336
+ "data_field": data_field
337
+ if data_field is not None
338
+ else _parse_data_field(service_config.get("HTTP_DATA_FIELD") or service_config.get("JAVA_SERVICE_DATA_FIELD")),
339
+ "abnormal_endpoint": abnormal_endpoint
340
+ or service_config.get("HTTP_ABNORMAL_ENDPOINT")
341
+ or service_config.get("JAVA_SERVICE_ABNORMAL_ENDPOINT", "/abnormal"),
342
+ }
343
+
344
+ client_config = ClientConfig(**config_kwargs)
345
+ client = PandaServiceClient(client_config)
346
+
347
+ with _factory_lock:
348
+ _factory = ClientFactory(client)
349
+
350
+ return client
351
+
352
+
353
+ def get_factory(base_url: Optional[str] = None) -> ClientFactory:
354
+ if _factory is None:
355
+ # 要求必须先调用 init() 或 init_token() 才能使用
356
+ # 不再自动从token文件读取配置,确保安全性
357
+ from panda_data.exceptions import ClientNotInitializedError
358
+ raise ClientNotInitializedError(
359
+ "客户端未初始化,请先调用 panda_data.init_token() 进行登录!"
360
+ )
361
+ return _factory
362
+
363
+
364
+ def get_client(base_url: Optional[str] = None) -> PandaServiceClient:
365
+ return get_factory(base_url).client
366
+
367
+
368
+ def _parse_data_field(raw: Optional[str]) -> Optional[Iterable[str]]:
369
+ if not raw:
370
+ return None
371
+ parts = [segment.strip() for segment in raw.split(".") if segment.strip()]
372
+ return tuple(parts) if parts else None
@@ -0,0 +1,177 @@
1
+ """
2
+ 配置模块,用于加载和管理配置信息
3
+ 支持从配置文件和环境变量导入,环境变量优先级更高
4
+ """
5
+ import os
6
+ import logging
7
+
8
+
9
+ # 获取logger
10
+ try:
11
+ from panda_data.logger.config import logger
12
+ except ImportError:
13
+ # 如果无法导入logger,创建一个基本的logger
14
+ logging.basicConfig(level=logging.INFO)
15
+ logger = logging.getLogger("config")
16
+
17
+
18
+ # 初始化配置变量config = None
19
+
20
+ def load_config():
21
+ """加载配置文件,并从环境变量更新配置"""
22
+ global config
23
+
24
+ config = {}
25
+
26
+ # ========== 日志配置 ==========
27
+ config["LOG_LEVEL"] = os.getenv("LOG_LEVEL", "DEBUG")
28
+ config["log_file"] = os.getenv("LOG_FILE", "logs/panda_data.log")
29
+ config["log_rotation"] = os.getenv("LOG_ROTATION", "1 MB")
30
+ config["LOG_PATH"] = os.getenv("LOG_PATH", "~/log")
31
+
32
+ # ========== HTTP 通用配置(client / init_token 等使用) ==========
33
+ config["DEFAULT_USERNAME"] = os.getenv("DEFAULT_USERNAME", "")
34
+ config["DEFAULT_PASSWORD"] = os.getenv("DEFAULT_PASSWORD", "")
35
+
36
+ config["HTTP_SERVICE_BASE_URL"] = os.getenv("HTTP_SERVICE_BASE_URL")
37
+ config["HTTP_TIMEOUT"] = os.getenv("HTTP_TIMEOUT", "300")
38
+ config["HTTP_MAX_RETRIES"] = os.getenv("HTTP_MAX_RETRIES", "3")
39
+ config["HTTP_VERIFY_SSL"] = os.getenv("HTTP_VERIFY_SSL", "true")
40
+ config["HTTP_PROXY_TYPE"] = os.getenv("HTTP_PROXY_TYPE") # "http" or "https"
41
+ config["HTTP_PROXY_HOST"] = os.getenv("HTTP_PROXY_HOST")
42
+ config["HTTP_PROXY_PORT"] = os.getenv("HTTP_PROXY_PORT")
43
+ config["HTTP_PROXY_USERNAME"] = os.getenv("HTTP_PROXY_USERNAME")
44
+ config["HTTP_PROXY_PASSWORD"] = os.getenv("HTTP_PROXY_PASSWORD")
45
+ config["HTTP_USE_GZIP"] = os.getenv("HTTP_USE_GZIP", "false")
46
+ config["HTTP_DATA_FIELD"] = os.getenv("HTTP_DATA_FIELD", "data")
47
+ config["HTTP_ABNORMAL_ENDPOINT"] = os.getenv("HTTP_ABNORMAL_ENDPOINT", "/abnormal")
48
+
49
+ # Legacy HTTP service config (for backward compatibility)
50
+ config["JAVA_SERVICE_BASE_URL"] = os.getenv("JAVA_SERVICE_BASE_URL", "http://pandadata.pandaai.online")
51
+ config["JAVA_SERVICE_TIMEOUT"] = os.getenv("JAVA_SERVICE_TIMEOUT", "15")
52
+ config["JAVA_SERVICE_MAX_POOL_SIZE"] = os.getenv("JAVA_SERVICE_MAX_POOL_SIZE", "10")
53
+ config["JAVA_SERVICE_MAX_RETRIES"] = os.getenv("JAVA_SERVICE_MAX_RETRIES", "3")
54
+ config["JAVA_SERVICE_BACKOFF_FACTOR"] = os.getenv("JAVA_SERVICE_BACKOFF_FACTOR", "0.3")
55
+ config["JAVA_SERVICE_VERIFY_SSL"] = os.getenv("JAVA_SERVICE_VERIFY_SSL", "true")
56
+ config["JAVA_SERVICE_DATA_FIELD"] = os.getenv("JAVA_SERVICE_DATA_FIELD", "data")
57
+ config["JAVA_SERVICE_ABNORMAL_ENDPOINT"] = os.getenv("JAVA_SERVICE_ABNORMAL_ENDPOINT", "/abnormal")
58
+
59
+ # ========== init_token(登录) ==========
60
+ config["JAVA_SERVICE_USER_ENDPOINT"] = os.getenv("JAVA_SERVICE_USER_ENDPOINT", "/dataUser")
61
+ config["JAVA_SERVICE_USER_PATH_LOGIN"] = os.getenv("JAVA_SERVICE_USER_PATH_LOGIN", "/login")
62
+
63
+ # ========== market_reader(行情数据) ==========
64
+ config["JAVA_SERVICE_KLINE_ENDPOINT"] = os.getenv("JAVA_SERVICE_KLINE_ENDPOINT", "/multi")
65
+ config["JAVA_SERVICE_KLINE_PATH_GET_MARKET_DATA"] = os.getenv("JAVA_SERVICE_KLINE_PATH_GET_MARKET_DATA", "/getMultiMarketData")
66
+ config["JAVA_SERVICE_KLINE_PATH_GET_MARKET_MIN_DATA"] = os.getenv("JAVA_SERVICE_KLINE_PATH_GET_MARKET_MIN_DATA", "/getMultiMarketMinData")
67
+ config["JAVA_SERVICE_TICK_PATH_FUTURE_TICK"] = os.getenv("JAVA_SERVICE_TICK_PATH_FUTURE_TICK", "/getFutureTickData")
68
+
69
+ # ========== market_reference_reader(市场参考) ==========
70
+ # KLINE - 股票详情
71
+ config["JAVA_SERVICE_KLINE_PATH_GET_STOCK_DETAIL"] = os.getenv("JAVA_SERVICE_KLINE_PATH_GET_STOCK_DETAIL", "/getStockDetail")
72
+
73
+ # INDEX - 指数
74
+ config["JAVA_SERVICE_INDEX_ENDPOINT"] = os.getenv("JAVA_SERVICE_INDEX_ENDPOINT", "/index")
75
+ config["JAVA_SERVICE_INDEX_PATH_GET_SYMBOL"] = os.getenv("JAVA_SERVICE_INDEX_PATH_GET_SYMBOL", "/getIndexSymbolData")
76
+ config["JAVA_SERVICE_INDEX_PATH_GET_INDICATOR"] = os.getenv("JAVA_SERVICE_INDEX_PATH_GET_INDICATOR", "/getIndexIndicatorData")
77
+ config["JAVA_SERVICE_INDEX_PATH_GET_WEIGHTS"] = os.getenv("JAVA_SERVICE_INDEX_PATH_GET_WEIGHTS", "/getIndexWeightsData")
78
+
79
+ # CONCEPT - 概念
80
+ config["JAVA_SERVICE_CONCEPT_ENDPOINT"] = os.getenv("JAVA_SERVICE_CONCEPT_ENDPOINT", "/concept")
81
+ config["JAVA_SERVICE_CONCEPT_PATH_GET_CONCEPT_LIST"] = os.getenv("JAVA_SERVICE_CONCEPT_PATH_GET_CONCEPT_LIST", "/getConceptData")
82
+ config["JAVA_SERVICE_CONCEPT_PATH_GET_CONCEPT_STOCK"] = os.getenv("JAVA_SERVICE_CONCEPT_PATH_GET_CONCEPT_STOCK", "/getConceptStockData")
83
+
84
+ # INDUSTRY - 行业
85
+ config["JAVA_SERVICE_INDUSTRY_ENDPOINT"] = os.getenv("JAVA_SERVICE_INDUSTRY_ENDPOINT", "/industry")
86
+ config["JAVA_SERVICE_INDUSTRY_PATH_GET_STOCK"] = os.getenv("JAVA_SERVICE_INDUSTRY_PATH_GET_STOCK", "/getIndustryStockData")
87
+ config["JAVA_SERVICE_INDUSTRY_PATH_GET_LIST"] = os.getenv("JAVA_SERVICE_INDUSTRY_PATH_GET_LIST", "/getIndustryList")
88
+ config["JAVA_SERVICE_INDUSTRY_PATH_GET_STOCK_INDUSTRY"] = os.getenv("JAVA_SERVICE_INDUSTRY_PATH_GET_STOCK_INDUSTRY", "/getStockIndustry")
89
+
90
+ # ABNORMAL - 龙虎榜
91
+ config["JAVA_SERVICE_ABNORMAL_PATH_GET_ABNORMAL_DATA"] = os.getenv("JAVA_SERVICE_ABNORMAL_PATH_GET_ABNORMAL_DATA", "/getAbnormalData")
92
+ config["JAVA_SERVICE_ABNORMAL_PATH_GET_ABNORMAL_DETAIL"] = os.getenv(
93
+ "JAVA_SERVICE_ABNORMAL_PATH_GET_ABNORMAL_DETAIL", "/getAbnormalDetailData")
94
+
95
+ # BUYBACK - 回购
96
+ config["JAVA_SERVICE_BUY_BACK_ENDPOINT"] = os.getenv("JAVA_SERVICE_BUY_BACK_ENDPOINT", "/buyback")
97
+ config["JAVA_SERVICE_BUY_BACK_PATH_GET_BUY_BACK_DATA"] = os.getenv(
98
+ "JAVA_SERVICE_BUY_BACK_PATH_GET_BUY_BACK_DATA", "/getBuyBackData")
99
+
100
+ # SECURITIES_MARGIN / STOCK_CONNECT / STOCK - 两融、北向、资金等
101
+ config["JAVA_SERVICE_SECURITIES_MARGIN_ENDPOINT"] = os.getenv("JAVA_SERVICE_SECURITIES_MARGIN_ENDPOINT", "/stock")
102
+ config["JAVA_SERVICE_SECURITIES_MARGIN_PATH_GET_MARGIN"] = os.getenv("JAVA_SERVICE_SECURITIES_MARGIN_PATH_GET_MARGIN", "/getSecuritiesMarginData")
103
+
104
+ config["JAVA_SERVICE_STOCK_CONNECT_ENDPOINT"] = os.getenv("JAVA_SERVICE_STOCK_CONNECT_ENDPOINT", "/stock")
105
+ config["JAVA_SERVICE_STOCK_CONNECT_PATH_GET_STOCK_CONNECT_DATA"] = os.getenv("JAVA_SERVICE_STOCK_CONNECT_PATH_GET_STOCK_CONNECT_DATA", "/getStockConnectData")
106
+
107
+ config["JAVA_SERVICE_STOCK_ENDPOINT"] = os.getenv("JAVA_SERVICE_STOCK_ENDPOINT", "/stock")
108
+ config["JAVA_SERVICE_STOCK_PATH_GET_FLOW"] = os.getenv("JAVA_SERVICE_STOCK_PATH_GET_FLOW", "/getStockFlowData")
109
+ config["JAVA_SERVICE_STOCK_PATH_GET_INVESTOR_ACTIVITIES"] = os.getenv("JAVA_SERVICE_STOCK_PATH_GET_INVESTOR_ACTIVITIES", "/getStockInvestorData")
110
+ config["JAVA_SERVICE_STOCK_PATH_GET_RESTRICTED_DETAILS"] = os.getenv("JAVA_SERVICE_STOCK_PATH_GET_RESTRICTED_DETAILS", "/getStockRestrictedData")
111
+ config["JAVA_SERVICE_STOCK_PATH_GET_HOLDER_NUMBER"] = os.getenv("JAVA_SERVICE_STOCK_PATH_GET_HOLDER_NUMBER", "/getStockHolderNumberData")
112
+ config["JAVA_SERVICE_STOCK_PATH_GET_MAIN_SHAREHOLDER"] = os.getenv("JAVA_SERVICE_STOCK_PATH_GET_MAIN_SHAREHOLDER", "/getStockMainHolderData")
113
+ config["JAVA_SERVICE_STOCK_PATH_GET_BLOCK_TRADE"] = os.getenv("JAVA_SERVICE_STOCK_PATH_GET_BLOCK_TRADE", "/getStockBlockTradeData")
114
+ config["JAVA_SERVICE_STOCK_PATH_GET_SHARES"] = os.getenv("JAVA_SERVICE_STOCK_PATH_GET_SHARES", "/getStockShareData")
115
+
116
+ # ========== financial_and_factors_reader(财务与因子) ==========
117
+ # FINANCIAL - 财务
118
+ config["JAVA_SERVICE_FINANCIAL_ENDPOINT"] = os.getenv("JAVA_SERVICE_FINANCIAL_ENDPOINT", "/financial")
119
+ config["JAVA_SERVICE_FINANCIAL_PATH_GET_FORECAST"] = os.getenv("JAVA_SERVICE_FINANCIAL_PATH_GET_FORECAST", "/getFinancialForecastData")
120
+ config["JAVA_SERVICE_FINANCIAL_PATH_GET_PERFORMANCE"] = os.getenv("JAVA_SERVICE_FINANCIAL_PATH_GET_PERFORMANCE", "/getFinancialPerformanceData")
121
+ config["JAVA_SERVICE_FINANCIAL_PATH_GET_EX"] = os.getenv("JAVA_SERVICE_FINANCIAL_PATH_GET_EX", "/getFinancialExData")
122
+ config["JAVA_SERVICE_FINANCIAL_PATH_GET_STATEMENT"] = os.getenv("JAVA_SERVICE_FINANCIAL_PATH_GET_STATEMENT", "/getFinancialStatementData")
123
+ config["JAVA_SERVICE_FINANCIAL_PATH_GET_STATEMENT_DAILY"] = os.getenv("JAVA_SERVICE_FINANCIAL_PATH_GET_STATEMENT_DAILY", "/getFinancialStatementDailyData")
124
+
125
+ # STOCK - 审计、复权因子
126
+ config["JAVA_SERVICE_STOCK_PATH_GET_AUDIT_OPINION"] = os.getenv("JAVA_SERVICE_STOCK_PATH_GET_AUDIT_OPINION", "/getStockAuditData")
127
+ config["JAVA_SERVICE_STOCK_PATH_GET_FACTOR_RESTORED"] = os.getenv("JAVA_SERVICE_STOCK_PATH_GET_FACTOR_RESTORED", "/getFactorRestoredData")
128
+
129
+ # KLINE - 因子
130
+ config["JAVA_SERVICE_KLINE_PATH_GET_FACTOR"] = os.getenv("JAVA_SERVICE_KLINE_PATH_GET_FACTOR", "/getFactor")
131
+
132
+ # ========== future_reader(期货) ==========
133
+ config["JAVA_SERVICE_FUTURE_ENDPOINT"] = os.getenv("JAVA_SERVICE_FUTURE_ENDPOINT", "/future")
134
+ config["JAVA_SERVICE_FUTURE_PATH_GET_FUTURE_LIST"] = os.getenv("JAVA_SERVICE_FUTURE_PATH_GET_FUTURE_LIST", "/getFutureList")
135
+ config["JAVA_SERVICE_FUTURE_PATH_GET_FACTOR_POST"] = os.getenv("JAVA_SERVICE_FUTURE_PATH_GET_FACTOR_POST", "/getFutureMarketPostData")
136
+ config["JAVA_SERVICE_FUTURE_PATH_FUTURE_DOMINANT"] = os.getenv("JAVA_SERVICE_FUTURE_PATH_FUTURE_DOMINANT", "/getFutureDominantData")
137
+
138
+ # ========== trading_tools_reader(交易工具) ==========
139
+ config["JAVA_SERVICE_CALENDAR_ENDPOINT"] = os.getenv("JAVA_SERVICE_CALENDAR_ENDPOINT", "/tradeCalendar")
140
+ config["JAVA_SERVICE_CALENDAR_PATH_GET_TRADE_CALENDAR_DATA"] = os.getenv("JAVA_SERVICE_CALENDAR_PATH_GET_TRADE_CALENDAR_DATA", "/getTradeCalendarData")
141
+ config["JAVA_SERVICE_CALENDAR_PATH_GET_PREVIOUS_NTH_TRADING_DAY"] = os.getenv("JAVA_SERVICE_CALENDAR_PATH_GET_PREVIOUS_NTH_TRADING_DAY", "/getPreviousNthTradingDay")
142
+ config["JAVA_SERVICE_CALENDAR_PATH_GET_LATEST_TRADING_DAY"] = os.getenv("JAVA_SERVICE_CALENDAR_PATH_GET_LATEST_TRADING_DAY", "/getLatestTradingDay")
143
+
144
+ config["JAVA_SERVICE_STOCK_PATH_GET_SPECIAL_TREATMENT"] = os.getenv("JAVA_SERVICE_STOCK_PATH_GET_SPECIAL_TREATMENT", "/getStockSpecialTreatmentData")
145
+ config["JAVA_SERVICE_STOCK_PATH_GET_TRADING_STOCK_LIST"] = os.getenv("JAVA_SERVICE_STOCK_PATH_GET_TRADING_STOCK_LIST", "/getTradingStockList")
146
+
147
+ # 打包前可加入 panda_data_local_config 模块覆盖配置
148
+ try:
149
+ import panda_data_local_config as _local
150
+ if getattr(_local, "JAVA_SERVICE_BASE_URL", None):
151
+ config["JAVA_SERVICE_BASE_URL"] = _local.JAVA_SERVICE_BASE_URL
152
+ except ImportError:
153
+ pass
154
+
155
+ return config
156
+
157
+
158
+ def get_config():
159
+ """
160
+ 获取配置对象,如果配置未加载则先加载配置
161
+
162
+ Returns:
163
+ dict: 配置信息字典
164
+ """
165
+ global config
166
+ if config is None:
167
+ config = load_config()
168
+ return config
169
+
170
+
171
+ # 初始加载配置
172
+ try:
173
+ config = load_config()
174
+ # logger.info(f"初始化配置成功: {config}") # 已禁用初始化配置日志
175
+ except Exception as e:
176
+ logger.error(f"初始化配置失败: {str(e)}")
177
+ # 不在初始化时抛出异常,留到实际使用时再处理
@@ -0,0 +1,2 @@
1
+ from .service import fetch_dataframe, request_service
2
+ __all__ = ["fetch_dataframe", "request_service"]
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Iterable, Optional
4
+
5
+ import pandas as pd
6
+
7
+ from panda_data.client import get_client
8
+
9
+
10
+ def request_service(
11
+ endpoint: str,
12
+ base_url: Optional[str] = None,
13
+ *,
14
+ payload: Optional[Dict[str, Any]] = None,
15
+ method: str = "POST",
16
+ params: Optional[Dict[str, Any]] = None,
17
+ headers: Optional[Dict[str, str]] = None,
18
+ data_path: Optional[Iterable[str]] = None,
19
+ ) -> Any:
20
+ client = get_client(base_url)
21
+ return client.request(
22
+ endpoint=endpoint,
23
+ payload=payload,
24
+ method=method,
25
+ params=params,
26
+ headers=headers,
27
+ data_path=data_path,
28
+ )
29
+
30
+
31
+ def fetch_dataframe(
32
+ endpoint: str,
33
+ *,
34
+ payload: Optional[Dict[str, Any]] = None,
35
+ method: str = "POST",
36
+ params: Optional[Dict[str, Any]] = None,
37
+ headers: Optional[Dict[str, str]] = None,
38
+ data_path: Optional[Iterable[str]] = None,
39
+ ) -> pd.DataFrame:
40
+ client = get_client()
41
+ return client.fetch_dataframe(
42
+ endpoint=endpoint,
43
+ payload=payload,
44
+ method=method,
45
+ params=params,
46
+ headers=headers,
47
+ data_path=data_path,
48
+ )
49
+