akshare-one 0.2.0__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.
- akshare_one/insider.py +2 -5
- akshare_one/modules/eastmoney/client.py +88 -0
- akshare_one/modules/eastmoney/utils.py +104 -0
- akshare_one/modules/financial/base.py +0 -19
- akshare_one/modules/financial/sina.py +1 -4
- akshare_one/modules/historical/base.py +0 -25
- akshare_one/modules/historical/eastmoney.py +1 -2
- akshare_one/modules/historical/eastmoney_direct.py +79 -0
- akshare_one/modules/historical/factory.py +2 -0
- akshare_one/modules/historical/sina.py +1 -2
- akshare_one/modules/insider/base.py +2 -52
- akshare_one/modules/insider/xueqiu.py +1 -2
- akshare_one/modules/news/base.py +0 -29
- akshare_one/modules/news/eastmoney.py +1 -2
- akshare_one/modules/realtime/base.py +0 -41
- akshare_one/modules/realtime/eastmoney.py +1 -2
- akshare_one/modules/realtime/eastmoney_direct.py +37 -0
- akshare_one/modules/realtime/factory.py +2 -0
- akshare_one/modules/realtime/xueqiu.py +1 -2
- akshare_one/stock.py +2 -2
- {akshare_one-0.2.0.dist-info → akshare_one-0.2.2.dist-info}/METADATA +9 -9
- akshare_one-0.2.2.dist-info/RECORD +33 -0
- {akshare_one-0.2.0.dist-info → akshare_one-0.2.2.dist-info}/WHEEL +1 -1
- akshare_one-0.2.0.dist-info/RECORD +0 -29
- {akshare_one-0.2.0.dist-info → akshare_one-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {akshare_one-0.2.0.dist-info → akshare_one-0.2.2.dist-info}/top_level.txt +0 -0
akshare_one/insider.py
CHANGED
@@ -3,19 +3,16 @@
|
|
3
3
|
包含上市公司内部交易相关功能
|
4
4
|
"""
|
5
5
|
|
6
|
-
from typing import Optional
|
7
6
|
import pandas as pd
|
8
7
|
from .modules.insider.factory import InsiderDataFactory
|
9
8
|
|
10
9
|
|
11
|
-
def get_inner_trade_data(
|
12
|
-
symbol: Optional[str] = None, source: str = "xueqiu"
|
13
|
-
) -> "pd.DataFrame":
|
10
|
+
def get_inner_trade_data(symbol: str, source: str = "xueqiu") -> "pd.DataFrame":
|
14
11
|
"""获取雪球内部交易数据
|
15
12
|
|
16
13
|
Args:
|
17
14
|
source: 数据源 (目前支持 "xueqiu")
|
18
|
-
symbol:
|
15
|
+
symbol: 股票代码,如"600000"
|
19
16
|
|
20
17
|
Returns:
|
21
18
|
pd.DataFrame:
|
@@ -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()
|
@@ -2,25 +2,6 @@ from abc import ABC, abstractmethod
|
|
2
2
|
import pandas as pd
|
3
3
|
|
4
4
|
|
5
|
-
def validate_financial_data(func):
|
6
|
-
"""Decorator to validate financial data returned by data providers"""
|
7
|
-
|
8
|
-
def wrapper(*args, **kwargs):
|
9
|
-
df = func(*args, **kwargs)
|
10
|
-
|
11
|
-
if not isinstance(df, pd.DataFrame):
|
12
|
-
raise ValueError("Returned data must be a pandas DataFrame")
|
13
|
-
|
14
|
-
# Validate report_date if present
|
15
|
-
if "report_date" in df.columns:
|
16
|
-
if not pd.api.types.is_datetime64_any_dtype(df["report_date"]):
|
17
|
-
raise ValueError("report_date must be datetime64 dtype")
|
18
|
-
|
19
|
-
return df
|
20
|
-
|
21
|
-
return wrapper
|
22
|
-
|
23
|
-
|
24
5
|
class FinancialDataProvider(ABC):
|
25
6
|
def __init__(self, symbol: str) -> None:
|
26
7
|
self.symbol = symbol
|
@@ -3,7 +3,7 @@ import pandas as pd
|
|
3
3
|
import akshare as ak
|
4
4
|
|
5
5
|
from akshare_one.modules.cache import CACHE_CONFIG
|
6
|
-
from .base import FinancialDataProvider
|
6
|
+
from .base import FinancialDataProvider
|
7
7
|
|
8
8
|
|
9
9
|
class SinaFinancialReport(FinancialDataProvider):
|
@@ -13,7 +13,6 @@ class SinaFinancialReport(FinancialDataProvider):
|
|
13
13
|
f"sh{symbol}" if not symbol.startswith(("sh", "sz", "bj")) else symbol
|
14
14
|
)
|
15
15
|
|
16
|
-
@validate_financial_data
|
17
16
|
@cached(
|
18
17
|
CACHE_CONFIG["financial_cache"],
|
19
18
|
key=lambda self, symbol=None: f"sina_balance_{self.symbol}",
|
@@ -30,7 +29,6 @@ class SinaFinancialReport(FinancialDataProvider):
|
|
30
29
|
raw_df = ak.stock_financial_report_sina(stock=self.stock, symbol="资产负债表")
|
31
30
|
return self._clean_balance_data(raw_df)
|
32
31
|
|
33
|
-
@validate_financial_data
|
34
32
|
@cached(
|
35
33
|
CACHE_CONFIG["financial_cache"],
|
36
34
|
key=lambda self, symbol=None: f"sina_income_{self.symbol}",
|
@@ -47,7 +45,6 @@ class SinaFinancialReport(FinancialDataProvider):
|
|
47
45
|
raw_df = ak.stock_financial_report_sina(stock=self.stock, symbol="利润表")
|
48
46
|
return self._clean_income_data(raw_df)
|
49
47
|
|
50
|
-
@validate_financial_data
|
51
48
|
@cached(
|
52
49
|
CACHE_CONFIG["financial_cache"],
|
53
50
|
key=lambda self, symbol=None: f"sina_cash_{self.symbol}",
|
@@ -2,31 +2,6 @@ from abc import ABC, abstractmethod
|
|
2
2
|
import pandas as pd
|
3
3
|
|
4
4
|
|
5
|
-
def validate_hist_data(func):
|
6
|
-
"""Decorator to validate historical data returned by data providers"""
|
7
|
-
|
8
|
-
def wrapper(*args, **kwargs):
|
9
|
-
df = func(*args, **kwargs)
|
10
|
-
|
11
|
-
if not isinstance(df, pd.DataFrame):
|
12
|
-
raise ValueError("Returned data must be a pandas DataFrame")
|
13
|
-
|
14
|
-
required_columns = {"timestamp", "open", "high", "low", "close", "volume"}
|
15
|
-
missing_cols = required_columns - set(df.columns)
|
16
|
-
if missing_cols:
|
17
|
-
raise ValueError(f"Missing required columns: {missing_cols}")
|
18
|
-
|
19
|
-
if "timestamp" in df.columns:
|
20
|
-
if not pd.api.types.is_datetime64_any_dtype(df["timestamp"]):
|
21
|
-
raise ValueError("timestamp must be datetime64 dtype")
|
22
|
-
if df["timestamp"].dt.tz is None or str(df["timestamp"].dt.tz) != "UTC":
|
23
|
-
raise ValueError("timestamp must be in UTC timezone")
|
24
|
-
|
25
|
-
return df
|
26
|
-
|
27
|
-
return wrapper
|
28
|
-
|
29
|
-
|
30
5
|
class HistoricalDataProvider(ABC):
|
31
6
|
def __init__(
|
32
7
|
self,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from cachetools import cached
|
2
|
-
from .base import HistoricalDataProvider
|
2
|
+
from .base import HistoricalDataProvider
|
3
3
|
import akshare as ak
|
4
4
|
import pandas as pd
|
5
5
|
from ..cache import CACHE_CONFIG
|
@@ -8,7 +8,6 @@ from ..cache import CACHE_CONFIG
|
|
8
8
|
class EastMoneyHistorical(HistoricalDataProvider):
|
9
9
|
"""Adapter for EastMoney historical stock data API"""
|
10
10
|
|
11
|
-
@validate_hist_data
|
12
11
|
@cached(
|
13
12
|
cache=CACHE_CONFIG["hist_data_cache"],
|
14
13
|
key=lambda self: f"eastmoney_hist_{self.symbol}_{self.interval}_{self.interval_multiplier}_{self.adjust}",
|
@@ -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
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from cachetools import cached
|
2
|
-
from .base import HistoricalDataProvider
|
2
|
+
from .base import HistoricalDataProvider
|
3
3
|
import akshare as ak
|
4
4
|
import pandas as pd
|
5
5
|
from ..cache import CACHE_CONFIG
|
@@ -8,7 +8,6 @@ from ..cache import CACHE_CONFIG
|
|
8
8
|
class SinaHistorical(HistoricalDataProvider):
|
9
9
|
"""Adapter for Sina historical stock data API"""
|
10
10
|
|
11
|
-
@validate_hist_data
|
12
11
|
@cached(
|
13
12
|
cache=CACHE_CONFIG["hist_data_cache"],
|
14
13
|
key=lambda self: f"sina_hist_{self.symbol}_{self.interval}_{self.interval_multiplier}_{self.adjust}",
|
@@ -1,63 +1,13 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
2
|
import pandas as pd
|
3
|
-
from typing import Optional
|
4
|
-
|
5
|
-
|
6
|
-
def validate_insider_data(func):
|
7
|
-
"""Decorator to validate insider trading data returned by data providers"""
|
8
|
-
|
9
|
-
def wrapper(*args, **kwargs):
|
10
|
-
df = func(*args, **kwargs)
|
11
|
-
|
12
|
-
if not isinstance(df, pd.DataFrame):
|
13
|
-
raise ValueError("Returned data must be a pandas DataFrame")
|
14
|
-
|
15
|
-
# Required fields for insider trading data
|
16
|
-
required_fields = {
|
17
|
-
"symbol",
|
18
|
-
"issuer",
|
19
|
-
"name",
|
20
|
-
"transaction_date",
|
21
|
-
"transaction_shares",
|
22
|
-
"transaction_price_per_share",
|
23
|
-
}
|
24
|
-
if not required_fields.issubset(df.columns):
|
25
|
-
missing = required_fields - set(df.columns)
|
26
|
-
raise ValueError(f"Missing required fields: {missing}")
|
27
|
-
|
28
|
-
# Validate timestamp if present
|
29
|
-
if "transaction_date" in df.columns:
|
30
|
-
if not pd.api.types.is_datetime64_any_dtype(df["transaction_date"]):
|
31
|
-
raise ValueError("transaction_date must be datetime64 dtype")
|
32
|
-
if (
|
33
|
-
df["transaction_date"].dt.tz is None
|
34
|
-
or str(df["transaction_date"].dt.tz) != "UTC"
|
35
|
-
):
|
36
|
-
raise ValueError("transaction_date must be in UTC timezone")
|
37
|
-
|
38
|
-
# Validate numeric fields
|
39
|
-
numeric_fields = {
|
40
|
-
"transaction_shares",
|
41
|
-
"transaction_price_per_share",
|
42
|
-
"transaction_value",
|
43
|
-
"shares_owned_before_transaction",
|
44
|
-
"shares_owned_after_transaction",
|
45
|
-
}
|
46
|
-
for field in numeric_fields & set(df.columns):
|
47
|
-
if not pd.api.types.is_numeric_dtype(df[field]):
|
48
|
-
raise ValueError(f"{field} must be numeric")
|
49
|
-
|
50
|
-
return df
|
51
|
-
|
52
|
-
return wrapper
|
53
3
|
|
54
4
|
|
55
5
|
class InsiderDataProvider(ABC):
|
56
|
-
def __init__(self, symbol:
|
6
|
+
def __init__(self, symbol: str) -> None:
|
57
7
|
self.symbol = symbol
|
58
8
|
|
59
9
|
@abstractmethod
|
60
|
-
def get_inner_trade_data(self
|
10
|
+
def get_inner_trade_data(self) -> pd.DataFrame:
|
61
11
|
"""Fetches insider trade data
|
62
12
|
|
63
13
|
Returns:
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from cachetools import cached
|
2
2
|
import pandas as pd
|
3
3
|
import akshare as ak
|
4
|
-
from .base import InsiderDataProvider
|
4
|
+
from .base import InsiderDataProvider
|
5
5
|
from ..utils import convert_xieqiu_symbol
|
6
6
|
from ..cache import CACHE_CONFIG
|
7
7
|
|
@@ -9,7 +9,6 @@ from ..cache import CACHE_CONFIG
|
|
9
9
|
class XueQiuInsider(InsiderDataProvider):
|
10
10
|
"""Provider for XueQiu insider trading data"""
|
11
11
|
|
12
|
-
@validate_insider_data
|
13
12
|
@cached(
|
14
13
|
cache=CACHE_CONFIG["financial_cache"],
|
15
14
|
key=lambda self, symbol=None: f"xueqiu_insider_{symbol if symbol else 'all'}",
|
akshare_one/modules/news/base.py
CHANGED
@@ -2,35 +2,6 @@ from abc import ABC, abstractmethod
|
|
2
2
|
import pandas as pd
|
3
3
|
|
4
4
|
|
5
|
-
def validate_news_data(func):
|
6
|
-
"""Decorator to validate news data returned by data providers"""
|
7
|
-
|
8
|
-
def wrapper(*args, **kwargs):
|
9
|
-
df = func(*args, **kwargs)
|
10
|
-
|
11
|
-
if not isinstance(df, pd.DataFrame):
|
12
|
-
raise ValueError("Returned data must be a pandas DataFrame")
|
13
|
-
|
14
|
-
# Required fields
|
15
|
-
required_fields = {"title", "publish_time"}
|
16
|
-
if not required_fields & set(df.columns):
|
17
|
-
raise ValueError(f"Must contain all required fields: {required_fields}")
|
18
|
-
|
19
|
-
# Validate publish_time if present
|
20
|
-
if "publish_time" in df.columns:
|
21
|
-
if not pd.api.types.is_datetime64_any_dtype(df["publish_time"]):
|
22
|
-
raise ValueError("publish_time must be datetime64 dtype")
|
23
|
-
if (
|
24
|
-
df["publish_time"].dt.tz is None
|
25
|
-
or str(df["publish_time"].dt.tz) != "UTC"
|
26
|
-
):
|
27
|
-
raise ValueError("publish_time must be in UTC timezone")
|
28
|
-
|
29
|
-
return df
|
30
|
-
|
31
|
-
return wrapper
|
32
|
-
|
33
|
-
|
34
5
|
class NewsDataProvider(ABC):
|
35
6
|
def __init__(self, symbol: str) -> None:
|
36
7
|
self.symbol = symbol
|
@@ -3,11 +3,10 @@ import pandas as pd
|
|
3
3
|
import akshare as ak
|
4
4
|
|
5
5
|
from ..cache import CACHE_CONFIG
|
6
|
-
from .base import NewsDataProvider
|
6
|
+
from .base import NewsDataProvider
|
7
7
|
|
8
8
|
|
9
9
|
class EastMoneyNews(NewsDataProvider):
|
10
|
-
@validate_news_data
|
11
10
|
@cached(
|
12
11
|
CACHE_CONFIG["news_cache"],
|
13
12
|
key=lambda self: f"eastmoney_news_{self.symbol}",
|
@@ -2,47 +2,6 @@ from abc import ABC, abstractmethod
|
|
2
2
|
import pandas as pd
|
3
3
|
|
4
4
|
|
5
|
-
def validate_realtime_data(func):
|
6
|
-
"""Decorator to validate realtime data returned by data providers"""
|
7
|
-
|
8
|
-
def wrapper(*args, **kwargs):
|
9
|
-
df = func(*args, **kwargs)
|
10
|
-
|
11
|
-
if not isinstance(df, pd.DataFrame):
|
12
|
-
raise ValueError("Returned data must be a pandas DataFrame")
|
13
|
-
|
14
|
-
# At least one of these core fields must be present
|
15
|
-
core_fields = {"timestamp", "price", "volume"}
|
16
|
-
if not core_fields & set(df.columns):
|
17
|
-
raise ValueError(f"Must contain at least one of: {core_fields}")
|
18
|
-
|
19
|
-
# Validate timestamp if present
|
20
|
-
if "timestamp" in df.columns:
|
21
|
-
if not pd.api.types.is_datetime64_any_dtype(df["timestamp"]):
|
22
|
-
raise ValueError("timestamp must be datetime64 dtype")
|
23
|
-
if df["timestamp"].dt.tz is None or str(df["timestamp"].dt.tz) != "UTC":
|
24
|
-
raise ValueError("timestamp must be in UTC timezone")
|
25
|
-
|
26
|
-
# Validate numeric fields if present
|
27
|
-
numeric_fields = {
|
28
|
-
"price",
|
29
|
-
"change",
|
30
|
-
"pct_change",
|
31
|
-
"open",
|
32
|
-
"high",
|
33
|
-
"low",
|
34
|
-
"prev_close",
|
35
|
-
"amount",
|
36
|
-
}
|
37
|
-
for field in numeric_fields & set(df.columns):
|
38
|
-
if not pd.api.types.is_numeric_dtype(df[field]):
|
39
|
-
raise ValueError(f"{field} must be numeric")
|
40
|
-
|
41
|
-
return df
|
42
|
-
|
43
|
-
return wrapper
|
44
|
-
|
45
|
-
|
46
5
|
class RealtimeDataProvider(ABC):
|
47
6
|
def __init__(self, symbol: str) -> None:
|
48
7
|
self.symbol = symbol
|
@@ -3,11 +3,10 @@ import pandas as pd
|
|
3
3
|
import akshare as ak
|
4
4
|
|
5
5
|
from ..cache import CACHE_CONFIG
|
6
|
-
from .base import RealtimeDataProvider
|
6
|
+
from .base import RealtimeDataProvider
|
7
7
|
|
8
8
|
|
9
9
|
class EastmoneyRealtime(RealtimeDataProvider):
|
10
|
-
@validate_realtime_data
|
11
10
|
@cached(
|
12
11
|
CACHE_CONFIG["realtime_cache"],
|
13
12
|
key=lambda self, symbol=None: f"eastmoney_{symbol if symbol else 'all'}",
|
@@ -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
|
@@ -3,11 +3,10 @@ import pandas as pd
|
|
3
3
|
import akshare as ak
|
4
4
|
from ..utils import convert_xieqiu_symbol
|
5
5
|
from ..cache import CACHE_CONFIG
|
6
|
-
from .base import RealtimeDataProvider
|
6
|
+
from .base import RealtimeDataProvider
|
7
7
|
|
8
8
|
|
9
9
|
class XueQiuRealtime(RealtimeDataProvider):
|
10
|
-
@validate_realtime_data
|
11
10
|
@cached(
|
12
11
|
cache=CACHE_CONFIG["realtime_cache"],
|
13
12
|
key=lambda self, symbol=None: f"xueqiu_{symbol}",
|
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')
|
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.
|
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
|
@@ -9,35 +9,35 @@ Keywords: akshare,financial-data,stock-data,quant
|
|
9
9
|
Requires-Python: >=3.12
|
10
10
|
Description-Content-Type: text/markdown
|
11
11
|
License-File: LICENSE
|
12
|
-
Requires-Dist: akshare>=1.16.
|
13
|
-
Requires-Dist: cachetools>=
|
12
|
+
Requires-Dist: akshare>=1.16.98
|
13
|
+
Requires-Dist: cachetools>=6.0.0
|
14
14
|
Dynamic: license-file
|
15
15
|
|
16
16
|
<div align="center">
|
17
17
|
<h1>AKShare One</h1>
|
18
18
|
<div>
|
19
|
-
<
|
19
|
+
<a href="README_zh.md">中文</a> | <strong>English</strong>
|
20
20
|
</div>
|
21
21
|
</div>
|
22
22
|
|
23
|
-
**AKShare One** is a
|
23
|
+
**AKShare One** is a data interface for obtaining Chinese A-shares, based on [AKShare](https://github.com/akfamily/akshare). It aims to simplify AKShare's usage and unify input/output formats from different data sources, making it easier to pass data to LLM.
|
24
24
|
|
25
25
|
## ✨ Features
|
26
26
|
|
27
27
|
- 📊 Unified stock code formats across data sources
|
28
28
|
- 🏗️ Standardized return data structures
|
29
|
-
- 🛠️ Simplified API
|
29
|
+
- 🛠️ Simplified API parameter design
|
30
30
|
- ⏱️ Automatic timestamp and adjustment handling
|
31
31
|
|
32
32
|
## 🚀 Core Features
|
33
33
|
|
34
|
-
|
|
35
|
-
|
34
|
+
| Function | Interface |
|
35
|
+
|------|------|
|
36
36
|
| Historical data | `get_hist_data` |
|
37
37
|
| Real-time quotes | `get_realtime_data` |
|
38
38
|
| Stock news | `get_news_data` |
|
39
39
|
| Financial data | `get_balance_sheet`/`get_income_statement`/`get_cash_flow` |
|
40
|
-
|
|
40
|
+
| Internal transactions | `get_inner_trade_data` |
|
41
41
|
|
42
42
|
## 📦 Quick Installation
|
43
43
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
akshare_one/__init__.py,sha256=M4eXCnBzGqa5FihT-q7DHaluTvidnqwVF7AgPgCikKU,878
|
2
|
+
akshare_one/financial.py,sha256=XAsonRzGK8akKtW2Q7LUrew4OFRnRAfZm0nw0JY73Jc,1426
|
3
|
+
akshare_one/insider.py,sha256=fM6wvlLSGm7a2NkQFvxEK2PkhN5WudrO-V0BboVz2Bo,1092
|
4
|
+
akshare_one/news.py,sha256=yrYeCaKTgCGP-TSyOfOou9gMw8185qiWrg380fD9-f8,669
|
5
|
+
akshare_one/stock.py,sha256=kldPmfggrgUH8pEcU8HGR5ofsxzYXJiUqj8GXCM5pBk,2266
|
6
|
+
akshare_one/modules/cache.py,sha256=47A80Xtfr4gkKvEfBQrT8Dz8hNFR769rBa2gU6ew25s,373
|
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
|
10
|
+
akshare_one/modules/financial/base.py,sha256=qj_XeujG7Tn2oO1niuIG4s7lMH3xg5zERDbcj75DUnU,536
|
11
|
+
akshare_one/modules/financial/factory.py,sha256=GqzFp6LoHWj7t5VwtZJkLFxBuaAW_0b__bduBrmlcOg,1301
|
12
|
+
akshare_one/modules/financial/sina.py,sha256=Hf6fVeN0gClS9GU2N9Hg6QJ1f8X4mecpEA6KWVgiwTc,10914
|
13
|
+
akshare_one/modules/historical/base.py,sha256=UeKfMU_k8SgazXn27M1FVNaYJ5vBNgUB0jVBDpgXnz8,980
|
14
|
+
akshare_one/modules/historical/eastmoney.py,sha256=2bYXobIwlrd5vG8pHlUZVE6edjQqn8AEGCVFGcWCCKk,8248
|
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
|
17
|
+
akshare_one/modules/historical/sina.py,sha256=R-5hBOP5zslDuC0qfzw5zNhQyWmbmkmqB2sI2WhKVgI,7249
|
18
|
+
akshare_one/modules/insider/base.py,sha256=xOhBDzMtZd1DutsHaJWe931P17fgW7dlivo1vz5ZflI,949
|
19
|
+
akshare_one/modules/insider/factory.py,sha256=om51vX-GTXknpptZGvmDD9m10FfgEVmy402xZ3bBsNM,1280
|
20
|
+
akshare_one/modules/insider/xueqiu.py,sha256=2gmSrcer8qw2Jb80kYT3tHNJ3U88hxd0dJSfoh846EY,3971
|
21
|
+
akshare_one/modules/news/base.py,sha256=yrBZf1q1QwJkCXygNmeLIEjvcqm1iti63tjb3s40L_4,559
|
22
|
+
akshare_one/modules/news/eastmoney.py,sha256=efVT4plk03s8TbSfhAhRLWgMLlR5G_2G0xGZTQgSxmY,1333
|
23
|
+
akshare_one/modules/news/factory.py,sha256=Em6c7m46K760iwIX3GZ15HdFu7pXT2w-n4QsjwHezjY,1264
|
24
|
+
akshare_one/modules/realtime/base.py,sha256=XtD-4L1pCrMtfbwtZR7tP_BhWB_eMWtpQr89rAdX7P4,702
|
25
|
+
akshare_one/modules/realtime/eastmoney.py,sha256=7IR35dwPr5xj0ErdBajYwyBivEoi55wd1Q5c0DEjUAI,1650
|
26
|
+
akshare_one/modules/realtime/eastmoney_direct.py,sha256=yFH1fNcZRieyxFN5YzX7Gcw5-CR-dUEgUHSeR0NPD0g,1238
|
27
|
+
akshare_one/modules/realtime/factory.py,sha256=qHdCDY6dA7ck4JgK3yUDqcne9WrruAA6XzbRT69kT2w,1480
|
28
|
+
akshare_one/modules/realtime/xueqiu.py,sha256=LKu0fW0EMt3c2m1w2kqBbEt9s2TcHUeBFpbFtueJgbU,2241
|
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,,
|
@@ -1,29 +0,0 @@
|
|
1
|
-
akshare_one/__init__.py,sha256=M4eXCnBzGqa5FihT-q7DHaluTvidnqwVF7AgPgCikKU,878
|
2
|
-
akshare_one/financial.py,sha256=XAsonRzGK8akKtW2Q7LUrew4OFRnRAfZm0nw0JY73Jc,1426
|
3
|
-
akshare_one/insider.py,sha256=fnbVT--7jGCvULhsiKNqLmZggo1VmR-eNOr4iq5BI7I,1179
|
4
|
-
akshare_one/news.py,sha256=yrYeCaKTgCGP-TSyOfOou9gMw8185qiWrg380fD9-f8,669
|
5
|
-
akshare_one/stock.py,sha256=K8KOHxHszHoWmdhgO4sj-6zfIUSMS--0PdzgsAOGk88,2203
|
6
|
-
akshare_one/modules/cache.py,sha256=47A80Xtfr4gkKvEfBQrT8Dz8hNFR769rBa2gU6ew25s,373
|
7
|
-
akshare_one/modules/utils.py,sha256=H4nrGf8m4_ezTiW5-OcNPxpV-neTYfffEfaOLDFLY9Y,323
|
8
|
-
akshare_one/modules/financial/base.py,sha256=JRZJhzl7kP16KEVOXP9mq1DjXW6rga2FJ8-sI5FH814,1107
|
9
|
-
akshare_one/modules/financial/factory.py,sha256=GqzFp6LoHWj7t5VwtZJkLFxBuaAW_0b__bduBrmlcOg,1301
|
10
|
-
akshare_one/modules/financial/sina.py,sha256=gHrbmykselWZC5tqR1Zkyc3oK0TcCCdCn_1z_8C2y_U,11026
|
11
|
-
akshare_one/modules/historical/base.py,sha256=hIk-DKxaDXfx9y9VWx-b918ntv6pCeqxjHpAiS7A5ZQ,1895
|
12
|
-
akshare_one/modules/historical/eastmoney.py,sha256=8lTZjbQ-P713vBvJxXTBmn6hPpNslWf5uAW8R6HJtyo,8292
|
13
|
-
akshare_one/modules/historical/factory.py,sha256=adxNNo_-PT180QXRUNwkdiN5x0lDJpPCt6rNFZcJ0ps,1389
|
14
|
-
akshare_one/modules/historical/sina.py,sha256=d-AWr4C72nvPYOC9IETqUXDoksofztWyoe9Ie9TC9Z0,7293
|
15
|
-
akshare_one/modules/insider/base.py,sha256=_TDvHr6TZa0sTynE_ZL0njnms-cb5Wd8hfqR7pdfvrU,2728
|
16
|
-
akshare_one/modules/insider/factory.py,sha256=om51vX-GTXknpptZGvmDD9m10FfgEVmy402xZ3bBsNM,1280
|
17
|
-
akshare_one/modules/insider/xueqiu.py,sha256=8ZBRn8tW4r0aTEsoaQuYzcNQ-q2oQ64Cn7VG_Hs8hhE,4021
|
18
|
-
akshare_one/modules/news/base.py,sha256=L55BLQionqTZGmrHZDpmNKuhcWQdIN8ZKR9jsMj1Bgo,1550
|
19
|
-
akshare_one/modules/news/eastmoney.py,sha256=iuZgpvub9zKDJNtGIShFXBQRHIG10oEvdMiYFDvbsdI,1377
|
20
|
-
akshare_one/modules/news/factory.py,sha256=Em6c7m46K760iwIX3GZ15HdFu7pXT2w-n4QsjwHezjY,1264
|
21
|
-
akshare_one/modules/realtime/base.py,sha256=Yztkh9IhCr0Y-N8SH81APL9uNr4ZMd10B5KfcC6Ekcg,2093
|
22
|
-
akshare_one/modules/realtime/eastmoney.py,sha256=PPfRC3LUIH0HWbjkgfHgoER5Aq8CtqGFbazjJdYFqcQ,1702
|
23
|
-
akshare_one/modules/realtime/factory.py,sha256=SxDvJJvp6VutRqLUGYRnrUBtJEOE0N7vVh5Rw-vJ6NY,1373
|
24
|
-
akshare_one/modules/realtime/xueqiu.py,sha256=WnSRktk4eyFV8_8c9SpCtL6UN08vdZdzFpCyhMyLRPM,2293
|
25
|
-
akshare_one-0.2.0.dist-info/licenses/LICENSE,sha256=Gg6A1GNSJCZWQ73aHJ7TXOa0i8RQ3FejZCTZ6Db07cU,1066
|
26
|
-
akshare_one-0.2.0.dist-info/METADATA,sha256=glFdSHMMfGgYlg1DIZvQ1r0xKnMCQHFgV215lM2pmbo,1865
|
27
|
-
akshare_one-0.2.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
28
|
-
akshare_one-0.2.0.dist-info/top_level.txt,sha256=kNiucyLVAGa89wmUSpXbBLWD7pF_RuahuiaOfLHZSyw,12
|
29
|
-
akshare_one-0.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|