Skip to content

Commit 3410688

Browse files
committed
support funds data
1 parent 9f66f7f commit 3410688

File tree

7 files changed

+832
-9
lines changed

7 files changed

+832
-9
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,19 +117,29 @@ from autoquant import Market
117117
Market.SZ
118118
Market.SH
119119
Market.HK
120+
Market.CN
120121
Market.US
121122
```
122123

123124
## Index
124125
AutoQuant support the indexes in multiple markets now.
126+
125127
Use StocksIndex Enum in codes:
126128
```
127129
from autoquant import StocksIndex
128130
129131
StocksIndex.ZZ500
130132
StocksIndex.HS300
131133
StocksIndex.SZ50
134+
```
135+
136+
Use FundsIndex Enum in codes:
132137

138+
```
139+
from autoquant import FundsIndex
140+
141+
FundsIndex.CN_ALL
142+
FundsIndex.CN_ETF
133143
```
134144

135145
## Metrics
@@ -167,6 +177,7 @@ output = MOM(close, timeperiod=5)
167177

168178
- BaostockProvider
169179
- TushareProvider
180+
- EastmoneyProvider
170181

171182
#### API
172183
```
@@ -198,12 +209,15 @@ def yearly_income_sheets(self, market: Market, code: str, years: list, **kwargs)
198209

199210
#### Provides List
200211
- BaostockProvider
212+
- EastmoneyProvider
201213

202214

203215
#### API
204216

205217
```
206218
def stocks_of_index(self, index: StocksIndex, **kwargs)
219+
220+
def funds_of_index(self, index: FundsIndex, **kwargs)
207221
```
208222

209223
# Contribution Guide

autoquant/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class Market(Enum):
77
SZ = auto()
88
HK = auto()
99
US = auto()
10+
CN = auto() # 代指中国所有境内市场,沪/深/北
1011

1112

1213
class PriceAdjustment(Enum):
@@ -22,3 +23,11 @@ class StocksIndex(Enum):
2223
ZZ500 = auto() # 中证500
2324
HS300 = auto() # 沪深300
2425
SZ50 = auto() # 上证50
26+
27+
28+
class FundsIndex(Enum):
29+
'''基金'''
30+
31+
# A股
32+
CN_ETF = auto() # A股ETF指数基金
33+
CN_ALL = auto() # A股所有基金

autoquant/collector/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from autoquant.provider import Provider
66
from autoquant.mixin.data import PriceMixin, StatementMixin, IndexMixin
7-
from autoquant import Market, StocksIndex
7+
from autoquant import Market, StocksIndex, FundsIndex
88

99
from autoquant.provider.baostock import BaostockProvider
1010
from autoquant.provider.snowball import SnowballProvider
@@ -63,3 +63,6 @@ def yearly_income_sheets(self, market: Market, code: str, years: list, **kwargs)
6363

6464
def stocks_of_index(self, index: StocksIndex, **kwargs):
6565
return self.__iter_providers(self.index_providers, self.stocks_of_index.__name__, index=index, **kwargs)
66+
67+
def funds_of_index(self, index: FundsIndex, **kwargs):
68+
return self.__iter_providers(self.index_providers, self.funds_of_index.__name__, index=index, **kwargs)

autoquant/mixin/data.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from datetime import MAXYEAR, date
2-
from abc import abstractmethod
2+
from abc import abstractmethod, ABCMeta
33

4-
from autoquant import Market, StocksIndex
4+
from autoquant import Market, StocksIndex, FundsIndex
55

66

