akshare-one 0.2.1__py3-none-any.whl → 0.2.2__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,88 @@
1
+ import requests
2
+ from typing import Dict, Any
3
+
4
+
5
+ class EastMoneyClient:
6
+ """
7
+ A client for interacting directly with EastMoney's data APIs.
8
+ This class handles session management, request signing, and API calls.
9
+ """
10
+
11
+ def __init__(self):
12
+ self.session = requests.Session()
13
+ self.session.headers.update(
14
+ {
15
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
16
+ "Referer": "https://quote.eastmoney.com/",
17
+ "Accept": "application/json, text/plain, */*",
18
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
19
+ }
20
+ )
21
+
22
+ def _get_security_id(self, symbol: str) -> str:
23
+ """
24
+ Converts a stock symbol to EastMoney's internal secid format.
25
+ e.g., '600519' -> '1.600519', '000001' -> '0.000001'
26
+ """
27
+ symbol = symbol.upper()
28
+ if symbol.startswith("SZ"):
29
+ market = "0"
30
+ code = symbol[2:]
31
+ elif symbol.startswith("SH"):
32
+ market = "1"
33
+ code = symbol[2:]
34
+ elif symbol.startswith("HK"):
35
+ market = "116"
36
+ code = symbol[2:]
37
+ elif len(symbol) == 6:
38
+ if symbol.startswith(("000", "001", "002", "003", "300")):
39
+ market = "0"
40
+ elif symbol.startswith(("600", "601", "603", "605", "688")):
41
+ market = "1"
42
+ else:
43
+ market = "0" # Default to SZ for ambiguity
44
+ code = symbol
45
+ elif len(symbol) == 5: # HK Market
46
+ market = "116"
47
+ code = symbol
48
+ else:
49
+ market = "0"
50
+ code = symbol
51
+ return f"{market}.{code}"
52
+
53
+ def fetch_historical_klines(
54
+ self, symbol: str, klt: str, fqt: str, start_date: str, end_date: str
55
+ ) -> Dict[str, Any]:
56
+ """
57
+ Fetches historical K-line (candlestick) data.
58
+ """
59
+ url = "https://push2his.eastmoney.com/api/qt/stock/kline/get"
60
+ secid = self._get_security_id(symbol)
61
+ params = {
62
+ "fields1": "f1,f2,f3,f4,f5,f6",
63
+ "fields2": "f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61",
64
+ "klt": klt,
65
+ "fqt": fqt,
66
+ "secid": secid,
67
+ "beg": start_date,
68
+ "end": end_date,
69
+ }
70
+ response = self.session.get(url, params=params)
71
+ response.raise_for_status()
72
+ return response.json()
73
+
74
+ def fetch_realtime_quote(self, symbol: str) -> Dict[str, Any]:
75
+ """
76
+ Fetches real-time quote data for a single stock.
77
+ """
78
+ url = "https://push2.eastmoney.com/api/qt/stock/get"
79
+ secid = self._get_security_id(symbol)
80
+ params = {
81
+ "invt": "2",
82
+ "fltt": "2",
83
+ "fields": "f43,f57,f58,f169,f170,f46,f60,f44,f51,f168,f47,f164,f163,f116,f60,f45,f52,f50,f48,f167,f117,f71,f161,f49,f530",
84
+ "secid": secid,
85
+ }
86
+ response = self.session.get(url, params=params)
87
+ response.raise_for_status()
88
+ return response.json()
@@ -0,0 +1,104 @@
1
+ import pandas as pd
2
+ from typing import Dict, Any
3
+
4
+
5
+ def parse_kline_data(data: Dict[str, Any]) -> pd.DataFrame:
6
+ """
7
+ Parses K-line data from the API response into a pandas DataFrame.
8
+ """
9
+ klines = data.get("data", {}).get("klines", [])
10
+ if not klines:
11
+ return pd.DataFrame(
12
+ columns=["timestamp", "open", "high", "low", "close", "volume"]
13
+ )
14
+
15
+ records = []
16
+ for kline in klines:
17
+ parts = kline.split(",")
18
+ if len(parts) >= 6:
19
+ records.append(
20
+ {
21
+ "timestamp": parts[0],
22
+ "open": float(parts[1]),
23
+ "close": float(parts[2]),
24
+ "high": float(parts[3]),
25
+ "low": float(parts[4]),
26
+ "volume": int(parts[5]),
27
+ }
28
+ )
29
+
30
+ df = pd.DataFrame(records)
31
+ if not df.empty:
32
+ df["timestamp"] = pd.to_datetime(df["timestamp"])
33
+ df["timestamp"] = (
34
+ df["timestamp"].dt.tz_localize("Asia/Shanghai").dt.tz_convert("UTC")
35
+ )
36
+ df = df[["timestamp", "open", "high", "low", "close", "volume"]]
37
+ return df
38
+
39
+
40
+ def parse_realtime_data(data: Dict[str, Any]) -> pd.DataFrame:
41
+ """
42
+ Parses real-time quote data from the API response into a pandas DataFrame.
43
+ """
44
+ stock_data = data.get("data")
45
+ if not stock_data:
46
+ return pd.DataFrame()
47
+
48
+ df = pd.DataFrame(
49
+ [
50
+ {
51
+ "symbol": stock_data.get("f57"),
52
+ "price": stock_data.get("f43"),
53
+ "change": stock_data.get("f169"),
54
+ "pct_change": stock_data.get("f170"),
55
+ "volume": stock_data.get("f47"),
56
+ "amount": stock_data.get("f48"),
57
+ "open": stock_data.get("f46"),
58
+ "high": stock_data.get("f44"),
59
+ "low": stock_data.get("f45"),
60
+ "prev_close": stock_data.get("f60"),
61
+ }
62
+ ]
63
+ )
64
+ df["timestamp"] = pd.Timestamp.now(tz="Asia/Shanghai").tz_convert("UTC")
65
+ return df
66
+
67
+
68
+ def resample_historical_data(
69
+ df: pd.DataFrame, interval: str, multiplier: int
70
+ ) -> pd.DataFrame:
71
+ """
72
+ Resamples historical data to a specified frequency.
73
+ """
74
+ if df.empty or multiplier <= 1:
75
+ return df
76
+
77
+ df = df.set_index("timestamp")
78
+
79
+ freq_map = {
80
+ "day": f"{multiplier}D",
81
+ "week": f"{multiplier}W-MON",
82
+ "month": f"{multiplier}MS",
83
+ "year": f"{multiplier * 12}MS",
84
+ }
85
+ freq = freq_map.get(interval)
86
+
87
+ if not freq:
88
+ return df.reset_index()
89
+
90
+ resampled = (
91
+ df.resample(freq)
92
+ .agg(
93
+ {
94
+ "open": "first",
95
+ "high": "max",
96
+ "low": "min",
97
+ "close": "last",
98
+ "volume": "sum",
99
+ }
100
+ )
101
+ .dropna()
102
+ )
103
+
104
+ return resampled.reset_index()
@@ -0,0 +1,79 @@
1
+ import pandas as pd
2
+ from cachetools import cached
3
+ from .base import HistoricalDataProvider
4
+ from ..cache import CACHE_CONFIG
5
+ from ..eastmoney.client import EastMoneyClient
6
+ from ..eastmoney.utils import parse_kline_data, resample_historical_data
7
+
8
+
9
+ class EastMoneyDirectHistorical(HistoricalDataProvider):
10
+ """Direct implementation for EastMoney historical stock data API"""
11
+
12
+ def __init__(self, *args, **kwargs):
13
+ super().__init__(*args, **kwargs)
14
+ self.client = EastMoneyClient()
15
+
16
+ @cached(
17
+ cache=CACHE_CONFIG["hist_data_cache"],
18
+ key=lambda self: f"eastmoney_direct_hist_{self.symbol}_{self.interval}_{self.interval_multiplier}_{self.adjust}",
19
+ )
20
+ def get_hist_data(self) -> pd.DataFrame:
21
+ """Fetches EastMoney historical market data directly from API"""
22
+ self.interval = self.interval.lower()
23
+ self._validate_interval_params()
24
+
25
+ try:
26
+ klt = self._get_kline_type()
27
+ fqt = self._get_adjust_type()
28
+ start_date = self.start_date.replace("-", "")
29
+ end_date = self.end_date.replace("-", "")
30
+
31
+ raw_data = self.client.fetch_historical_klines(
32
+ symbol=self.symbol,
33
+ klt=klt,
34
+ fqt=fqt,
35
+ start_date=start_date,
36
+ end_date=end_date,
37
+ )
38
+
39
+ if raw_data.get("rc") != 0:
40
+ raise ValueError(f"API returned error: {raw_data.get('msg')}")
41
+
42
+ df = parse_kline_data(raw_data)
43
+
44
+ df = resample_historical_data(df, self.interval, self.interval_multiplier)
45
+
46
+ return df
47
+
48
+ except Exception as e:
49
+ raise ValueError(f"Failed to fetch historical data for {self.symbol}: {e}")
50
+
51
+ def _get_kline_type(self) -> str:
52
+ """Get K-line type based on interval."""
53
+ kline_map = {
54
+ "minute": "1",
55
+ "hour": "60",
56
+ "day": "101",
57
+ "week": "102",
58
+ "month": "103",
59
+ "year": "103",
60
+ }
61
+
62
+ base_klt = kline_map.get(self.interval, "101")
63
+
64
+ if self.interval == "minute" and self.interval_multiplier in [5, 15, 30, 60]:
65
+ return str(self.interval_multiplier)
66
+
67
+ return base_klt
68
+
69
+ def _get_adjust_type(self) -> str:
70
+ """Get adjustment type."""
71
+ adjust_map = {"none": "0", "qfq": "1", "hfq": "2"}
72
+ return adjust_map.get(self.adjust, "0")
73
+
74
+ def _validate_interval_params(self) -> None:
75
+ """Validates the interval and multiplier."""
76
+ if self.interval not in self.get_supported_intervals():
77
+ raise ValueError(f"Unsupported interval: {self.interval}")
78
+ if self.interval in ["minute", "hour"] and self.interval_multiplier < 1:
79
+ raise ValueError("Interval multiplier must be >= 1 for minute/hour.")
@@ -1,5 +1,6 @@
1
1
  from .base import HistoricalDataProvider
