akshare-one 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. {akshare_one-0.2.0 → akshare_one-0.2.2}/PKG-INFO +9 -9
  2. {akshare_one-0.2.0 → akshare_one-0.2.2}/README.md +6 -6
  3. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/insider.py +2 -5
  4. akshare_one-0.2.2/akshare_one/modules/eastmoney/client.py +88 -0
  5. akshare_one-0.2.2/akshare_one/modules/eastmoney/utils.py +104 -0
  6. akshare_one-0.2.2/akshare_one/modules/financial/base.py +22 -0
  7. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/financial/sina.py +1 -4
  8. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/historical/base.py +0 -25
  9. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/historical/eastmoney.py +1 -2
  10. akshare_one-0.2.2/akshare_one/modules/historical/eastmoney_direct.py +79 -0
  11. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/historical/factory.py +2 -0
  12. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/historical/sina.py +1 -2
  13. akshare_one-0.2.2/akshare_one/modules/insider/base.py +28 -0
  14. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/insider/xueqiu.py +1 -2
  15. akshare_one-0.2.2/akshare_one/modules/news/base.py +22 -0
  16. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/news/eastmoney.py +1 -2
  17. akshare_one-0.2.2/akshare_one/modules/realtime/base.py +27 -0
  18. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/realtime/eastmoney.py +1 -2
  19. akshare_one-0.2.2/akshare_one/modules/realtime/eastmoney_direct.py +37 -0
  20. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/realtime/factory.py +2 -0
  21. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/realtime/xueqiu.py +1 -2
  22. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/stock.py +2 -2
  23. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one.egg-info/PKG-INFO +9 -9
  24. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one.egg-info/SOURCES.txt +9 -1
  25. akshare_one-0.2.2/akshare_one.egg-info/requires.txt +2 -0
  26. {akshare_one-0.2.0 → akshare_one-0.2.2}/pyproject.toml +12 -5
  27. akshare_one-0.2.2/tests/test_financial.py +64 -0
  28. akshare_one-0.2.2/tests/test_insider.py +47 -0
  29. akshare_one-0.2.2/tests/test_news.py +50 -0
  30. akshare_one-0.2.2/tests/test_stock.py +133 -0
  31. akshare_one-0.2.0/akshare_one/modules/financial/base.py +0 -41
  32. akshare_one-0.2.0/akshare_one/modules/insider/base.py +0 -78
  33. akshare_one-0.2.0/akshare_one/modules/news/base.py +0 -51
  34. akshare_one-0.2.0/akshare_one/modules/realtime/base.py +0 -68
  35. akshare_one-0.2.0/akshare_one.egg-info/requires.txt +0 -2
  36. {akshare_one-0.2.0 → akshare_one-0.2.2}/LICENSE +0 -0
  37. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/__init__.py +0 -0
  38. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/financial.py +0 -0
  39. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/cache.py +0 -0
  40. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/financial/factory.py +0 -0
  41. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/insider/factory.py +0 -0
  42. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/news/factory.py +0 -0
  43. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/modules/utils.py +0 -0
  44. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one/news.py +0 -0
  45. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one.egg-info/dependency_links.txt +0 -0
  46. {akshare_one-0.2.0 → akshare_one-0.2.2}/akshare_one.egg-info/top_level.txt +0 -0
  47. {akshare_one-0.2.0 → akshare_one-0.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akshare-one
3
- Version: 0.2.0
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.94
13
- Requires-Dist: cachetools>=5.5.2
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
- <strong>English</strong> | <a href="README_zh.md">中文</a>
19
+ <a href="README_zh.md">中文</a> | <strong>English</strong>
20
20
  </div>
21
21
  </div>
22
22
 
23
- **AKShare One** is a standardized interface for Chinese financial market data, built as a wrapper around [AKShare](https://github.com/akfamily/akshare) to solve inconsistencies in input/output formats across different data sources.
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 parameters
29
+ - 🛠️ Simplified API parameter design
30
30
  - ⏱️ Automatic timestamp and adjustment handling
31
31
 
32
32
  ## 🚀 Core Features
33
33
 
34
- | Feature | Interface |
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
- | Insider trading | `get_inner_trade_data` |
40
+ | Internal transactions | `get_inner_trade_data` |
41
41
 
42
42
  ## 📦 Quick Installation
43
43
 
@@ -1,28 +1,28 @@
1
1
  <div align="center">
2
2
  <h1>AKShare One</h1>
3
3
  <div>
4
- <strong>English</strong> | <a href="README_zh.md">中文</a>
4
+ <a href="README_zh.md">中文</a> | <strong>English</strong>
5
5
  </div>
6
6
  </div>
7
7
 
8
- **AKShare One** is a standardized interface for Chinese financial market data, built as a wrapper around [AKShare](https://github.com/akfamily/akshare) to solve inconsistencies in input/output formats across different data sources.
8
+ **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.
9
9
 
10
10
  ## ✨ Features
11
11
 
12
12
  - 📊 Unified stock code formats across data sources
13
13
  - 🏗️ Standardized return data structures
14
- - 🛠️ Simplified API parameters
14
+ - 🛠️ Simplified API parameter design
15
15
  - ⏱️ Automatic timestamp and adjustment handling
16
16
 
17
17
  ## 🚀 Core Features
18
18
 
19
- | Feature | Interface |
20
- |---------|-----------|
19
+ | Function | Interface |
20
+ |------|------|
21
21
  | Historical data | `get_hist_data` |
22
22
  | Real-time quotes | `get_realtime_data` |
23
23
  | Stock news | `get_news_data` |
24
24
  | Financial data | `get_balance_sheet`/`get_income_statement`/`get_cash_flow` |
25
- | Insider trading | `get_inner_trade_data` |
25
+ | Internal transactions | `get_inner_trade_data` |
26
26
 
27
27
  ## 📦 Quick Installation
28
28
 
@@ -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: 可选股票代码,如"600000",不传则返回所有数据
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()
@@ -0,0 +1,22 @@
1
+ from abc import ABC, abstractmethod
2
+ import pandas as pd
3
+
4
+
5
+ class FinancialDataProvider(ABC):
6
+ def __init__(self, symbol: str) -> None:
7
+ self.symbol = symbol
8
+
9
+ @abstractmethod
10
+ def get_balance_sheet(self) -> pd.DataFrame:
11
+ """Fetches balance sheet data"""
12
+ pass
13
+
14
+ @abstractmethod
15
+ def get_income_statement(self) -> pd.DataFrame:
16
+ """Fetches income statement data"""
17
+ pass
18
+
19
+ @abstractmethod
20
+ def get_cash_flow(self) -> pd.DataFrame:
21
+ """Fetches cash flow data"""
22
+ pass
@@ -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, validate_financial_data
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, validate_hist_data
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, validate_hist_data
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}",
@@ -0,0 +1,28 @@
1
+ from abc import ABC, abstractmethod
2
+ import pandas as pd
3
+
4
+
5
+ class InsiderDataProvider(ABC):
6
+ def __init__(self, symbol: str) -> None:
7
+ self.symbol = symbol
8
+
9
+ @abstractmethod
10
+ def get_inner_trade_data(self) -> pd.DataFrame:
11
+ """Fetches insider trade data
12
+
13
+ Returns:
14
+ pd.DataFrame:
15
+ - symbol: 股票代码
16
+ - issuer: 股票名称
17
+ - name: 变动人
18
+ - title: 董监高职务
19
+ - transaction_date: 变动日期(UTC时区)
20
+ - transaction_shares: 变动股数
21
+ - transaction_price_per_share: 成交均价
22
+ - shares_owned_after_transaction: 变动后持股数
23
+ - relationship: 与董监高关系
24
+ - is_board_director: 是否为董事会成员
25
+ - transaction_value: 交易金额(变动股数*成交均价)
26
+ - shares_owned_before_transaction: 变动前持股数
27
+ """
28
+ pass
@@ -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, validate_insider_data
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'}",
@@ -0,0 +1,22 @@
1
+ from abc import ABC, abstractmethod
2
+ import pandas as pd
3
+
4
+
5
+ class NewsDataProvider(ABC):
6
+ def __init__(self, symbol: str) -> None:
7
+ self.symbol = symbol
8
+
9
+ @abstractmethod
10
+ def get_news_data(self) -> pd.DataFrame:
11
+ """Fetches news data for given symbol
12
+
13
+ Returns:
14
+ pd.DataFrame:
15
+ - keyword: 关键词
16
+ - title: 新闻标题
17
+ - content: 新闻内容
18
+ - publish_time: 发布时间 (UTC)
19
+ - source: 文章来源
20
+ - url: 新闻链接
21
+ """
22
+ pass
@@ -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, validate_news_data
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}",
@@ -0,0 +1,27 @@
1
+ from abc import ABC, abstractmethod
2
+ import pandas as pd
3
+
4
+
5
+ class RealtimeDataProvider(ABC):
6
+ def __init__(self, symbol: str) -> None:
7
+ self.symbol = symbol
8
+
9
+ @abstractmethod
10
+ def get_current_data(self) -> pd.DataFrame:
11
+ """Fetches realtime market data
12
+
13
+ Returns:
14
+ pd.DataFrame:
15
+ - symbol: 股票代码
16
+ - price: 最新价
17
+ - change: 涨跌额
18
+ - pct_change: 涨跌幅(%)
19
+ - timestamp: 时间戳
20
+ - volume: 成交量(手)
21
+ - amount: 成交额(元)
22
+ - open: 今开
23
+ - high: 最高
24
+ - low: 最低
25
+ - prev_close: 昨收
26
+ """
27
+ pass
@@ -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, validate_realtime_data
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'}",