77
class PriceMixin:
@@ -12,24 +12,28 @@ def daily_prices(self, market: Market, code: str, start: date, end: date, **kwar
1212
start: the start date
1313
end: then end date
1414
'''
15-
pass
15+
raise NotImplementedError
1616

1717

1818
class StatementMixin:
1919
@abstractmethod
2020
def quarter_statement(self, market: Market, code: str, quarter: date, **kwargs):
21-
pass
21+
raise NotImplementedError
2222

2323
@abstractmethod
2424
def yearly_balance_sheet(self, market: Market, code: str, years: list, **kwargs):
25-
pass
25+
raise NotImplementedError
2626

2727
@abstractmethod
2828
def yearly_income_sheets(self, market: Market, code: str, years: list, **kwargs):
29-
pass
29+
raise NotImplementedError
3030

3131

3232
class IndexMixin:
3333
@abstractmethod
3434
def stocks_of_index(self, index: StocksIndex, **kwargs):
35-
pass
35+
raise NotImplementedError
36+
37+
@abstractmethod
38+
def funds_of_index(self, index: FundsIndex, **kwargs):
39+
raise NotImplementedError

autoquant/provider/eastmoney.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import re
2+
import arrow
3+
import pandas as pd
4+
from datetime import date
5+
import requests
6+
from bs4 import BeautifulSoup
7+
8+
from . import Provider
9+
from autoquant.mixin.data import IndexMixin, PriceMixin
10+
from autoquant import Market, FundsIndex
11+
12+
13+
class EastmoneyProvider(PriceMixin, IndexMixin, Provider):
14+
_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36'
15+
_API_FUNDS_INDEX = "http://fund.eastmoney.com/js/fundcode_search.js"
16+
_API_DAILY_PRICES = "http://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&code={}&page={}&sdate={}&edate={}&per={}"
17+
18+
def daily_prices(self, market: Market, code: str, start: date, end: date, **kwargs):
19+
def __html(fund_code, start_date, end_date, page=1, per=20):
20+
url = self._API_DAILY_PRICES.format(fund_code, page, start_date, end_date, per)
21+
HTML = requests.get(url, headers={'User-Agent': self._UA})
22+
HTML.encoding = "utf-8"
23+
page_cnt = re.findall(r'pages:(.*),', HTML.text)[0]
24+
return HTML, int(page_cnt)
25+
26+
def __parse(HTML):
27+
soup = BeautifulSoup(HTML.text, 'html.parser')
28+
trs = soup.find_all("tr")
29+
res = []
30+
for tr in trs[1:]:
31+
date = tr.find_all("td")[0].text # 净值日期
32+
unit_net = tr.find_all("td")[1].text # 单位净值
33+
acc_net = tr.find_all("td")[2].text # 累计净值
34+
fund_r = tr.find_all("td")[3].text # 日增长率
35+
buy_status = tr.find_all("td")[4].text # 申购状态
36+
sell_status = tr.find_all("td")[5].text # 赎回状态
37+
res.append([date, unit_net, acc_net, fund_r, buy_status, sell_status])
38+
df = pd.DataFrame(res, columns=['净值日期', '单位净值', '累计净值', '日增长率', '申购状态', '赎回状态'])
39+
40+
return df
41+
42+
assert market == Market.CN, 'only Market.CN is supported in EastmoneyProvider::daily_prices'
43+
html, pages = __html(code, start, end)
44+
res_df = pd.DataFrame()
45+
for page in range(pages):
46+
html, _ = __html(code, start, end, page=page + 1)
47+
df_ = __parse(html)
48+
res_df = pd.concat([res_df, df_])
49+
50+
df = pd.DataFrame({
51+
'market': market,
52+
'code': code,
53+
'datetime': res_df['净值日期'].astype('datetime64[ns]'),
54+
'close': res_df['单位净值'].astype(float),
55+
'close_acc': res_df['累计净值'].astype(float),
56+
'pct_change': res_df['日增长率'].map(lambda x: x.strip('%')).astype(float),
57+
'status_purchase': res_df['申购状态'].map(lambda x: 'OPEN' if '开放' in x else 'CLOSE'),
58+
'status_redeem': res_df['赎回状态'].map(lambda x: 'OPEN' if '开放' in x else 'CLOSE')
59+
})
60+
df.index = df['datetime']
61+
return df
62+
63+
def funds_of_index(self, index: FundsIndex, **kwargs):
64+
'''
65+
get all funds via api: http://fund.eastmoney.com/js/fundcode_search.js
66+
'''
67+
res = requests.get(self._API_FUNDS_INDEX, headers={'User-Agent': self._UA})
68+
res.encoding = "utf-8"
69+
list_ = eval(re.findall(r'\[.*\]', res.text)[0])
70+
df = pd.DataFrame(list_)
71+
df.columns = ['code', 'logogram', 'name', 'type', 'name_spell']
72+
73+
all = pd.DataFrame({
74+
'updated_at': arrow.now().format('YYYY-MM-DD'),
75+
'market': Market.CN,
76+
'code': df['code'],
77+
'name': df['name'],
78+
})
79+
80+
return {
81+
FundsIndex.CN_ALL: lambda: all,
82+
FundsIndex.CN_ETF: lambda: all[all['name'].str.contains('ETF')]
83+
}[index]()

0 commit comments

Comments
 (0)