2
2
  from .eastmoney import EastMoneyHistorical
3
+ from .eastmoney_direct import EastMoneyDirectHistorical
3
4
  from .sina import SinaHistorical
4
5
 
5
6
 
@@ -10,6 +11,7 @@ class HistoricalDataFactory:
10
11
 
11
12
  _providers = {
12
13
  "eastmoney": EastMoneyHistorical,
14
+ "eastmoney_direct": EastMoneyDirectHistorical,
13
15
  "sina": SinaHistorical,
14
16
  }
15
17
 
@@ -0,0 +1,37 @@
1
+ import pandas as pd
2
+ from cachetools import cached
3
+ from .base import RealtimeDataProvider
4
+ from ..cache import CACHE_CONFIG
5
+ from ..eastmoney.client import EastMoneyClient
6
+ from ..eastmoney.utils import parse_realtime_data
7
+
8
+
9
+ class EastMoneyDirectRealtime(RealtimeDataProvider):
10
+ """Direct implementation for EastMoney realtime stock data API"""
11
+
12
+ def __init__(self, symbol: str):
13
+ super().__init__(symbol)
14
+ self.client = EastMoneyClient()
15
+
16
+ @cached(
17
+ cache=CACHE_CONFIG["realtime_cache"],
18
+ key=lambda self: f"eastmoney_direct_realtime_{self.symbol}",
19
+ )
20
+ def get_current_data(self) -> pd.DataFrame:
21
+ """Get real-time stock data"""
22
+ try:
23
+ raw_data = self.client.fetch_realtime_quote(self.symbol)
24
+
25
+ if raw_data.get("rc") != 0:
26
+ raise ValueError(f"API returned error: {raw_data.get('msg')}")
27
+
28
+ df = parse_realtime_data(raw_data)
29
+
30
+ # Ensure the output matches the base class definition
31
+ if self.symbol:
32
+ df = df[df["symbol"] == self.symbol].reset_index(drop=True)
33
+
34
+ return df
35
+
36
+ except Exception as e:
37
+ raise ValueError(f"Failed to get real-time data for {self.symbol}: {e}")
@@ -1,6 +1,7 @@
1
1
  from .eastmoney import EastmoneyRealtime
