akshare-one 0.3.1__py3-none-any.whl → 0.3.3__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/__init__.py +214 -31
- akshare_one/indicators.py +395 -395
- akshare_one/modules/cache.py +10 -9
- akshare_one/modules/eastmoney/client.py +88 -88
- akshare_one/modules/eastmoney/utils.py +104 -104
- akshare_one/modules/financial/base.py +27 -22
- akshare_one/modules/financial/eastmoney.py +184 -0
- akshare_one/modules/financial/factory.py +46 -44
- akshare_one/modules/financial/sina.py +298 -273
- akshare_one/modules/historical/base.py +47 -47
- akshare_one/modules/historical/eastmoney.py +241 -241
- akshare_one/modules/historical/eastmoney_direct.py +79 -79
- akshare_one/modules/historical/factory.py +48 -48
- akshare_one/modules/historical/sina.py +254 -254
- akshare_one/modules/indicators/base.py +158 -158
- akshare_one/modules/indicators/factory.py +33 -33
- akshare_one/modules/indicators/simple.py +230 -230
- akshare_one/modules/indicators/talib.py +263 -263
- akshare_one/modules/info/base.py +25 -0
- akshare_one/modules/info/eastmoney.py +52 -0
- akshare_one/modules/info/factory.py +44 -0
- akshare_one/modules/insider/base.py +28 -28
- akshare_one/modules/insider/factory.py +44 -44
- akshare_one/modules/insider/xueqiu.py +115 -115
- akshare_one/modules/news/base.py +22 -22
- akshare_one/modules/news/eastmoney.py +47 -47
- akshare_one/modules/news/factory.py +44 -44
- akshare_one/modules/realtime/base.py +27 -27
- akshare_one/modules/realtime/eastmoney.py +57 -57
- akshare_one/modules/realtime/eastmoney_direct.py +37 -37
- akshare_one/modules/realtime/factory.py +48 -48
- akshare_one/modules/realtime/xueqiu.py +60 -60
- akshare_one/modules/utils.py +10 -10
- {akshare_one-0.3.1.dist-info → akshare_one-0.3.3.dist-info}/METADATA +70 -70
- akshare_one-0.3.3.dist-info/RECORD +39 -0
- {akshare_one-0.3.1.dist-info → akshare_one-0.3.3.dist-info}/licenses/LICENSE +21 -21
- akshare_one/financial.py +0 -46
- akshare_one/insider.py +0 -33
- akshare_one/news.py +0 -27
- akshare_one/stock.py +0 -78
- akshare_one-0.3.1.dist-info/RECORD +0 -39
- {akshare_one-0.3.1.dist-info → akshare_one-0.3.3.dist-info}/WHEEL +0 -0
- {akshare_one-0.3.1.dist-info → akshare_one-0.3.3.dist-info}/top_level.txt +0 -0
@@ -1,28 +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
|
+
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,44 +1,44 @@
|
|
1
|
-
from .xueqiu import XueQiuInsider
|
2
|
-
from .base import InsiderDataProvider
|
3
|
-
|
4
|
-
|
5
|
-
class InsiderDataFactory:
|
6
|
-
"""
|
7
|
-
Factory class for creating insider data providers
|
8
|
-
"""
|
9
|
-
|
10
|
-
_providers = {
|
11
|
-
"xueqiu": XueQiuInsider,
|
12
|
-
}
|
13
|
-
|
14
|
-
@classmethod
|
15
|
-
def get_provider(cls, provider_name: str, **kwargs) -> InsiderDataProvider:
|
16
|
-
"""
|
17
|
-
Get an insider data provider by name
|
18
|
-
|
19
|
-
Args:
|
20
|
-
provider_name: Name of the provider (e.g., 'xueqiu')
|
21
|
-
**kwargs: Additional arguments to pass to the provider's constructor
|
22
|
-
|
23
|
-
Returns:
|
24
|
-
InsiderDataProvider: An instance of the requested provider
|
25
|
-
|
26
|
-
Raises:
|
27
|
-
ValueError: If the requested provider is not found
|
28
|
-
"""
|
29
|
-
provider_class = cls._providers.get(provider_name.lower())
|
30
|
-
if not provider_class:
|
31
|
-
raise ValueError(f"Unknown insider data provider: {provider_name}")
|
32
|
-
|
33
|
-
return provider_class(**kwargs)
|
34
|
-
|
35
|
-
@classmethod
|
36
|
-
def register_provider(cls, name: str, provider_class: type):
|
37
|
-
"""
|
38
|
-
Register a new insider data provider
|
39
|
-
|
40
|
-
Args:
|
41
|
-
name: Name to associate with this provider
|
42
|
-
provider_class: The provider class to register
|
43
|
-
"""
|
44
|
-
cls._providers[name.lower()] = provider_class
|
1
|
+
from .xueqiu import XueQiuInsider
|
2
|
+
from .base import InsiderDataProvider
|
3
|
+
|
4
|
+
|
5
|
+
class InsiderDataFactory:
|
6
|
+
"""
|
7
|
+
Factory class for creating insider data providers
|
8
|
+
"""
|
9
|
+
|
10
|
+
_providers = {
|
11
|
+
"xueqiu": XueQiuInsider,
|
12
|
+
}
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
def get_provider(cls, provider_name: str, **kwargs) -> InsiderDataProvider:
|
16
|
+
"""
|
17
|
+
Get an insider data provider by name
|
18
|
+
|
19
|
+
Args:
|
20
|
+
provider_name: Name of the provider (e.g., 'xueqiu')
|
21
|
+
**kwargs: Additional arguments to pass to the provider's constructor
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
InsiderDataProvider: An instance of the requested provider
|
25
|
+
|
26
|
+
Raises:
|
27
|
+
ValueError: If the requested provider is not found
|
28
|
+
"""
|
29
|
+
provider_class = cls._providers.get(provider_name.lower())
|
30
|
+
if not provider_class:
|
31
|
+
raise ValueError(f"Unknown insider data provider: {provider_name}")
|
32
|
+
|
33
|
+
return provider_class(**kwargs)
|
34
|
+
|
35
|
+
@classmethod
|
36
|
+
def register_provider(cls, name: str, provider_class: type):
|
37
|
+
"""
|
38
|
+
Register a new insider data provider
|
39
|
+
|
40
|
+
Args:
|
41
|
+
name: Name to associate with this provider
|
42
|
+
provider_class: The provider class to register
|
43
|
+
"""
|
44
|
+
cls._providers[name.lower()] = provider_class
|
@@ -1,115 +1,115 @@
|
|
1
|
-
from cachetools import cached
|
2
|
-
import pandas as pd
|
3
|
-
import akshare as ak
|
4
|
-
from .base import InsiderDataProvider
|
5
|
-
from ..utils import convert_xieqiu_symbol
|
6
|
-
from ..cache import CACHE_CONFIG
|
7
|
-
|
8
|
-
|
9
|
-
class XueQiuInsider(InsiderDataProvider):
|
10
|
-
"""Provider for XueQiu insider trading data"""
|
11
|
-
|
12
|
-
@cached(
|
13
|
-
cache=CACHE_CONFIG["financial_cache"],
|
14
|
-
key=lambda self, symbol=None: f"xueqiu_insider_{symbol if symbol else 'all'}",
|
15
|
-
)
|
16
|
-
def get_inner_trade_data(self) -> pd.DataFrame:
|
17
|
-
"""获取雪球内部交易数据
|
18
|
-
|
19
|
-
Args:
|
20
|
-
symbol: 可选股票代码,如"600000",不传则返回所有数据
|
21
|
-
|
22
|
-
Returns:
|
23
|
-
Standardized DataFrame with insider trading data:
|
24
|
-
- symbol: 股票代码
|
25
|
-
- name: 股票名称
|
26
|
-
- change_date: 变动日期
|
27
|
-
- insider: 变动人
|
28
|
-
- shares_changed: 变动股数
|
29
|
-
- avg_price: 成交均价
|
30
|
-
- shares_after: 变动后持股数
|
31
|
-
- relationship: 与董监高关系
|
32
|
-
- position: 董监高职务
|
33
|
-
"""
|
34
|
-
raw_df = ak.stock_inner_trade_xq()
|
35
|
-
if self.symbol:
|
36
|
-
xueqiu_symbol = convert_xieqiu_symbol(self.symbol)
|
37
|
-
raw_df = raw_df[raw_df["股票代码"] == xueqiu_symbol]
|
38
|
-
return self._clean_insider_data(raw_df)
|
39
|
-
|
40
|
-
def _clean_insider_data(self, raw_df: pd.DataFrame) -> pd.DataFrame:
|
41
|
-
"""清理和标准化内部交易数据
|
42
|
-
|
43
|
-
Args:
|
44
|
-
raw_df: Raw DataFrame from XueQiu API
|
45
|
-
|
46
|
-
Returns:
|
47
|
-
Standardized DataFrame with consistent columns
|
48
|
-
"""
|
49
|
-
column_mapping = {
|
50
|
-
"股票代码": "symbol",
|
51
|
-
"股票名称": "issuer",
|
52
|
-
"变动人": "name",
|
53
|
-
"董监高职务": "title",
|
54
|
-
"变动日期": "transaction_date",
|
55
|
-
"变动股数": "transaction_shares",
|
56
|
-
"成交均价": "transaction_price_per_share",
|
57
|
-
"变动后持股数": "shares_owned_after_transaction",
|
58
|
-
"与董监高关系": "relationship",
|
59
|
-
}
|
60
|
-
|
61
|
-
df = raw_df.rename(columns=column_mapping)
|
62
|
-
|
63
|
-
# Convert symbol back to original format (remove SH/SZ prefix)
|
64
|
-
if "symbol" in df.columns:
|
65
|
-
df["symbol"] = df["symbol"].str.replace(r"^[A-Z]{2}", "", regex=True)
|
66
|
-
|
67
|
-
# Add is_board_director column
|
68
|
-
df["is_board_director"] = df["title"].str.contains("董事")
|
69
|
-
|
70
|
-
# Calculate transaction_value
|
71
|
-
if (
|
72
|
-
"transaction_shares" in df.columns
|
73
|
-
and "transaction_price_per_share" in df.columns
|
74
|
-
):
|
75
|
-
df["transaction_value"] = (
|
76
|
-
df["transaction_shares"] * df["transaction_price_per_share"]
|
77
|
-
)
|
78
|
-
|
79
|
-
# Add shares_owned_before_transaction if possible
|
80
|
-
if (
|
81
|
-
"shares_owned_after_transaction" in df.columns
|
82
|
-
and "transaction_shares" in df.columns
|
83
|
-
):
|
84
|
-
df["shares_owned_before_transaction"] = (
|
85
|
-
df["shares_owned_after_transaction"] - df["transaction_shares"]
|
86
|
-
)
|
87
|
-
|
88
|
-
# Convert date format
|
89
|
-
if "transaction_date" in df.columns:
|
90
|
-
df["transaction_date"] = (
|
91
|
-
pd.to_datetime(df["transaction_date"])
|
92
|
-
.dt.tz_localize("Asia/Shanghai")
|
93
|
-
.dt.tz_convert("UTC")
|
94
|
-
)
|
95
|
-
|
96
|
-
if "filing_date" in df.columns:
|
97
|
-
df["filing_date"] = (
|
98
|
-
pd.to_datetime(df["filing_date"])
|
99
|
-
.dt.tz_localize("Asia/Shanghai")
|
100
|
-
.dt.tz_convert("UTC")
|
101
|
-
)
|
102
|
-
|
103
|
-
# Convert numeric columns
|
104
|
-
numeric_cols = [
|
105
|
-
"transaction_shares",
|
106
|
-
"transaction_price_per_share",
|
107
|
-
"transaction_value",
|
108
|
-
"shares_owned_before_transaction",
|
109
|
-
"shares_owned_after_transaction",
|
110
|
-
]
|
111
|
-
for col in numeric_cols:
|
112
|
-
if col in df.columns:
|
113
|
-
df[col] = pd.to_numeric(df[col], errors="coerce")
|
114
|
-
|
115
|
-
return df.reset_index(drop=True)
|
1
|
+
from cachetools import cached
|
2
|
+
import pandas as pd
|
3
|
+
import akshare as ak
|
4
|
+
from .base import InsiderDataProvider
|
5
|
+
from ..utils import convert_xieqiu_symbol
|
6
|
+
from ..cache import CACHE_CONFIG
|
7
|
+
|
8
|
+
|
9
|
+
class XueQiuInsider(InsiderDataProvider):
|
10
|
+
"""Provider for XueQiu insider trading data"""
|
11
|
+
|
12
|
+
@cached(
|
13
|
+
cache=CACHE_CONFIG["financial_cache"],
|
14
|
+
key=lambda self, symbol=None: f"xueqiu_insider_{symbol if symbol else 'all'}",
|
15
|
+
)
|
16
|
+
def get_inner_trade_data(self) -> pd.DataFrame:
|
17
|
+
"""获取雪球内部交易数据
|
18
|
+
|
19
|
+
Args:
|
20
|
+
symbol: 可选股票代码,如"600000",不传则返回所有数据
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
Standardized DataFrame with insider trading data:
|
24
|
+
- symbol: 股票代码
|
25
|
+
- name: 股票名称
|
26
|
+
- change_date: 变动日期
|
27
|
+
- insider: 变动人
|
28
|
+
- shares_changed: 变动股数
|
29
|
+
- avg_price: 成交均价
|
30
|
+
- shares_after: 变动后持股数
|
31
|
+
- relationship: 与董监高关系
|
32
|
+
- position: 董监高职务
|
33
|
+
"""
|
34
|
+
raw_df = ak.stock_inner_trade_xq()
|
35
|
+
if self.symbol:
|
36
|
+
xueqiu_symbol = convert_xieqiu_symbol(self.symbol)
|
37
|
+
raw_df = raw_df[raw_df["股票代码"] == xueqiu_symbol]
|
38
|
+
return self._clean_insider_data(raw_df)
|
39
|
+
|
40
|
+
def _clean_insider_data(self, raw_df: pd.DataFrame) -> pd.DataFrame:
|
41
|
+
"""清理和标准化内部交易数据
|
42
|
+
|
43
|
+
Args:
|
44
|
+
raw_df: Raw DataFrame from XueQiu API
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
Standardized DataFrame with consistent columns
|
48
|
+
"""
|
49
|
+
column_mapping = {
|
50
|
+
"股票代码": "symbol",
|
51
|
+
"股票名称": "issuer",
|
52
|
+
"变动人": "name",
|
53
|
+
"董监高职务": "title",
|
54
|
+
"变动日期": "transaction_date",
|
55
|
+
"变动股数": "transaction_shares",
|
56
|
+
"成交均价": "transaction_price_per_share",
|
57
|
+
"变动后持股数": "shares_owned_after_transaction",
|
58
|
+
"与董监高关系": "relationship",
|
59
|
+
}
|
60
|
+
|
61
|
+
df = raw_df.rename(columns=column_mapping)
|
62
|
+
|
63
|
+
# Convert symbol back to original format (remove SH/SZ prefix)
|
64
|
+
if "symbol" in df.columns:
|
65
|
+
df["symbol"] = df["symbol"].str.replace(r"^[A-Z]{2}", "", regex=True)
|
66
|
+
|
67
|
+
# Add is_board_director column
|
68
|
+
df["is_board_director"] = df["title"].str.contains("董事")
|
69
|
+
|
70
|
+
# Calculate transaction_value
|
71
|
+
if (
|
72
|
+
"transaction_shares" in df.columns
|
73
|
+
and "transaction_price_per_share" in df.columns
|
74
|
+
):
|
75
|
+
df["transaction_value"] = (
|
76
|
+
df["transaction_shares"] * df["transaction_price_per_share"]
|
77
|
+
)
|
78
|
+
|
79
|
+
# Add shares_owned_before_transaction if possible
|
80
|
+
if (
|
81
|
+
"shares_owned_after_transaction" in df.columns
|
82
|
+
and "transaction_shares" in df.columns
|
83
|
+
):
|
84
|
+
df["shares_owned_before_transaction"] = (
|
85
|
+
df["shares_owned_after_transaction"] - df["transaction_shares"]
|
86
|
+
)
|
87
|
+
|
88
|
+
# Convert date format
|
89
|
+
if "transaction_date" in df.columns:
|
90
|
+
df["transaction_date"] = (
|
91
|
+
pd.to_datetime(df["transaction_date"])
|
92
|
+
.dt.tz_localize("Asia/Shanghai")
|
93
|
+
.dt.tz_convert("UTC")
|
94
|
+
)
|
95
|
+
|
96
|
+
if "filing_date" in df.columns:
|
97
|
+
df["filing_date"] = (
|
98
|
+
pd.to_datetime(df["filing_date"])
|
99
|
+
.dt.tz_localize("Asia/Shanghai")
|
100
|
+
.dt.tz_convert("UTC")
|
101
|
+
)
|
102
|
+
|
103
|
+
# Convert numeric columns
|
104
|
+
numeric_cols = [
|
105
|
+
"transaction_shares",
|
106
|
+
"transaction_price_per_share",
|
107
|
+
"transaction_value",
|
108
|
+
"shares_owned_before_transaction",
|
109
|
+
"shares_owned_after_transaction",
|
110
|
+
]
|
111
|
+
for col in numeric_cols:
|
112
|
+
if col in df.columns:
|
113
|
+
df[col] = pd.to_numeric(df[col], errors="coerce")
|
114
|
+
|
115
|
+
return df.reset_index(drop=True)
|
akshare_one/modules/news/base.py
CHANGED
@@ -1,22 +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
|
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
|
@@ -1,47 +1,47 @@
|
|
1
|
-
from cachetools import cached
|
2
|
-
import pandas as pd
|
3
|
-
import akshare as ak
|
4
|
-
|
5
|
-
from ..cache import CACHE_CONFIG
|
6
|
-
from .base import NewsDataProvider
|
7
|
-
|
8
|
-
|
9
|
-
class EastMoneyNews(NewsDataProvider):
|
10
|
-
@cached(
|
11
|
-
CACHE_CONFIG["news_cache"],
|
12
|
-
key=lambda self: f"eastmoney_news_{self.symbol}",
|
13
|
-
)
|
14
|
-
def get_news_data(self) -> pd.DataFrame:
|
15
|
-
"""获取东方财富个股新闻数据"""
|
16
|
-
raw_df = ak.stock_news_em(symbol=self.symbol)
|
17
|
-
return self._clean_news_data(raw_df)
|
18
|
-
|
19
|
-
def _clean_news_data(self, raw_df: pd.DataFrame) -> pd.DataFrame:
|
20
|
-
"""清理和标准化新闻数据"""
|
21
|
-
column_mapping = {
|
22
|
-
"关键词": "keyword",
|
23
|
-
"新闻标题": "title",
|
24
|
-
"新闻内容": "content",
|
25
|
-
"发布时间": "publish_time",
|
26
|
-
"文章来源": "source",
|
27
|
-
"新闻链接": "url",
|
28
|
-
}
|
29
|
-
|
30
|
-
df = raw_df.rename(columns=column_mapping)
|
31
|
-
|
32
|
-
# Convert time to UTC
|
33
|
-
df["publish_time"] = (
|
34
|
-
pd.to_datetime(df["publish_time"])
|
35
|
-
.dt.tz_localize("Asia/Shanghai")
|
36
|
-
.dt.tz_convert("UTC")
|
37
|
-
)
|
38
|
-
|
39
|
-
required_columns = [
|
40
|
-
"keyword",
|
41
|
-
"title",
|
42
|
-
"content",
|
43
|
-
"publish_time",
|
44
|
-
"source",
|
45
|
-
"url",
|
46
|
-
]
|
47
|
-
return df[required_columns]
|
1
|
+
from cachetools import cached
|
2
|
+
import pandas as pd
|
3
|
+
import akshare as ak
|
4
|
+
|
5
|
+
from ..cache import CACHE_CONFIG
|
6
|
+
from .base import NewsDataProvider
|
7
|
+
|
8
|
+
|
9
|
+
class EastMoneyNews(NewsDataProvider):
|
10
|
+
@cached(
|
11
|
+
CACHE_CONFIG["news_cache"],
|
12
|
+
key=lambda self: f"eastmoney_news_{self.symbol}",
|
13
|
+
)
|
14
|
+
def get_news_data(self) -> pd.DataFrame:
|
15
|
+
"""获取东方财富个股新闻数据"""
|
16
|
+
raw_df = ak.stock_news_em(symbol=self.symbol)
|
17
|
+
return self._clean_news_data(raw_df)
|
18
|
+
|
19
|
+
def _clean_news_data(self, raw_df: pd.DataFrame) -> pd.DataFrame:
|
20
|
+
"""清理和标准化新闻数据"""
|
21
|
+
column_mapping = {
|
22
|
+
"关键词": "keyword",
|
23
|
+
"新闻标题": "title",
|
24
|
+
"新闻内容": "content",
|
25
|
+
"发布时间": "publish_time",
|
26
|
+
"文章来源": "source",
|
27
|
+
"新闻链接": "url",
|
28
|
+
}
|
29
|
+
|
30
|
+
df = raw_df.rename(columns=column_mapping)
|
31
|
+
|
32
|
+
# Convert time to UTC
|
33
|
+
df["publish_time"] = (
|
34
|
+
pd.to_datetime(df["publish_time"])
|
35
|
+
.dt.tz_localize("Asia/Shanghai")
|
36
|
+
.dt.tz_convert("UTC")
|
37
|
+
)
|
38
|
+
|
39
|
+
required_columns = [
|
40
|
+
"keyword",
|
41
|
+
"title",
|
42
|
+
"content",
|
43
|
+
"publish_time",
|
44
|
+
"source",
|
45
|
+
"url",
|
46
|
+
]
|
47
|
+
return df[required_columns]
|
@@ -1,44 +1,44 @@
|
|
1
|
-
from .eastmoney import EastMoneyNews
|
2
|
-
from .base import NewsDataProvider
|
3
|
-
|
4
|
-
|
5
|
-
class NewsDataFactory:
|
6
|
-
"""
|
7
|
-
Factory class for creating news data providers
|
8
|
-
"""
|
9
|
-
|
10
|
-
_providers = {
|
11
|
-
"eastmoney": EastMoneyNews,
|
12
|
-
}
|
13
|
-
|
14
|
-
@classmethod
|
15
|
-
def get_provider(cls, provider_name: str, **kwargs) -> NewsDataProvider:
|
16
|
-
"""
|
17
|
-
Get a news data provider by name
|
18
|
-
|
19
|
-
Args:
|
20
|
-
provider_name: Name of the provider (e.g., 'eastmoney')
|
21
|
-
**kwargs: Additional arguments to pass to the provider's constructor
|
22
|
-
|
23
|
-
Returns:
|
24
|
-
NewsDataProvider: An instance of the requested provider
|
25
|
-
|
26
|
-
Raises:
|
27
|
-
ValueError: If the requested provider is not found
|
28
|
-
"""
|
29
|
-
provider_class = cls._providers.get(provider_name.lower())
|
30
|
-
if not provider_class:
|
31
|
-
raise ValueError(f"Unknown news data provider: {provider_name}")
|
32
|
-
|
33
|
-
return provider_class(**kwargs)
|
34
|
-
|
35
|
-
@classmethod
|
36
|
-
def register_provider(cls, name: str, provider_class: type):
|
37
|
-
"""
|
38
|
-
Register a new news data provider
|
39
|
-
|
40
|
-
Args:
|
41
|
-
name: Name to associate with this provider
|
42
|
-
provider_class: The provider class to register
|
43
|
-
"""
|
44
|
-
cls._providers[name.lower()] = provider_class
|
1
|
+
from .eastmoney import EastMoneyNews
|
2
|
+
from .base import NewsDataProvider
|
3
|
+
|
4
|
+
|
5
|
+
class NewsDataFactory:
|
6
|
+
"""
|
7
|
+
Factory class for creating news data providers
|
8
|
+
"""
|
9
|
+
|
10
|
+
_providers = {
|
11
|
+
"eastmoney": EastMoneyNews,
|
12
|
+
}
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
def get_provider(cls, provider_name: str, **kwargs) -> NewsDataProvider:
|
16
|
+
"""
|
17
|
+
Get a news data provider by name
|
18
|
+
|
19
|
+
Args:
|
20
|
+
provider_name: Name of the provider (e.g., 'eastmoney')
|
21
|
+
**kwargs: Additional arguments to pass to the provider's constructor
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
NewsDataProvider: An instance of the requested provider
|
25
|
+
|
26
|
+
Raises:
|
27
|
+
ValueError: If the requested provider is not found
|
28
|
+
"""
|
29
|
+
provider_class = cls._providers.get(provider_name.lower())
|
30
|
+
if not provider_class:
|
31
|
+
raise ValueError(f"Unknown news data provider: {provider_name}")
|
32
|
+
|
33
|
+
return provider_class(**kwargs)
|
34
|
+
|
35
|
+
@classmethod
|
36
|
+
def register_provider(cls, name: str, provider_class: type):
|
37
|
+
"""
|
38
|
+
Register a new news data provider
|
39
|
+
|
40
|
+
Args:
|
41
|
+
name: Name to associate with this provider
|
42
|
+
provider_class: The provider class to register
|
43
|
+
"""
|
44
|
+
cls._providers[name.lower()] = provider_class
|
@@ -1,27 +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
|
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
|