openbb-akshare 0.3.5__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.
- openbb_akshare/__init__.py +28 -0
- openbb_akshare/models/__init__.py +1 -0
- openbb_akshare/models/available_indices.py +72 -0
- openbb_akshare/models/company_news.py +100 -0
- openbb_akshare/models/equity_historical.py +118 -0
- openbb_akshare/models/equity_quote.py +153 -0
- openbb_akshare/models/historical_dividends.py +109 -0
- openbb_akshare/openbb.py +43 -0
- openbb_akshare/router.py +48 -0
- openbb_akshare/utils/__init__.py +1 -0
- openbb_akshare/utils/futures.csv +189 -0
- openbb_akshare/utils/helpers.py +206 -0
- openbb_akshare/utils/references.py +1387 -0
- openbb_akshare/utils/tools.py +142 -0
- openbb_akshare-0.3.5.dist-info/METADATA +148 -0
- openbb_akshare-0.3.5.dist-info/RECORD +18 -0
- openbb_akshare-0.3.5.dist-info/WHEEL +4 -0
- openbb_akshare-0.3.5.dist-info/entry_points.txt +6 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
"""openbb_akshare OpenBB Platform Provider."""
|
2
|
+
|
3
|
+
from openbb_core.provider.abstract.provider import Provider
|
4
|
+
from openbb_akshare.models.company_news import AKShareCompanyNewsFetcher
|
5
|
+
from openbb_akshare.models.equity_quote import AKShareEquityQuoteFetcher
|
6
|
+
from openbb_akshare.models.equity_historical import AKShareEquityHistoricalFetcher
|
7
|
+
from openbb_akshare.models.historical_dividends import AKShareHistoricalDividendsFetcher
|
8
|
+
from openbb_akshare.models.available_indices import AKShareAvailableIndicesFetcher
|
9
|
+
|
10
|
+
# mypy: disable-error-code="list-item"
|
11
|
+
|
12
|
+
provider = Provider(
|
13
|
+
name="akshare",
|
14
|
+
description="Data provider for openbb-akshare.",
|
15
|
+
# Only add 'credentials' if they are needed.
|
16
|
+
# For multiple login details, list them all here.
|
17
|
+
# credentials=["api_key"],
|
18
|
+
website="https://akshare.akfamily.xyz/",
|
19
|
+
# Here, we list out the fetchers showing what our provider can get.
|
20
|
+
# The dictionary key is the fetcher's name, used in the `router.py`.
|
21
|
+
fetcher_dict={
|
22
|
+
"AvailableIndices": AKShareAvailableIndicesFetcher,
|
23
|
+
"CompanyNews": AKShareCompanyNewsFetcher,
|
24
|
+
"EquityQuote": AKShareEquityQuoteFetcher,
|
25
|
+
"EquityHistorical": AKShareEquityHistoricalFetcher,
|
26
|
+
"HistoricalDividends": AKShareHistoricalDividendsFetcher,
|
27
|
+
}
|
28
|
+
)
|
@@ -0,0 +1 @@
|
|
1
|
+
"""AKShare models directory."""
|
@@ -0,0 +1,72 @@
|
|
1
|
+
"""AKShare Available Indices Model."""
|
2
|
+
|
3
|
+
# pylint: disable=unused-argument
|
4
|
+
|
5
|
+
from typing import Any, Dict, List, Optional
|
6
|
+
from datetime import (
|
7
|
+
date as dateType
|
8
|
+
)
|
9
|
+
|
10
|
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
11
|
+
from openbb_core.provider.standard_models.available_indices import (
|
12
|
+
AvailableIndicesData,
|
13
|
+
AvailableIndicesQueryParams,
|
14
|
+
)
|
15
|
+
from pydantic import Field
|
16
|
+
|
17
|
+
|
18
|
+
class AKShareAvailableIndicesQueryParams(AvailableIndicesQueryParams):
|
19
|
+
"""AKShare Available Indices Query.
|
20
|
+
|
21
|
+
Source: https://akshare.akfamily.xyz/data/index/index.html#id22
|
22
|
+
"""
|
23
|
+
|
24
|
+
|
25
|
+
class AKShareAvailableIndicesData(AvailableIndicesData):
|
26
|
+
"""AKShare Available Indices Data."""
|
27
|
+
|
28
|
+
__alias_dict__ = {
|
29
|
+
"name": "display_name",
|
30
|
+
"symbol": "index_code",
|
31
|
+
}
|
32
|
+
|
33
|
+
publish_date: Optional[dateType] = Field(
|
34
|
+
default=None,
|
35
|
+
description="The date of the index published.",
|
36
|
+
)
|
37
|
+
symbol: str = Field(description="Symbol for the index.")
|
38
|
+
|
39
|
+
|
40
|
+
class AKShareAvailableIndicesFetcher(
|
41
|
+
Fetcher[
|
42
|
+
AKShareAvailableIndicesQueryParams,
|
43
|
+
List[AKShareAvailableIndicesData],
|
44
|
+
]
|
45
|
+
):
|
46
|
+
"""Transform the query, extract and transform the data from the AKShare endpoints."""
|
47
|
+
|
48
|
+
@staticmethod
|
49
|
+
def transform_query(params: Dict[str, Any]) -> AKShareAvailableIndicesQueryParams:
|
50
|
+
"""Transform the query params."""
|
51
|
+
return AKShareAvailableIndicesQueryParams(**params)
|
52
|
+
|
53
|
+
@staticmethod
|
54
|
+
def extract_data(
|
55
|
+
query: AKShareAvailableIndicesQueryParams, # pylint disable=unused-argument
|
56
|
+
credentials: Optional[Dict[str, str]],
|
57
|
+
**kwargs: Any,
|
58
|
+
) -> List[Dict]:
|
59
|
+
"""Extract the data."""
|
60
|
+
import akshare as ak
|
61
|
+
|
62
|
+
index_stock_info_df = ak.index_stock_info()
|
63
|
+
index_stock_info_df["currency"] = "CNY"
|
64
|
+
|
65
|
+
return index_stock_info_df.to_dict(orient="records")
|
66
|
+
|
67
|
+
@staticmethod
|
68
|
+
def transform_data(
|
69
|
+
query: AKShareAvailableIndicesQueryParams, data: List[Dict], **kwargs: Any
|
70
|
+
) -> List[AKShareAvailableIndicesData]:
|
71
|
+
"""Return the transformed data."""
|
72
|
+
return [AKShareAvailableIndicesData.model_validate(d) for d in data]
|
@@ -0,0 +1,100 @@
|
|
1
|
+
"""AKShare Company News Model."""
|
2
|
+
|
3
|
+
# pylint: disable=unused-argument
|
4
|
+
from typing import Any, Dict, List, Optional
|
5
|
+
|
6
|
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
7
|
+
from openbb_core.provider.standard_models.company_news import (
|
8
|
+
CompanyNewsData,
|
9
|
+
CompanyNewsQueryParams,
|
10
|
+
)
|
11
|
+
from pydantic import Field, field_validator
|
12
|
+
|
13
|
+
class AKshareCompanyNewsQueryParams(CompanyNewsQueryParams):
|
14
|
+
"""AKShare Company News Query.
|
15
|
+
|
16
|
+
Source: https://so.eastmoney.com/news/s
|
17
|
+
"""
|
18
|
+
|
19
|
+
__json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}}
|
20
|
+
|
21
|
+
@field_validator("symbol", mode="before", check_fields=False)
|
22
|
+
@classmethod
|
23
|
+
def _symbol_mandatory(cls, v):
|
24
|
+
"""Symbol mandatory validator."""
|
25
|
+
if not v:
|
26
|
+
raise ValueError("Required field missing -> symbol")
|
27
|
+
return v
|
28
|
+
|
29
|
+
|
30
|
+
class AKShareCompanyNewsData(CompanyNewsData):
|
31
|
+
"""AKShare Company News Data."""
|
32
|
+
|
33
|
+
source: Optional[str] = Field(
|
34
|
+
default=None, description="Source of the news article"
|
35
|
+
)
|
36
|
+
|
37
|
+
|
38
|
+
class AKShareCompanyNewsFetcher(
|
39
|
+
Fetcher[
|
40
|
+
AKshareCompanyNewsQueryParams,
|
41
|
+
List[AKShareCompanyNewsData],
|
42
|
+
]
|
43
|
+
):
|
44
|
+
"""Transform the query, extract and transform the data from the AKShare endpoints."""
|
45
|
+
|
46
|
+
@staticmethod
|
47
|
+
def transform_query(params: Dict[str, Any]) -> AKshareCompanyNewsQueryParams:
|
48
|
+
"""Define example transform_query.
|
49
|
+
|
50
|
+
Here we can pre-process the query parameters and add any extra parameters that
|
51
|
+
will be used inside the extract_data method.
|
52
|
+
"""
|
53
|
+
return AKshareCompanyNewsQueryParams(**params)
|
54
|
+
|
55
|
+
@staticmethod
|
56
|
+
async def aextract_data(
|
57
|
+
query: AKshareCompanyNewsQueryParams,
|
58
|
+
credentials: Optional[Dict[str, str]],
|
59
|
+
**kwargs: Any,
|
60
|
+
) -> List[dict]:
|
61
|
+
"""Extract data."""
|
62
|
+
# pylint: disable=import-outside-toplevel
|
63
|
+
import asyncio # noqa
|
64
|
+
from openbb_core.provider.utils.errors import EmptyDataError
|
65
|
+
from openbb_core.provider.utils.helpers import get_requests_session
|
66
|
+
import akshare as ak
|
67
|
+
|
68
|
+
results: list = []
|
69
|
+
symbols = query.symbol.split(",") # type: ignore
|
70
|
+
async def get_one(symbol):
|
71
|
+
data = ak.stock_news_em(symbol)
|
72
|
+
for idx, d in data.iterrows():
|
73
|
+
new_content: dict = {}
|
74
|
+
new_content["text"] = d["新闻内容"]
|
75
|
+
new_content["url"] = d["新闻链接"]
|
76
|
+
new_content["source"] = d["文章来源"]
|
77
|
+
new_content["title"] = d["新闻标题"]
|
78
|
+
new_content["date"] = d["发布时间"]
|
79
|
+
|
80
|
+
results.append(new_content)
|
81
|
+
|
82
|
+
tasks = [get_one(symbol) for symbol in symbols]
|
83
|
+
|
84
|
+
await asyncio.gather(*tasks)
|
85
|
+
|
86
|
+
if not results:
|
87
|
+
raise EmptyDataError("No data was returned for the given symbol(s)")
|
88
|
+
|
89
|
+
return results
|
90
|
+
|
91
|
+
@staticmethod
|
92
|
+
def transform_data(
|
93
|
+
query: AKshareCompanyNewsQueryParams, data: List[dict], **kwargs: Any
|
94
|
+
) -> List[AKShareCompanyNewsData]:
|
95
|
+
"""Define example transform_data.
|
96
|
+
|
97
|
+
Right now, we're converting the data to fit our desired format.
|
98
|
+
You can apply other transformations to it here.
|
99
|
+
"""
|
100
|
+
return [AKShareCompanyNewsData(**d) for d in data]
|
@@ -0,0 +1,118 @@
|
|
1
|
+
"""AKShare Equity Historical Price Model."""
|
2
|
+
|
3
|
+
# pylint: disable=unused-argument
|
4
|
+
|
5
|
+
import asyncio
|
6
|
+
from datetime import datetime
|
7
|
+
from typing import Any, Dict, List, Literal, Optional
|
8
|
+
from warnings import warn
|
9
|
+
|
10
|
+
from dateutil.relativedelta import relativedelta
|
11
|
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
12
|
+
from openbb_core.provider.standard_models.equity_historical import (
|
13
|
+
EquityHistoricalData,
|
14
|
+
EquityHistoricalQueryParams,
|
15
|
+
)
|
16
|
+
from openbb_core.provider.utils.descriptions import (
|
17
|
+
DATA_DESCRIPTIONS,
|
18
|
+
QUERY_DESCRIPTIONS,
|
19
|
+
)
|
20
|
+
from openbb_core.provider.utils.errors import EmptyDataError
|
21
|
+
from openbb_core.provider.utils.helpers import (
|
22
|
+
amake_request,
|
23
|
+
get_querystring,
|
24
|
+
)
|
25
|
+
from openbb_fmp.utils.helpers import get_interval
|
26
|
+
from pydantic import Field
|
27
|
+
|
28
|
+
|
29
|
+
class AKShareEquityHistoricalQueryParams(EquityHistoricalQueryParams):
|
30
|
+
"""AKShare Equity Historical Price Query.
|
31
|
+
|
32
|
+
Source: https://financialmodelingprep.com/developer/docs/#Stock-Historical-Price
|
33
|
+
"""
|
34
|
+
|
35
|
+
__json_schema_extra__ = {
|
36
|
+
"symbol": {"multiple_items_allowed": True},
|
37
|
+
"period": {"choices": ["daily", "weekly", "monthly"]},
|
38
|
+
}
|
39
|
+
|
40
|
+
period: Literal["daily", "weekly", "monthly"] = Field(
|
41
|
+
default="daily", description=QUERY_DESCRIPTIONS.get("period", "")
|
42
|
+
)
|
43
|
+
|
44
|
+
|
45
|
+
class AKShareEquityHistoricalData(EquityHistoricalData):
|
46
|
+
"""AKShare Equity Historical Price Data."""
|
47
|
+
|
48
|
+
__alias_dict__ = {
|
49
|
+
"date": "日期",
|
50
|
+
"open": "开盘",
|
51
|
+
"close": "收盘",
|
52
|
+
"high": "最高",
|
53
|
+
"low": "最低",
|
54
|
+
"volume": "成交量",
|
55
|
+
"change": "涨跌额",
|
56
|
+
"change_percent": "涨跌幅",
|
57
|
+
}
|
58
|
+
|
59
|
+
change: Optional[float] = Field(
|
60
|
+
default=None,
|
61
|
+
description="Change in the price from the previous close.",
|
62
|
+
)
|
63
|
+
change_percent: Optional[float] = Field(
|
64
|
+
default=None,
|
65
|
+
description="Change in the price from the previous close, as a normalized percent.",
|
66
|
+
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
|
67
|
+
)
|
68
|
+
|
69
|
+
|
70
|
+
class AKShareEquityHistoricalFetcher(
|
71
|
+
Fetcher[
|
72
|
+
AKShareEquityHistoricalQueryParams,
|
73
|
+
List[AKShareEquityHistoricalData],
|
74
|
+
]
|
75
|
+
):
|
76
|
+
"""Transform the query, extract and transform the data from the AKShare endpoints."""
|
77
|
+
|
78
|
+
@staticmethod
|
79
|
+
def transform_query(params: Dict[str, Any]) -> AKShareEquityHistoricalQueryParams:
|
80
|
+
"""Transform the query params."""
|
81
|
+
transformed_params = params
|
82
|
+
|
83
|
+
now = datetime.now().date()
|
84
|
+
if params.get("start_date") is None:
|
85
|
+
transformed_params["start_date"] = now - relativedelta(years=1)
|
86
|
+
|
87
|
+
if params.get("end_date") is None:
|
88
|
+
transformed_params["end_date"] = now
|
89
|
+
|
90
|
+
return AKShareEquityHistoricalQueryParams(**transformed_params)
|
91
|
+
|
92
|
+
@staticmethod
|
93
|
+
def extract_data(
|
94
|
+
query: AKShareEquityHistoricalQueryParams,
|
95
|
+
credentials: Optional[Dict[str, str]],
|
96
|
+
**kwargs: Any,
|
97
|
+
) -> List[Dict]:
|
98
|
+
"""Return the raw data from the AKShare endpoint."""
|
99
|
+
from openbb_akshare.utils.helpers import ak_download
|
100
|
+
|
101
|
+
data = ak_download(symbol=query.symbol, start_date=query.start_date, end_date=query.end_date, period="daily", adjust="")
|
102
|
+
|
103
|
+
if data.empty:
|
104
|
+
raise EmptyDataError()
|
105
|
+
|
106
|
+
return data
|
107
|
+
|
108
|
+
|
109
|
+
@staticmethod
|
110
|
+
def transform_data(
|
111
|
+
query: AKShareEquityHistoricalQueryParams, data: List[Dict], **kwargs: Any
|
112
|
+
) -> List[AKShareEquityHistoricalData]:
|
113
|
+
"""Return the transformed data."""
|
114
|
+
|
115
|
+
return [
|
116
|
+
AKShareEquityHistoricalData.model_validate(d)
|
117
|
+
for d in data.to_dict("records")
|
118
|
+
]
|
@@ -0,0 +1,153 @@
|
|
1
|
+
"""AKShare Equity Quote Model."""
|
2
|
+
|
3
|
+
# pylint: disable=unused-argument
|
4
|
+
|
5
|
+
from typing import Any, Dict, List, Optional
|
6
|
+
from warnings import warn
|
7
|
+
|
8
|
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
9
|
+
from openbb_core.provider.standard_models.equity_quote import (
|
10
|
+
EquityQuoteData,
|
11
|
+
EquityQuoteQueryParams,
|
12
|
+
)
|
13
|
+
from pydantic import Field
|
14
|
+
import logging
|
15
|
+
from openbb_akshare.utils.tools import setup_logger, normalize_symbol, get_symbol_base
|
16
|
+
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
class AKShareEquityQuoteQueryParams(EquityQuoteQueryParams):
|
20
|
+
"""AKShare Equity Quote Query."""
|
21
|
+
|
22
|
+
__json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}}
|
23
|
+
|
24
|
+
|
25
|
+
class AKShareEquityQuoteData(EquityQuoteData):
|
26
|
+
"""AKShare Equity Quote Data."""
|
27
|
+
|
28
|
+
__alias_dict__ = {
|
29
|
+
"symbol": "代码",
|
30
|
+
"name": "名称",
|
31
|
+
"exchange": "交易所",
|
32
|
+
"last_price": "现价",
|
33
|
+
"open": "今开",
|
34
|
+
"close": "均价",
|
35
|
+
"high": "最高",
|
36
|
+
"low": "最低",
|
37
|
+
"prev_close": "昨收",
|
38
|
+
"year_high": "52周最高",
|
39
|
+
"year_low": "52周最低",
|
40
|
+
"volume": "成交量",
|
41
|
+
"change": "涨跌",
|
42
|
+
"participant_timestamp": "时间",
|
43
|
+
"market_cap": "流通值",
|
44
|
+
"pe_ratio": "市盈率(动)",
|
45
|
+
"pe_ratio_ttm": "市盈率(TTM)",
|
46
|
+
"turnover": "成交额",
|
47
|
+
"currency": "货币",
|
48
|
+
}
|
49
|
+
|
50
|
+
market_cap: Optional[float] = Field(
|
51
|
+
default=None,
|
52
|
+
description="Market capitalization.",
|
53
|
+
)
|
54
|
+
pe_ratio: Optional[float] = Field(
|
55
|
+
default=None,
|
56
|
+
description="Price-to-earnings ratio.",
|
57
|
+
)
|
58
|
+
pe_ratio_ttm: Optional[float] = Field(
|
59
|
+
default=None,
|
60
|
+
description="Price-to-earnings ratio (TTM).",
|
61
|
+
)
|
62
|
+
turnover: Optional[float] = Field(
|
63
|
+
default=None,
|
64
|
+
description="Turnover of the stock.",
|
65
|
+
)
|
66
|
+
currency: Optional[str] = Field(
|
67
|
+
default=None,
|
68
|
+
description="Currency of the price.",
|
69
|
+
)
|
70
|
+
|
71
|
+
|
72
|
+
class AKShareEquityQuoteFetcher(
|
73
|
+
Fetcher[AKShareEquityQuoteQueryParams, List[AKShareEquityQuoteData]]
|
74
|
+
):
|
75
|
+
"""AKShare Equity Quote Fetcher."""
|
76
|
+
|
77
|
+
@staticmethod
|
78
|
+
def transform_query(params: Dict[str, Any]) -> AKShareEquityQuoteQueryParams:
|
79
|
+
"""Transform the query."""
|
80
|
+
return AKShareEquityQuoteQueryParams(**params)
|
81
|
+
|
82
|
+
@staticmethod
|
83
|
+
async def aextract_data(
|
84
|
+
query: AKShareEquityQuoteQueryParams,
|
85
|
+
credentials: Optional[Dict[str, str]],
|
86
|
+
**kwargs: Any,
|
87
|
+
) -> List[Dict]:
|
88
|
+
"""Extract the raw data from AKShare."""
|
89
|
+
# pylint: disable=import-outside-toplevel
|
90
|
+
import asyncio # noqa
|
91
|
+
from curl_adapter import CurlCffiAdapter
|
92
|
+
from openbb_core.provider.utils.helpers import get_requests_session
|
93
|
+
import akshare as ak
|
94
|
+
|
95
|
+
symbols = query.symbol.split(",")
|
96
|
+
results = []
|
97
|
+
fields = ['代码',
|
98
|
+
'名称',
|
99
|
+
'交易所',
|
100
|
+
'现价',
|
101
|
+
'今开',
|
102
|
+
'均价',
|
103
|
+
'最高',
|
104
|
+
'最低',
|
105
|
+
'昨收',
|
106
|
+
'52周最高',
|
107
|
+
'52周最低',
|
108
|
+
'成交量',
|
109
|
+
'涨跌',
|
110
|
+
'时间',
|
111
|
+
'流通值',
|
112
|
+
'市盈率(动)',
|
113
|
+
'市盈率(TTM)',
|
114
|
+
'成交额',
|
115
|
+
'货币']
|
116
|
+
async def get_one(symbol):
|
117
|
+
"""Get the data for one ticker symbol."""
|
118
|
+
result: dict = {}
|
119
|
+
ticker: dict = {}
|
120
|
+
try:
|
121
|
+
symbol_b, symbol_f, market = normalize_symbol(symbol)
|
122
|
+
if market == "SH" or market == "SZ":
|
123
|
+
symbol = market+symbol_b
|
124
|
+
elif market == "BJ":
|
125
|
+
symbol = "NQ"+symbol_b
|
126
|
+
ticker_df = ak.stock_individual_spot_xq(symbol)
|
127
|
+
if ticker_df.empty:
|
128
|
+
logger.debug("ticker_df is empty.")
|
129
|
+
else:
|
130
|
+
ticker = dict(zip(ticker_df['item'], ticker_df['value']))
|
131
|
+
except Exception as e:
|
132
|
+
warn(f"Error getting data for {symbol}: {e}")
|
133
|
+
if ticker:
|
134
|
+
for field in fields:
|
135
|
+
if field in ticker:
|
136
|
+
result[field] = ticker.get(field, None)
|
137
|
+
if result:
|
138
|
+
results.append(result)
|
139
|
+
|
140
|
+
tasks = [get_one(symbol) for symbol in symbols]
|
141
|
+
|
142
|
+
await asyncio.gather(*tasks)
|
143
|
+
|
144
|
+
return results
|
145
|
+
|
146
|
+
@staticmethod
|
147
|
+
def transform_data(
|
148
|
+
query: AKShareEquityQuoteQueryParams,
|
149
|
+
data: List[Dict],
|
150
|
+
**kwargs: Any,
|
151
|
+
) -> List[AKShareEquityQuoteData]:
|
152
|
+
"""Transform the data."""
|
153
|
+
return [AKShareEquityQuoteData.model_validate(d) for d in data]
|
@@ -0,0 +1,109 @@
|
|
1
|
+
"""AKShare Historical Dividends Model."""
|
2
|
+
|
3
|
+
# pylint: disable=unused-argument
|
4
|
+
from datetime import (
|
5
|
+
date as dateType,
|
6
|
+
datetime,
|
7
|
+
)
|
8
|
+
from typing import Any, Dict, List, Optional
|
9
|
+
from pydantic import Field, field_validator
|
10
|
+
|
11
|
+
from openbb_core.app.model.abstract.error import OpenBBError
|
12
|
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
13
|
+
from openbb_core.provider.standard_models.historical_dividends import (
|
14
|
+
HistoricalDividendsData,
|
15
|
+
HistoricalDividendsQueryParams,
|
16
|
+
)
|
17
|
+
|
18
|
+
import logging
|
19
|
+
logger = logging.getLogger(__name__)
|
20
|
+
|
21
|
+
class AKShareHistoricalDividendsQueryParams(HistoricalDividendsQueryParams):
|
22
|
+
"""AKShare Historical Dividends Query."""
|
23
|
+
|
24
|
+
|
25
|
+
class AKShareHistoricalDividendsData(HistoricalDividendsData):
|
26
|
+
"""AKShare Historical Dividends Data. All data is split-adjusted."""
|
27
|
+
reported_date: Optional[dateType] = Field(
|
28
|
+
default=None,
|
29
|
+
description="Earnings Announcement Date.",
|
30
|
+
)
|
31
|
+
description: Optional[str] = Field(
|
32
|
+
default=None,
|
33
|
+
description="Record date of the historical dividends.",
|
34
|
+
)
|
35
|
+
record_date: Optional[dateType] = Field(
|
36
|
+
default=None,
|
37
|
+
description="Record date of the historical dividends.",
|
38
|
+
)
|
39
|
+
declaration_date: Optional[dateType] = Field(
|
40
|
+
default=None,
|
41
|
+
description="Declaration date of the historical dividends.",
|
42
|
+
)
|
43
|
+
@field_validator(
|
44
|
+
"declaration_date",
|
45
|
+
"record_date",
|
46
|
+
"reported_date",
|
47
|
+
"ex_dividend_date",
|
48
|
+
mode="before",
|
49
|
+
check_fields=False,
|
50
|
+
)
|
51
|
+
@classmethod
|
52
|
+
def date_validate(cls, v: str): # pylint: disable=E0213
|
53
|
+
"""Validate dates."""
|
54
|
+
if not isinstance(v, str):
|
55
|
+
return v
|
56
|
+
return dateType.fromisoformat(v) if v else None
|
57
|
+
|
58
|
+
class AKShareHistoricalDividendsFetcher(
|
59
|
+
Fetcher[
|
60
|
+
AKShareHistoricalDividendsQueryParams, List[AKShareHistoricalDividendsData]
|
61
|
+
]
|
62
|
+
):
|
63
|
+
"""AKShare Historical Dividends Fetcher."""
|
64
|
+
|
65
|
+
@staticmethod
|
66
|
+
def transform_query(
|
67
|
+
params: Dict[str, Any],
|
68
|
+
) -> AKShareHistoricalDividendsQueryParams:
|
69
|
+
"""Transform the query."""
|
70
|
+
return AKShareHistoricalDividendsQueryParams(**params)
|
71
|
+
|
72
|
+
@staticmethod
|
73
|
+
def extract_data(
|
74
|
+
query: AKShareHistoricalDividendsQueryParams,
|
75
|
+
credentials: Optional[Dict[str, str]],
|
76
|
+
**kwargs: Any,
|
77
|
+
) -> List[Dict]:
|
78
|
+
"""Extract the raw data from AKShare."""
|
79
|
+
# pylint: disable=import-outside-toplevel
|
80
|
+
from openbb_akshare.utils.tools import normalize_symbol
|
81
|
+
from openbb_akshare.utils.helpers import get_a_dividends, get_hk_dividends
|
82
|
+
|
83
|
+
symbol_b, symbol_f, market = normalize_symbol(query.symbol)
|
84
|
+
if market == "HK":
|
85
|
+
dividends = get_hk_dividends(symbol_b)
|
86
|
+
else:
|
87
|
+
dividends = get_a_dividends(symbol_b)
|
88
|
+
|
89
|
+
logger.info(
|
90
|
+
"Fetched historical dividends:\n%s",
|
91
|
+
dividends
|
92
|
+
)
|
93
|
+
return dividends
|
94
|
+
|
95
|
+
@staticmethod
|
96
|
+
def transform_data(
|
97
|
+
query: AKShareHistoricalDividendsQueryParams,
|
98
|
+
data: List[Dict],
|
99
|
+
**kwargs: Any,
|
100
|
+
) -> List[AKShareHistoricalDividendsData]:
|
101
|
+
"""Transform the data."""
|
102
|
+
#return [AKShareHistoricalDividendsData.model_validate(d) for d in data]
|
103
|
+
result: List[AKShareHistoricalDividendsData] = []
|
104
|
+
for d in data:
|
105
|
+
result.append(AKShareHistoricalDividendsData(**d))
|
106
|
+
logger.info(
|
107
|
+
"Transformed historical dividends completed.\n"
|
108
|
+
)
|
109
|
+
return result
|
openbb_akshare/openbb.py
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
"""openbb_akshare OpenBB Platform extension."""
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import List, Optional, Union
|
4
|
+
|
5
|
+
from openbb_core.app.static.app_factory import BaseApp as _BaseApp
|
6
|
+
from openbb_core.app.static.app_factory import create_app as _create_app
|
7
|
+
from openbb_core.app.static.package_builder import \
|
8
|
+
PackageBuilder as _PackageBuilder
|
9
|
+
|
10
|
+
_this_dir = Path(__file__).parent.resolve()
|
11
|
+
_PackageBuilder(_this_dir).auto_build()
|
12
|
+
|
13
|
+
|
14
|
+
def build(
|
15
|
+
modules: Optional[Union[str, List[str]]] = None,
|
16
|
+
lint: bool = True,
|
17
|
+
verbose: bool = False,
|
18
|
+
) -> None:
|
19
|
+
"""Build extension modules.
|
20
|
+
|
21
|
+
Parameters
|
22
|
+
----------
|
23
|
+
modules : Optional[List[str]], optional
|
24
|
+
The modules to rebuild, by default None
|
25
|
+
For example: "/news" or ["/news", "/crypto"]
|
26
|
+
If None, all modules are rebuilt.
|
27
|
+
lint : bool, optional
|
28
|
+
Whether to lint the code, by default True
|
29
|
+
verbose : bool, optional
|
30
|
+
Enable/disable verbose mode
|
31
|
+
"""
|
32
|
+
_PackageBuilder(_this_dir, lint, verbose).build(modules)
|
33
|
+
|
34
|
+
|
35
|
+
try:
|
36
|
+
# pylint: disable=import-outside-toplevel
|
37
|
+
from openbb_akshare.package.__extensions__ import Extensions as _Extensions
|
38
|
+
|
39
|
+
obb: Union[_BaseApp, _Extensions] = _create_app(_Extensions) # type: ignore
|
40
|
+
sdk = obb
|
41
|
+
except (ImportError, ModuleNotFoundError):
|
42
|
+
print("Failed to import extensions.")
|
43
|
+
obb = sdk = _create_app() # type: ignore
|
openbb_akshare/router.py
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
"""openbb-akshare router command example."""
|
2
|
+
|
3
|
+
import requests
|
4
|
+
from openbb_core.app.model.command_context import CommandContext
|
5
|
+
from openbb_core.app.model.obbject import OBBject
|
6
|
+
from openbb_core.app.provider_interface import (ExtraParams, ProviderChoices,
|
7
|
+
StandardParams)
|
8
|
+
from openbb_core.app.query import Query
|
9
|
+
from openbb_core.app.router import Router
|
10
|
+
from pydantic import BaseModel
|
11
|
+
|
12
|
+
router = Router(prefix="")
|
13
|
+
|
14
|
+
|
15
|
+
@router.command(methods=["GET"])
|
16
|
+
async def get_example(symbol: str = "AAPL") -> OBBject[dict]:
|
17
|
+
"""Get options data."""
|
18
|
+
base_url = "https://www.cboe.com/education/tools/trade-optimizer/symbol-info"
|
19
|
+
|
20
|
+
response = requests.get(base_url + f"?symbol={symbol}", timeout=5).json()
|
21
|
+
return OBBject(results=response["details"])
|
22
|
+
|
23
|
+
|
24
|
+
@router.command(methods=["POST"])
|
25
|
+
async def post_example(
|
26
|
+
data: dict,
|
27
|
+
bid_col: str = "bid",
|
28
|
+
ask_col: str = "ask",
|
29
|
+
) -> OBBject[dict]:
|
30
|
+
"""Calculate mid and spread."""
|
31
|
+
bid = data[bid_col]
|
32
|
+
ask = data[ask_col]
|
33
|
+
mid = (bid + ask) / 2
|
34
|
+
spread = ask - bid
|
35
|
+
|
36
|
+
return OBBject(results={"mid": mid, "spread": spread})
|
37
|
+
|
38
|
+
|
39
|
+
# pylint: disable=unused-argument
|
40
|
+
@router.command(model="Example")
|
41
|
+
async def model_example(
|
42
|
+
cc: CommandContext,
|
43
|
+
provider_choices: ProviderChoices,
|
44
|
+
standard_params: StandardParams,
|
45
|
+
extra_params: ExtraParams,
|
46
|
+
) -> OBBject[BaseModel]:
|
47
|
+
"""Example Data."""
|
48
|
+
return await OBBject.from_query(Query(**locals()))
|
@@ -0,0 +1 @@
|
|
1
|
+
"""AKShare utils directory."""
|