2
2
  from .xueqiu import XueQiuRealtime
3
3
  from .base import RealtimeDataProvider
4
+ from .eastmoney_direct import EastMoneyDirectRealtime
4
5
 
5
6
 
6
7
  class RealtimeDataFactory:
@@ -11,6 +12,7 @@ class RealtimeDataFactory:
11
12
  _providers = {
12
13
  "eastmoney": EastmoneyRealtime,
13
14
  "xueqiu": XueQiuRealtime,
15
+ "eastmoney_direct": EastMoneyDirectRealtime,
14
16
  }
15
17
 
16
18
  @classmethod
akshare_one/stock.py CHANGED
@@ -28,7 +28,7 @@ def get_hist_data(
28
28
  start_date: 开始日期 (YYYY-MM-DD)
29
29
  end_date: 结束日期 (YYYY-MM-DD)
30
30
  adjust: 复权类型 ('none','qfq','hfq')
31
- source: 数据源 ('eastmoney', 'sina') (default: 'eastmoney')
31
+ source: 数据源 ('eastmoney', 'eastmoney_direct', 'sina') (default: 'eastmoney')
32
32
 
33
33
  Returns:
34
34
  pd.DataFrame:
@@ -58,7 +58,7 @@ def get_realtime_data(
58
58
 
59
59
  Args:
60
60
  symbol: 股票代码 (如 "600000")
61
- source: 数据源 ('eastmoney', 'xueqiu')
61
+ source: 数据源 ('eastmoney', 'eastmoney_direct', 'xueqiu')
62
62
 
63
63
  Returns:
64
64
  pd.DataFrame:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akshare-one
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Standardized interface for Chinese financial market data, built on AKShare with unified data formats and simplified APIs
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/zwldarren/akshare-one
@@ -2,15 +2,18 @@ akshare_one/__init__.py,sha256=M4eXCnBzGqa5FihT-q7DHaluTvidnqwVF7AgPgCikKU,878
2
2
  akshare_one/financial.py,sha256=XAsonRzGK8akKtW2Q7LUrew4OFRnRAfZm0nw0JY73Jc,1426
3
3
  akshare_one/insider.py,sha256=fM6wvlLSGm7a2NkQFvxEK2PkhN5WudrO-V0BboVz2Bo,1092
4
4
  akshare_one/news.py,sha256=yrYeCaKTgCGP-TSyOfOou9gMw8185qiWrg380fD9-f8,669
5
- akshare_one/stock.py,sha256=7Suh8SWbn9LPO-dylu1oKnb25iFadiTL5lxLYC-uvDs,2226
5
+ akshare_one/stock.py,sha256=kldPmfggrgUH8pEcU8HGR5ofsxzYXJiUqj8GXCM5pBk,2266
6
6
  akshare_one/modules/cache.py,sha256=47A80Xtfr4gkKvEfBQrT8Dz8hNFR769rBa2gU6ew25s,373
7
7
  akshare_one/modules/utils.py,sha256=H4nrGf8m4_ezTiW5-OcNPxpV-neTYfffEfaOLDFLY9Y,323
8
+ akshare_one/modules/eastmoney/client.py,sha256=_fOXtvAbs65WHY3s2Ge1bsCTpVDbfkNj9yaVErF6GoU,3104
9
+ akshare_one/modules/eastmoney/utils.py,sha256=KzT0x6dF8y6wgc9Wzewr450NCee1i_dK2EhocBJQc5g,2896
8
10
  akshare_one/modules/financial/base.py,sha256=qj_XeujG7Tn2oO1niuIG4s7lMH3xg5zERDbcj75DUnU,536
9
11
  akshare_one/modules/financial/factory.py,sha256=GqzFp6LoHWj7t5VwtZJkLFxBuaAW_0b__bduBrmlcOg,1301
10
12
  akshare_one/modules/financial/sina.py,sha256=Hf6fVeN0gClS9GU2N9Hg6QJ1f8X4mecpEA6KWVgiwTc,10914
11
13
  akshare_one/modules/historical/base.py,sha256=UeKfMU_k8SgazXn27M1FVNaYJ5vBNgUB0jVBDpgXnz8,980
12
14
  akshare_one/modules/historical/eastmoney.py,sha256=2bYXobIwlrd5vG8pHlUZVE6edjQqn8AEGCVFGcWCCKk,8248
13
- akshare_one/modules/historical/factory.py,sha256=adxNNo_-PT180QXRUNwkdiN5x0lDJpPCt6rNFZcJ0ps,1389
15
+ akshare_one/modules/historical/eastmoney_direct.py,sha256=QRgGrY6NhAB2NAWCh_H3G0q2wKnSEnYQBRjMq-GjXGM,2814
16
+ akshare_one/modules/historical/factory.py,sha256=K1JwhScNPvuP-wR33C5VnosJYXJ3u1RykJuaMU6-3ls,1500
14
17
  akshare_one/modules/historical/sina.py,sha256=R-5hBOP5zslDuC0qfzw5zNhQyWmbmkmqB2sI2WhKVgI,7249
15
18
  akshare_one/modules/insider/base.py,sha256=xOhBDzMtZd1DutsHaJWe931P17fgW7dlivo1vz5ZflI,949
16
19
  akshare_one/modules/insider/factory.py,sha256=om51vX-GTXknpptZGvmDD9m10FfgEVmy402xZ3bBsNM,1280
@@ -20,10 +23,11 @@ akshare_one/modules/news/eastmoney.py,sha256=efVT4plk03s8TbSfhAhRLWgMLlR5G_2G0xG
20
23
  akshare_one/modules/news/factory.py,sha256=Em6c7m46K760iwIX3GZ15HdFu7pXT2w-n4QsjwHezjY,1264
21
24
  akshare_one/modules/realtime/base.py,sha256=XtD-4L1pCrMtfbwtZR7tP_BhWB_eMWtpQr89rAdX7P4,702
22
25
  akshare_one/modules/realtime/eastmoney.py,sha256=7IR35dwPr5xj0ErdBajYwyBivEoi55wd1Q5c0DEjUAI,1650
23
- akshare_one/modules/realtime/factory.py,sha256=SxDvJJvp6VutRqLUGYRnrUBtJEOE0N7vVh5Rw-vJ6NY,1373
26
+ akshare_one/modules/realtime/eastmoney_direct.py,sha256=yFH1fNcZRieyxFN5YzX7Gcw5-CR-dUEgUHSeR0NPD0g,1238
27
+ akshare_one/modules/realtime/factory.py,sha256=qHdCDY6dA7ck4JgK3yUDqcne9WrruAA6XzbRT69kT2w,1480
24
28
  akshare_one/modules/realtime/xueqiu.py,sha256=LKu0fW0EMt3c2m1w2kqBbEt9s2TcHUeBFpbFtueJgbU,2241
25
- akshare_one-0.2.1.dist-info/licenses/LICENSE,sha256=Gg6A1GNSJCZWQ73aHJ7TXOa0i8RQ3FejZCTZ6Db07cU,1066
26
- akshare_one-0.2.1.dist-info/METADATA,sha256=ofJg4rjif2v6WZJfiO4JjHk_-Q_I3-CDQXp6wPU7Vas,1897
27
- akshare_one-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
- akshare_one-0.2.1.dist-info/top_level.txt,sha256=kNiucyLVAGa89wmUSpXbBLWD7pF_RuahuiaOfLHZSyw,12
29
- akshare_one-0.2.1.dist-info/RECORD,,
29
+ akshare_one-0.2.2.dist-info/licenses/LICENSE,sha256=Gg6A1GNSJCZWQ73aHJ7TXOa0i8RQ3FejZCTZ6Db07cU,1066
30
+ akshare_one-0.2.2.dist-info/METADATA,sha256=vprtEz4JO60IGhEU4DoHpMmuuZ2WLAedIGLKjQVOCK8,1897
31
+ akshare_one-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
+ akshare_one-0.2.2.dist-info/top_level.txt,sha256=kNiucyLVAGa89wmUSpXbBLWD7pF_RuahuiaOfLHZSyw,12
33
+ akshare_one-0.2.2.dist-info/RECORD,,