rquote 0.2.4__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.
- rquote/__init__.py +14 -0
- rquote/main.py +423 -0
- rquote/plots.py +71 -0
- rquote/utils.py +197 -0
- rquote-0.2.4.dist-info/METADATA +39 -0
- rquote-0.2.4.dist-info/RECORD +8 -0
- rquote-0.2.4.dist-info/WHEEL +5 -0
- rquote-0.2.4.dist-info/top_level.txt +1 -0
rquote/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'''
|
|
2
|
+
rquote
|
|
3
|
+
|
|
4
|
+
A stock history data api and related tools
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2021 Roi ZHAO
|
|
7
|
+
|
|
8
|
+
'''
|
|
9
|
+
|
|
10
|
+
from .main import get_price, get_stock_concepts, get_concept_stocks, get_bk_stocks
|
|
11
|
+
from .main import get_all_concepts, get_all_industries
|
|
12
|
+
from .main import get_cn_stock_list, get_hk_stocks_hsi, get_hk_stocks_ggt
|
|
13
|
+
from .utils import WebUtils, BasicFactors
|
|
14
|
+
from .plots import PlotUtils
|
rquote/main.py
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
import re
|
|
6
|
+
import base64
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from .utils import WebUtils, hget, logger
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def make_tgts(mkts=['ch', 'hk', 'us', 'fund', 'future'], money_min=2e8) -> []:
|
|
12
|
+
cands = []
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_cn_stock_list(money_min=2e8):
|
|
16
|
+
'''
|
|
17
|
+
Return sorted stock list ordered by latest amount of money, cut at `money_min`
|
|
18
|
+
item in returned list are [code, name, change, amount, mktcap]
|
|
19
|
+
'''
|
|
20
|
+
a = hget(
|
|
21
|
+
base64.b64decode('aHR0cDovLzM4LnB1c2gyLmVhc3Rtb25leS5jb20vYXBpL3F0L2Ns'+
|
|
22
|
+
'aXN0L2dldD9jYj1qUXVlcnkxMTI0MDk0NTg3NjE4NDQzNzQ4MDFfMTYyNzI4ODQ4O'+
|
|
23
|
+
'Tk2MSZwbj0xJnB6PTEwMDAwJnBvPTEmbnA9MSZ1dD1iZDFkOWRkYjA0MDg5NzAwY2'+
|
|
24
|
+
'Y5YzI3ZjZmNzQyNjI4MSZmbHR0PTImaW52dD0yJmZpZD1mNiZmcz1tOjArdDo2LG0'+
|
|
25
|
+
'6MCt0OjgwLG06MSt0OjIsbToxK3Q6MjMmZmllbGRzPWYxMixmMTQsZjMsZjYsZjIxJl89'
|
|
26
|
+
).decode() + str(int(time.time()*1e3))
|
|
27
|
+
)
|
|
28
|
+
if a:
|
|
29
|
+
a = json.loads(a.text.split(
|
|
30
|
+
'jQuery112409458761844374801_1627288489961(')[1][:-2])
|
|
31
|
+
|
|
32
|
+
# cdir = os.path.dirname(__file__)
|
|
33
|
+
# with open(os.path.join(cdir, 'ranka'), 'wb') as f:
|
|
34
|
+
# f.write(a.content)
|
|
35
|
+
# a = pd.read_excel(os.path.join(cdir, 'ranka'), header=1)
|
|
36
|
+
# os.remove(os.path.join(cdir, 'ranka'))
|
|
37
|
+
# a.columns = ['code', 'name', 'close', 'p_change', 'change', '_', '_', '_',
|
|
38
|
+
# 'money', 'open', 'yest_close', 'high', 'low']
|
|
39
|
+
# a = a[a.money > money_min]
|
|
40
|
+
a = [ ['sh'+i['f12'] if i['f12'][0]=='6' else 'sz'+i['f12'],
|
|
41
|
+
i['f14'], i['f3'], i['f6'], i['f21']] for i in a['data']['diff']
|
|
42
|
+
if i['f6']!='-' and float(i['f6']) > money_min]
|
|
43
|
+
#cands=[(i.code,i.name) for i in a[['code','name']].itertuples()]
|
|
44
|
+
return a
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# def get_hk_stocks_hotest80():
|
|
48
|
+
# a = hget(
|
|
49
|
+
# 'http://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php' +
|
|
50
|
+
# '/Market_Center.getHKStockData?page=1&num=80&sort=amount&asc=0&node=' +
|
|
51
|
+
# 'qbgg_hk&_s_r_a=sort').text
|
|
52
|
+
# if a:
|
|
53
|
+
# a = [['hk'+i['symbol'], i['name'], i['changepercent'], i['amount'],
|
|
54
|
+
# i['market_value']] for i in json.loads(a)]
|
|
55
|
+
# return a
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_us_stocks_biggest(k=60):
|
|
59
|
+
# return list of [symbol, name, price, volume, mktcap, pe]
|
|
60
|
+
uscands = []
|
|
61
|
+
a = hget(
|
|
62
|
+
"https://stock.finance.sina.com.cn/usstock/api/jsonp.php/IO.XSRV2."+
|
|
63
|
+
"CallbackList['f0j3ltzVzdo2Fo4p']/US_CategoryService.getList?page=1"+
|
|
64
|
+
"&num=60&sort=&asc=0&market=&id=", headers=WebUtils.headers()).text
|
|
65
|
+
if a:
|
|
66
|
+
uslist = json.loads(a.split('(',1)[1][:-2])['data']
|
|
67
|
+
# Warning: symbol not fitted
|
|
68
|
+
uscands = [('us' + i['symbol'], i['name'], i['price'], i['volume'],
|
|
69
|
+
i['mktcap']) for i in uslist]
|
|
70
|
+
return uscands
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_cn_fund_list():
|
|
74
|
+
'''
|
|
75
|
+
Return sorted etf list (ordered by latest amount of money),
|
|
76
|
+
of [code, name, change, amount, price]
|
|
77
|
+
'''
|
|
78
|
+
a = hget(base64.b64decode('aHR0cDovL3ZpcC5zdG9jay5maW5hbmNlLnNpbmEuY29tL'+
|
|
79
|
+
'mNuL3F1b3Rlc19zZXJ2aWNlL2FwaS9qc29ucC5waHAvSU8uWFNSVjIuQ2FsbGJhY2tMaX'+
|
|
80
|
+
'N0WydrMldhekswNk5Rd2xoeVh2J10vTWFya2V0X0NlbnRlci5nZXRIUU5vZGVEYXRhU2l'+
|
|
81
|
+
'tcGxlP3BhZ2U9MSZudW09MTAwMCZzb3J0PWFtb3VudCZhc2M9MCZub2RlPWV0Zl9ocV9m'+
|
|
82
|
+
'dW5kJiU1Qm9iamVjdCUyMEhUTUxEaXZFbGVtZW50JTVEPXhtNGkw').decode()).text
|
|
83
|
+
if a:
|
|
84
|
+
fundcands = [[i['symbol'], i['name'], i['changepercent'], i['amount'], i['trade']]
|
|
85
|
+
for i in json.loads(a.split('k2WazK06NQwlhyXv')[1][3:-2])]
|
|
86
|
+
return fundcands
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_cn_future_list():
|
|
90
|
+
'''
|
|
91
|
+
Return cn future id list, with prefix of `fu`
|
|
92
|
+
e.g. ['fuSC2109',
|
|
93
|
+
'fuRB2110',
|
|
94
|
+
'fuHC2110',
|
|
95
|
+
'fuFU2109',
|
|
96
|
+
...]
|
|
97
|
+
'''
|
|
98
|
+
a = hget('https://finance.sina.com.cn/futuremarket/').text
|
|
99
|
+
if a:
|
|
100
|
+
futurelist_active = [
|
|
101
|
+
'fu' + i for i in re.findall(r'quotes/(.*?\d+).shtml', a)]
|
|
102
|
+
return futurelist_active
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_price(i, sdate='', edate='', freq='day', days=320, fq='qfq',
|
|
106
|
+
dd=None) -> (str, str, pd.DataFrame):
|
|
107
|
+
'''
|
|
108
|
+
Args:
|
|
109
|
+
sdate: start date
|
|
110
|
+
edate: end date
|
|
111
|
+
dd: data dictionary, any local cache with get/put methods
|
|
112
|
+
days: day length of fetching, overwriting sdate
|
|
113
|
+
fq: qfq for non
|
|
114
|
+
'''
|
|
115
|
+
if dd is not None:
|
|
116
|
+
a = dd.get(i)
|
|
117
|
+
if a:
|
|
118
|
+
n, d = a
|
|
119
|
+
logger.debug('loading price from dd {}'.format(i))
|
|
120
|
+
return i, n, d
|
|
121
|
+
logger.debug('fetching price of {}'.format(i))
|
|
122
|
+
qtimg_stock = 'http://web.ifzq.gtimg.cn/appstock/app/newfqkline/get?param=' + \
|
|
123
|
+
'{},{},{},{},{},{}'
|
|
124
|
+
qtimg_stock_hk = 'http://web.ifzq.gtimg.cn/appstock/app/hkfqkline/get?' + \
|
|
125
|
+
'param={},{},{},{},{},{}'
|
|
126
|
+
qtimg_stock_us = 'http://web.ifzq.gtimg.cn/appstock/app/usfqkline/get?' + \
|
|
127
|
+
'param={},{},{},{},{},{}'
|
|
128
|
+
sina_future_d = 'https://stock2.finance.sina.com.cn/futures/api/jsonp.php/' + \
|
|
129
|
+
'var%20t1nf_{}=/InnerFuturesNewService.getDailyKLine?symbol={}'
|
|
130
|
+
sina_btc = 'https://quotes.sina.cn/fx/api/openapi.php/BtcService.getDayKLine?' + \
|
|
131
|
+
'symbol=btcbtcusd'
|
|
132
|
+
|
|
133
|
+
# sina_future_d.format('FB0','FB0')
|
|
134
|
+
|
|
135
|
+
if i[:2] == 'BK':
|
|
136
|
+
try:
|
|
137
|
+
a = hget(base64.b64decode('aHR0cDovL3B1c2gyaGlzLmVhc3' +
|
|
138
|
+
'Rtb25leS5jb20vYXBpL3F0L3N0b2NrL2tsaW5lL2dldD9jYj1qUX' +
|
|
139
|
+
'VlcnkxMTI0MDIyNTY2NDQ1ODczNzY2OTcyXzE2MTc4NjQ1NjgxMz' +
|
|
140
|
+
'Emc2VjaWQ9OTAu').decode() + i +
|
|
141
|
+
'&fields1=f1%2Cf2%2Cf3%2Cf4%2Cf5' +
|
|
142
|
+
'&fields2=f51%2Cf52%2Cf53%2Cf54%2Cf55%2Cf56%2Cf57%2Cf58' +
|
|
143
|
+
'&klt=101&fqt=0&beg=19900101&end=20990101&_=1',
|
|
144
|
+
headers=WebUtils.headers())
|
|
145
|
+
if not a:
|
|
146
|
+
logger.warning('{} hget failed: {}'.format(i, a))
|
|
147
|
+
return i, 'None', pd.DataFrame([])
|
|
148
|
+
a = json.loads(a.text.split(
|
|
149
|
+
'jQuery1124022566445873766972_1617864568131(')[1][:-2])
|
|
150
|
+
if not a['data']:
|
|
151
|
+
logger.warning('{} data empty: {}'.format(i, a))
|
|
152
|
+
return i, 'None', pd.DataFrame([])
|
|
153
|
+
name = a['data']['name']
|
|
154
|
+
d = pd.DataFrame([i.split(',') for i in a['data']['klines']], columns=[
|
|
155
|
+
'date', 'open', 'close', 'high', 'low', 'vol', 'money', 'p'])
|
|
156
|
+
d = d.set_index(['date']).astype(float)
|
|
157
|
+
# d.index = pd.DatetimeIndex(d.index)
|
|
158
|
+
return i, name, d
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.warning('error fetching {}, err: {}'.format(i, e))
|
|
161
|
+
return i, 'None', pd.DataFrame([])
|
|
162
|
+
|
|
163
|
+
if i[:2] == 'fu':
|
|
164
|
+
try:
|
|
165
|
+
ix = i[2:] if i[-1]=='0' else i[2:-4]
|
|
166
|
+
if ix == 'btc' or ix == 'BTC':
|
|
167
|
+
url = sina_btc
|
|
168
|
+
d = json.loads(hget(url).text)['result']['data'].split('|')
|
|
169
|
+
d = pd.DataFrame([i.split(',') for i in d],
|
|
170
|
+
columns=['date', 'open', 'high', 'low', 'close', 'vol', 'amount'])
|
|
171
|
+
return i, 'BTC', d
|
|
172
|
+
else:
|
|
173
|
+
d = pd.DataFrame(json.loads(hget(sina_future_d.format(
|
|
174
|
+
ix, ix)).text.split('(')[1][:-2]))
|
|
175
|
+
d.columns = ['date', 'open', 'high', 'low', 'close', 'vol', 'p', 's']
|
|
176
|
+
d = d.set_index(['date']).astype(float)
|
|
177
|
+
# d.index = pd.DatetimeIndex(d.index)
|
|
178
|
+
return i, ix, d
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.warning('error get price {}, err {}'.format(i[2:-4], e))
|
|
181
|
+
return i, 'None', pd.DataFrame([])
|
|
182
|
+
|
|
183
|
+
if i[0] in ['0', '1', '3', '5', '6']:
|
|
184
|
+
i = 'sh'+i if i[0] in ['5', '6'] else 'sz'+i
|
|
185
|
+
if i[:2] in ['sh', 'sz']:
|
|
186
|
+
url = qtimg_stock.format(i, freq, sdate, edate, days, fq)
|
|
187
|
+
elif i[:2] == 'hk':
|
|
188
|
+
url = qtimg_stock_hk.format(i, freq, sdate, edate, days, fq)
|
|
189
|
+
elif i[:2] == 'us':
|
|
190
|
+
url = qtimg_stock_us.format(i, freq, sdate, edate, days, fq)
|
|
191
|
+
else:
|
|
192
|
+
raise ValueError('target market not supported')
|
|
193
|
+
a = hget(url)
|
|
194
|
+
#a = json.loads(a.text.replace('kline_dayqfq=', ''))['data'][i]
|
|
195
|
+
print('=====',url,a)
|
|
196
|
+
a = json.loads(a.text)['data'][i]
|
|
197
|
+
name = ''
|
|
198
|
+
try:
|
|
199
|
+
for tkt in ['day', 'qfqday', 'hfqday', 'week', 'qfqweek', 'hfqweek',
|
|
200
|
+
'month', 'qfqmonth', 'hfqmonth']:
|
|
201
|
+
if tkt in a:
|
|
202
|
+
tk = tkt
|
|
203
|
+
break
|
|
204
|
+
b = pd.DataFrame([j[:6] for j in a[tk]],
|
|
205
|
+
columns=['date',
|
|
206
|
+
'open',
|
|
207
|
+
'close',
|
|
208
|
+
'high',
|
|
209
|
+
'low',
|
|
210
|
+
'vol']).set_index(['date']).astype(float)
|
|
211
|
+
if 'qt' in a:
|
|
212
|
+
name = a['qt'][i][1]
|
|
213
|
+
except Exception as e:
|
|
214
|
+
logger.warning('error fetching {}, err: {}'.format(i, e))
|
|
215
|
+
return i, name, b
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def get_price_longer(i, l=2, dd={}):
|
|
219
|
+
# default get price 320 day, l as years
|
|
220
|
+
_, name, a = get_price(i, dd=dd)
|
|
221
|
+
d1 = a.index.format()[0]
|
|
222
|
+
for y in range(1, l):
|
|
223
|
+
d0 = str(int(d1[:4]) - 1) + d1[4:]
|
|
224
|
+
a = pd.concat((get_price(i, d0, d1)[2], a), 0).drop_duplicates()
|
|
225
|
+
d1 = d0
|
|
226
|
+
return i, name, a
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def get_tick(tgts=[]):
|
|
230
|
+
'''
|
|
231
|
+
Get quotes of a tick
|
|
232
|
+
tgt list format:
|
|
233
|
+
us stocks like gb_symbol, e.g. gb_aapl, gb_goog
|
|
234
|
+
Return list of dict of given symbols for current timestamp
|
|
235
|
+
'''
|
|
236
|
+
if not tgts:
|
|
237
|
+
return []
|
|
238
|
+
sina_tick = 'https://hq.sinajs.cn/?list='
|
|
239
|
+
head_row = ['name', 'price', 'price_change_rate', 'timesec',
|
|
240
|
+
'price_change', '_', '_', '_', '_', '_', 'volume', '_', '_',
|
|
241
|
+
'_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_',
|
|
242
|
+
'_', 'last_close', '_', '_', '_', 'turnover', '_', '_', '_', '_']
|
|
243
|
+
|
|
244
|
+
if type(tgts) == list:
|
|
245
|
+
tgts = ['gb_' + i.lower() for i in tgts]
|
|
246
|
+
elif type(tgts) == str:
|
|
247
|
+
tgts = ['gb_' + tgts]
|
|
248
|
+
else:
|
|
249
|
+
raise ValueError('tgt should be list or str, e.g. APPL,')
|
|
250
|
+
|
|
251
|
+
a = hget(sina_tick + ','.join(tgts))
|
|
252
|
+
if not a:
|
|
253
|
+
logger.warning('hget failed {}'.format(tgts))
|
|
254
|
+
return []
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
dat = [i.split('"')[1].split(',') for i in a.text.split(';\n') if ',' in i]
|
|
258
|
+
dat_trim = [{k:i[j] for j,k in enumerate(head_row) if k!='_'} for i in dat]
|
|
259
|
+
except Exception as e:
|
|
260
|
+
logger.warming('data not complete, check tgt be code str or list without'+
|
|
261
|
+
' prefix, your given: {}'.format(tgts))
|
|
262
|
+
return dat_trim
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def get_stock_concepts(i) -> []:
|
|
266
|
+
'''
|
|
267
|
+
Return concept id(start with `BK`) list of a stock, from eastmoney
|
|
268
|
+
'''
|
|
269
|
+
f10url = base64.b64decode('aHR0cDovL2YxMC5lYXN0bW9uZXkuY29tLy9Db3JlQ29uY2V' +
|
|
270
|
+
'wdGlvbi9Db3JlQ29uY2VwdGlvbkFqYXg/Y29kZT0=').decode()
|
|
271
|
+
#drop_cons = ['融资融券', '创业板综', '深股通', '沪股通', '深成500', '长江三角']
|
|
272
|
+
#drop_tails = ['板块', '概念', '0_', '成份', '重仓']
|
|
273
|
+
url = f10url + i
|
|
274
|
+
try:
|
|
275
|
+
concepts = json.loads(hget(url).text)[
|
|
276
|
+
'hxtc'][0]['ydnr'].split()
|
|
277
|
+
except Exception as e:
|
|
278
|
+
logger.error(str(e))
|
|
279
|
+
concepts = ['']
|
|
280
|
+
#concepts = [i for i in concepts if i not in drop_cons]
|
|
281
|
+
#concepts = [i for i in concepts if i[-2:] not in drop_tails]
|
|
282
|
+
#concepts = [i for i in concepts if '股' not in i]
|
|
283
|
+
return concepts
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def get_concept_stocks(bkid, dc=None):
|
|
287
|
+
'''
|
|
288
|
+
Return stocks of input bkid, e.g. BK0420, BK0900
|
|
289
|
+
dc : dictionary of concepts, local cache with get/put
|
|
290
|
+
'''
|
|
291
|
+
if dc is not None:
|
|
292
|
+
a = dc.get(bkid)
|
|
293
|
+
if a:
|
|
294
|
+
return a
|
|
295
|
+
bkid = bkid if isinstance(bkid, str) else 'BK' + str(bkid).zfill(4)
|
|
296
|
+
a = hget(
|
|
297
|
+
base64.b64decode('aHR0cDovL3B1c2gyLmVhc3Rtb25leS5jb20vYXBpL3F0L2NsaXN0' +
|
|
298
|
+
'L2dldD9jYj1qUXVlcnkxMTIzMDQwNTcwNTM4NTY5NDcwMTA1XzE2MTgwNDc5OTA2O' +
|
|
299
|
+
'TAmZmlkPWY2MiZwbz0xJnB6PTUwMCZwbj0xJm5wPTEmZmx0dD0yJmludnQ9MiZmcz' +
|
|
300
|
+
'1iJTNB').decode() +
|
|
301
|
+
bkid +
|
|
302
|
+
'&fields=f3%2Cf6%2Cf12%2Cf14%2Cf21').text
|
|
303
|
+
a = json.loads(
|
|
304
|
+
a.split('jQuery1123040570538569470105_1618047990690(')[1][:-2])['data']['diff']
|
|
305
|
+
logger.debug('get fresh conc {}'.format(bkid))
|
|
306
|
+
a = [ ['sh'+i['f12'] if i['f12'][0]=='6' else 'sz'+i['f12'],
|
|
307
|
+
i['f14'], i['f3'], i['f6'], i['f21']] for i in a]
|
|
308
|
+
return a
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _east_list_fmt(burl, api_name):
|
|
312
|
+
'''
|
|
313
|
+
formatter of eastmoney api
|
|
314
|
+
Return list of list:
|
|
315
|
+
[sid, name, rise, amount, mkt]
|
|
316
|
+
'''
|
|
317
|
+
a = hget(base64.b64decode(burl).decode() +
|
|
318
|
+
str(int(time.time()*1e3)))
|
|
319
|
+
if a:
|
|
320
|
+
a = a.text
|
|
321
|
+
else:
|
|
322
|
+
return
|
|
323
|
+
a = json.loads(a.split(api_name+'(')[1][:-2])['data']['diff']
|
|
324
|
+
a = [ [i['f12'],i['f14'], i['f3'], i['f6'], i['f20']] for i in a]
|
|
325
|
+
return a
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def get_all_industries():
|
|
329
|
+
'''
|
|
330
|
+
Return sorted industry item list ordered by latest amount of money,
|
|
331
|
+
item in returned list are [code, name, change, amount, price]
|
|
332
|
+
'''
|
|
333
|
+
a = _east_list_fmt('aHR0cHM6Ly84Ny5wdXNoMi5lYXN0bW9uZXkuY29tL2FwaS9xdC9jbGl'+
|
|
334
|
+
'zdC9nZXQ/Y2I9alF1ZXJ5MTEyNDAzNzExNzU2NTU3MTk3MTM0NV8xNjI3MDQ3MTg4NTk5'+
|
|
335
|
+
'JnBuPTEmcHo9MTAwJnBvPTEmbnA9MSZ1dD1iZDFkOWRkYjA0MDg5NzAwY2Y5YzI3ZjZmN'+
|
|
336
|
+
'zQyNjI4MSZmbHR0PTImaW52dD0yJmZpZD1mMyZmcz1tOjkwK3Q6MitmOiE1MCZmaWVsZH'+
|
|
337
|
+
'M9ZjMsZjYsZjEyLGYxNCxmMjAsZjEwNCxmMTA1Jl89',
|
|
338
|
+
'jQuery1124037117565571971345_1627047188599')
|
|
339
|
+
logger.debug('get industries {}'.format(len(a)))
|
|
340
|
+
return a
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def get_all_concepts():
|
|
344
|
+
'''
|
|
345
|
+
Return sorted concept item list ordered by latest amount of money,
|
|
346
|
+
item in returned list are [code, name, change, amount, price]
|
|
347
|
+
'''
|
|
348
|
+
a = _east_list_fmt('aHR0cHM6Ly8yMi5wdXNoMi5lYXN0bW9uZXkuY29tL2FwaS9xdC9jbGl'+
|
|
349
|
+
'zdC9nZXQ/Y2I9alF1ZXJ5MTEyNDA3MzI5ODQxOTMwNzY4OTc5XzE2MjcxMDk0NjA2MzMm'+
|
|
350
|
+
'cG49MSZwej00MDAmcG89MSZucD0xJnV0PWJkMWQ5ZGRiMDQwODk3MDBjZjljMjdmNmY3N'+
|
|
351
|
+
'DI2MjgxJmZsdHQ9MiZpbnZ0PTImZmlkPWYzJmZzPW06OTArdDozK2Y6ITUwJmZpZWxkcz'+
|
|
352
|
+
'1mMyxmNixmMTIsZjE0LGYyMCxmMTA0LGYxMDUmXz0='
|
|
353
|
+
,'jQuery112407329841930768979_1627109460633')
|
|
354
|
+
logger.debug('get concepts {}'.format(len(a)))
|
|
355
|
+
return a
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def get_bk_stocks(bkid):
|
|
359
|
+
'''
|
|
360
|
+
Return stock item list of given bk id,
|
|
361
|
+
item in returned list are [code, name, change, amount, price]
|
|
362
|
+
'''
|
|
363
|
+
url = base64.b64decode('aHR0cDovLzgyLnB1c2gyLmVhc3Rtb25leS5jb20vYXBpL3F0L2'+
|
|
364
|
+
'NsaXN0L2dldD9jYj1qUXVlcnkxMTI0MDQ4Njk5NjMwMDk1MTM3NzE0XzE2Mjc0Nzc0OTU'+
|
|
365
|
+
'wNjQmcG49MSZwej0yMDAwJnBvPTAmbnA9MSZ1dD1iZDFkOWRkYjA0MDg5NzAwY2Y5YzI3'+
|
|
366
|
+
'ZjZmNzQyNjI4MSZmbHR0PTImaW52dD0yJmZpZD1mNiZmcz1iOg==').decode()+ \
|
|
367
|
+
bkid + '+f:!50&fields=f3,f6,f12,f14,f20&_='
|
|
368
|
+
a = _east_list_fmt(base64.b64encode(bytes(url, encoding='utf-8')),
|
|
369
|
+
'jQuery1124048699630095137714_1627477495064')
|
|
370
|
+
logger.debug('get bk stocks {}'.format(len(a)))
|
|
371
|
+
return a
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def get_industry_stocks(bkid):
|
|
375
|
+
'''
|
|
376
|
+
Return sorted industry item list ordered by latest amount of money,
|
|
377
|
+
item in returned list are [code, name, change, amount, price]
|
|
378
|
+
'''
|
|
379
|
+
url = base64.b64decode('aHR0cHM6Ly82Mi5wdXNoMi5lYXN0bW9uZXkuY29tL2FwaS9xdC'+
|
|
380
|
+
'9jbGlzdC9nZXQ/Y2I9alF1ZXJ5MTEyNDA4Mzc4MjAwMDc0NDQ0MzA5XzE2Mjc4MjQ2MDM'+
|
|
381
|
+
'1NjImcG49MSZwej0yMDAwJnBvPTAmbnA9MSZ1dD1iZDFkOWRkYjA0MDg5NzAwY2Y5YzI3'+
|
|
382
|
+
'ZjZmNzQyNjI4MSZmbHR0PTImaW52dD0yJmZpZD1mNiZmcz1iOg==').decode()+ \
|
|
383
|
+
bkid + '+f:!50&fields=f3,f6,f12,f14,f20&_='
|
|
384
|
+
a = _east_list_fmt(base64.b64encode(bytes(url, encoding='utf-8')),
|
|
385
|
+
'jQuery112408378200074444309_1627824603562')
|
|
386
|
+
logger.debug('get industry stocks {}'.format(len(a)))
|
|
387
|
+
return a
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def get_hk_stocks_ggt():
|
|
391
|
+
'''
|
|
392
|
+
Return sorted stock item list in GangGuTong, ordered by amount of money,
|
|
393
|
+
item in returned list are [code, name, change, amount, price]
|
|
394
|
+
'''
|
|
395
|
+
a = _east_list_fmt('aHR0cHM6Ly8yLnB1c2gyLmVhc3Rtb25leS5jb20vYXBpL3F0L2NsaX'+
|
|
396
|
+
'N0L2dldD9jYj1qUXVlcnkxMTI0MDI0MzYyMzA4OTA2NjE1MDgyXzE2MjgyNTg5MzEyMjQ'+
|
|
397
|
+
'mcG49MSZwej0xMDAwJnBvPTAmbnA9MSZ1dD1iZDFkOWRkYjA0MDg5NzAwY2Y5YzI3ZjZm'+
|
|
398
|
+
'NzQyNjI4MSZmbHR0PTImaW52dD0yJmZpZD1mNiZmcz1iOkRMTUswMTQ2LGI6RExNSzAxN'+
|
|
399
|
+
'DQmZmllbGRzPWYzLGY2LGYxMixmMTQsZjIwJl89',
|
|
400
|
+
'jQuery1124024362308906615082_1628258931224')
|
|
401
|
+
a = [ ['hk'+i[0], i[1], i[2], i[3], i[4]] for i in a]
|
|
402
|
+
logger.debug('get hk stocks GangGuTong {}'.format(len(a)))
|
|
403
|
+
return a
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def get_hk_stocks_hsi():
|
|
407
|
+
'''
|
|
408
|
+
Return sorted stock item list in HSI, ordered by amount of money,
|
|
409
|
+
item in returned list are [code, name, change, amount, price]
|
|
410
|
+
'''
|
|
411
|
+
a = _east_list_fmt('aHR0cHM6Ly81Ni5wdXNoMi5lYXN0bW9uZXkuY29tL2FwaS9xdC9jbG'+
|
|
412
|
+
'lzdC9nZXQ/Y2I9alF1ZXJ5MTEyNDA3ODg4ODY4NDU5NDc5NzkyXzE2MjgyNTk1NjQ2NzE'+
|
|
413
|
+
'mcG49MSZwej0xMDAwJnBvPTEmbnA9MSZ1dD1iZDFkOWRkYjA0MDg5NzAwY2Y5YzI3ZjZm'+
|
|
414
|
+
'NzQyNjI4MSZmbHR0PTImaW52dD0yJmZpZD1mNiZmcz1iOkRMTUswMTQxJmZpZWxkcz1mM'+
|
|
415
|
+
'yxmNixmMTIsZjE0LGYyMCZfPQ==',
|
|
416
|
+
'jQuery112407888868459479792_1628259564671')
|
|
417
|
+
a = [ ['hk'+i[0], i[1], i[2], i[3], i[4]] for i in a]
|
|
418
|
+
logger.debug('get hk stocks HSI {}'.format(len(a)))
|
|
419
|
+
return a
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
|
rquote/plots.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from .main import get_price
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PlotUtils:
|
|
7
|
+
|
|
8
|
+
def plot_candle(i, sdate='', edate='', dsh=False, vol=True):
|
|
9
|
+
|
|
10
|
+
import plotly.graph_objs as go
|
|
11
|
+
'''
|
|
12
|
+
Plot candles of i
|
|
13
|
+
Input: id
|
|
14
|
+
Output: plotting data and default layout
|
|
15
|
+
'''
|
|
16
|
+
layout = go.Layout(
|
|
17
|
+
barmode = 'stack',
|
|
18
|
+
xaxis = dict(
|
|
19
|
+
type = 'category',
|
|
20
|
+
rangeslider = dict(visible=False),
|
|
21
|
+
spikemode = 'toaxis+across+marker',
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
dt = []
|
|
26
|
+
icolor, dcolor = 'red', 'green'
|
|
27
|
+
_, n, v = get_price(i, sdate=sdate, edate=edate)
|
|
28
|
+
v = (v - v.low.min()) * 10 / (v.high.max() - v.low.min()) + 2
|
|
29
|
+
v = v.round(3) # compress html data
|
|
30
|
+
|
|
31
|
+
dt += [
|
|
32
|
+
go.Candlestick(
|
|
33
|
+
x=v.index.to_series(),
|
|
34
|
+
open=v.open,
|
|
35
|
+
high=v.high,
|
|
36
|
+
low=v.low,
|
|
37
|
+
close=v.close,
|
|
38
|
+
opacity=0.5,
|
|
39
|
+
hoverinfo='none',
|
|
40
|
+
name=n,
|
|
41
|
+
increasing={'line':{'color':icolor}},
|
|
42
|
+
decreasing={'line':{'color':dcolor}}
|
|
43
|
+
),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
if vol:
|
|
47
|
+
vvol = v.vol / v.vol.max()
|
|
48
|
+
vvol *= 2
|
|
49
|
+
dt += [go.Bar(x = v.index.to_series(), y=vvol, name='vol', opacity=0.5)]
|
|
50
|
+
|
|
51
|
+
icolor, dcolor = 'cyan', 'gray'
|
|
52
|
+
if dsh:
|
|
53
|
+
_, _, dsh = get_price('sh000001', sdate=sdate, edate=edate)
|
|
54
|
+
dsh = (dsh / dsh.iloc[0,0] - 1) * 10
|
|
55
|
+
dt += [
|
|
56
|
+
go.Candlestick(
|
|
57
|
+
x=dsh.index.to_series(),
|
|
58
|
+
open=dsh.open,
|
|
59
|
+
high=dsh.high,
|
|
60
|
+
low=dsh.low,
|
|
61
|
+
close=dsh.close,
|
|
62
|
+
opacity=0.5,
|
|
63
|
+
hoverinfo='none',
|
|
64
|
+
name='sh',
|
|
65
|
+
increasing={'line':{'color':icolor}},
|
|
66
|
+
decreasing={'line':{'color':dcolor}}
|
|
67
|
+
),
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
return dt, layout
|
|
71
|
+
|
rquote/utils.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import random
|
|
5
|
+
import logging
|
|
6
|
+
import httpx
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import uuid
|
|
10
|
+
|
|
11
|
+
def setup_logger():
|
|
12
|
+
logger = logging.getLogger('rquote')
|
|
13
|
+
if not logger.handlers:
|
|
14
|
+
logger.setLevel(logging.INFO)
|
|
15
|
+
file_handler = logging.FileHandler('/tmp/rquote.log')
|
|
16
|
+
|
|
17
|
+
formatter = logging.Formatter('%(asctime)-15s:%(lineno)s %(message)s')
|
|
18
|
+
file_handler.setFormatter(formatter)
|
|
19
|
+
|
|
20
|
+
logger.addHandler(file_handler)
|
|
21
|
+
logger.addHandler(logging.StreamHandler())
|
|
22
|
+
|
|
23
|
+
return logger
|
|
24
|
+
|
|
25
|
+
logger = setup_logger()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WebUtils:
|
|
29
|
+
@staticmethod
|
|
30
|
+
def ua():
|
|
31
|
+
ua_list = [
|
|
32
|
+
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101',
|
|
33
|
+
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122',
|
|
34
|
+
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71',
|
|
35
|
+
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95',
|
|
36
|
+
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71',
|
|
37
|
+
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)',
|
|
38
|
+
'Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50',
|
|
39
|
+
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0',
|
|
40
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.5 Safari/534.55.3',
|
|
41
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
|
|
42
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/521.61'
|
|
43
|
+
]
|
|
44
|
+
return random.choice(ua_list)
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def headers(cls):
|
|
48
|
+
header = {
|
|
49
|
+
'referer': str(uuid.uuid4()),
|
|
50
|
+
'user-agent': cls.ua()
|
|
51
|
+
}
|
|
52
|
+
return header
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def reqget(cls, url, headers, method, proxy=None):
|
|
56
|
+
'''
|
|
57
|
+
request.get() wrapper
|
|
58
|
+
'''
|
|
59
|
+
headers['user-agent'] = cls.ua
|
|
60
|
+
try:
|
|
61
|
+
r = httpx.get(url, allow_redirects=True)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
logger.error('Fetch url {} err: {}'.format(url, e))
|
|
64
|
+
return None
|
|
65
|
+
if r:
|
|
66
|
+
if method == 'text':
|
|
67
|
+
return r.text
|
|
68
|
+
elif method == 'content':
|
|
69
|
+
return r.content
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def test_proxy(cls, proxy: str):
|
|
73
|
+
'''
|
|
74
|
+
proxy format 'ip:port'
|
|
75
|
+
test baidu.com for cn
|
|
76
|
+
# test google.com for non-cn (not effective due to DNS hijacking)
|
|
77
|
+
'''
|
|
78
|
+
try:
|
|
79
|
+
with httpx.Client(proxies=proxy) as client:
|
|
80
|
+
r = client.get('https://baidu.com', timeout=2)
|
|
81
|
+
if r.ok:
|
|
82
|
+
return 1
|
|
83
|
+
else:
|
|
84
|
+
return 0
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.info(f'test proxy {proxy} negative')
|
|
87
|
+
return 0
|
|
88
|
+
if r.ok:
|
|
89
|
+
logger.info(f'test proxy {proxy} positive')
|
|
90
|
+
return 1
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class BasicFactors:
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def break_rise(d) -> float:
|
|
97
|
+
if d.open[-1] / d.close[-2] > 1.002 and d.close[-1] > d.open[-1]:
|
|
98
|
+
return round((d.open[-1] - d.close[-2]) / d.close[-2], 2)
|
|
99
|
+
else:
|
|
100
|
+
return 0
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def min_resist(d) -> float:
|
|
104
|
+
sup, pre, pcur = 0, 0, d.close[-1]
|
|
105
|
+
for i in d.iterrows():
|
|
106
|
+
p = (i[1].open + i[1].close) / 2
|
|
107
|
+
if p > pcur:
|
|
108
|
+
pre += i[1].vol
|
|
109
|
+
if p < pcur:
|
|
110
|
+
sup += i[1].vol
|
|
111
|
+
minres = (sup - pre) / (sup + pre)
|
|
112
|
+
if abs(minres - 1) < .01 and d.close[-2] < max(d.close[:-2]):
|
|
113
|
+
minres += .2
|
|
114
|
+
minres = round(minres, 2)
|
|
115
|
+
return minres
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def vol_extreme(d):
|
|
119
|
+
d = d.vol
|
|
120
|
+
v60max = d.rolling(60).max()
|
|
121
|
+
v60min = d.rolling(60).min()
|
|
122
|
+
# any in last 3days
|
|
123
|
+
for i in range(1, 3):
|
|
124
|
+
if d[-i] > v60max[-i - 1]:
|
|
125
|
+
return round(d[-i] / v60max[-i - 1], 2)
|
|
126
|
+
if d[-i] < v60min[-i - 1]:
|
|
127
|
+
return round(-d[-i] / v60min[-i - 1], 2)
|
|
128
|
+
else:
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def bias_rate_over_ma60(d):
|
|
133
|
+
r60 = d.close - d.close.rolling(60).mean()
|
|
134
|
+
if r60[-1] > 0:
|
|
135
|
+
return round(r60[-1] / r60.rolling(60).max()[-1], 2)
|
|
136
|
+
else:
|
|
137
|
+
return round(-r60[-1] / r60.rolling(60).min()[-1], 2)
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def op_ma(d) -> float:
|
|
141
|
+
''' op: ma score'''
|
|
142
|
+
if len(d) < 22:
|
|
143
|
+
return
|
|
144
|
+
d['mv5'] = d.close.rolling(5).mean()
|
|
145
|
+
d['mv10'] = d.close.rolling(10).mean()
|
|
146
|
+
d['mv20'] = d.close.rolling(20).mean()
|
|
147
|
+
d['mv60'] = d.close.rolling(60).mean()
|
|
148
|
+
|
|
149
|
+
def ma20(d):
|
|
150
|
+
ret = 0
|
|
151
|
+
# .2 for over ma60
|
|
152
|
+
if d.close[-1] > d.mv60[-1]:
|
|
153
|
+
ret += 0.2
|
|
154
|
+
# .2 for all upwards ma's
|
|
155
|
+
if (d.mv5[-1] > d.mv5[-2] and d.mv10[-1] >
|
|
156
|
+
d.mv10[-2] and d.mv20[-1] > d.mv20[-2]):
|
|
157
|
+
ret += 0.2
|
|
158
|
+
for j in range(1, 3):
|
|
159
|
+
if not (d.close[-j] > d.mv5[-j] and d.close[-j]
|
|
160
|
+
> d.mv10[-j] and d.close[-j] > d.mv20[-j]):
|
|
161
|
+
return ret
|
|
162
|
+
for j in range(3, 5):
|
|
163
|
+
if (d.close[-j] > d.mv5[-j] and d.close[-j] >
|
|
164
|
+
d.mv10[-j] and d.close[-j] > d.mv20[-j]):
|
|
165
|
+
return ret
|
|
166
|
+
# .2 for just rush over ma's (fresh score)
|
|
167
|
+
ret += 0.2
|
|
168
|
+
return ret
|
|
169
|
+
return ma20(d)
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
def op_cnt(d, cont_min=3) -> (int):
|
|
173
|
+
''' op: count continous bulling days over index'''
|
|
174
|
+
d.index = pd.DatetimeIndex(d.index)
|
|
175
|
+
td = (d.p_change_on_sh.rolling(cont_min).min() > 0).astype(int) * \
|
|
176
|
+
(d.p_change.rolling(cont_min).min() > 0).astype(int)
|
|
177
|
+
ret = 0 if td[-1] <= 0 else td[-1]
|
|
178
|
+
# is_first_day = True if td[-2] <= 0 else False
|
|
179
|
+
return ret
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class hget:
|
|
183
|
+
'''
|
|
184
|
+
class version request.get wrapper
|
|
185
|
+
'''
|
|
186
|
+
def __init__(self, url, *args, **kwargs):
|
|
187
|
+
self.url = url
|
|
188
|
+
try:
|
|
189
|
+
r = httpx.get(
|
|
190
|
+
self.url, follow_redirects=True, *args, **kwargs)
|
|
191
|
+
self.text = r.text
|
|
192
|
+
self.content = r.content
|
|
193
|
+
except Exception as e:
|
|
194
|
+
logger.error(f'fetch {self.url} err: {e}')
|
|
195
|
+
self.text = ''
|
|
196
|
+
self.content = b''
|
|
197
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rquote
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: Mostly day quotes of cn/hk/us/fund/future markets, side with quote list fetch
|
|
5
|
+
Home-page: https://github.com/kids/rquote
|
|
6
|
+
Author: Roizhao
|
|
7
|
+
Author-email: roizhao@gmail.com
|
|
8
|
+
Requires-Python: >=3.4.0
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: httpx>=0.20.0
|
|
11
|
+
Requires-Dist: pandas>=1.0.0
|
|
12
|
+
Requires-Dist: setuptools>=59.6.0
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: author-email
|
|
15
|
+
Dynamic: home-page
|
|
16
|
+
Dynamic: requires-python
|
|
17
|
+
|
|
18
|
+
# rquote
|
|
19
|
+
|
|
20
|
+
`get_price`方法提供 A股/港股/美股/ETF基金/期货 日线历史数据获取
|
|
21
|
+
|
|
22
|
+
使用新浪/腾讯的id形式,如`sh000001`表示上证指数,`sz000001`表示深市000001股票`平安银行`,`sh510050`表示上证50指数ETF,`hk00700`表示港股腾讯。
|
|
23
|
+
期货代码需加`fu`前缀,如`fuAP2110`,美股需加对应交易所后缀,如`usBABA.N`,`usC.N`,`usAAPL.OQ`等
|
|
24
|
+
BTC/USD也放在fu下面,i.d.`fuBTC`
|
|
25
|
+
|
|
26
|
+
e.g.
|
|
27
|
+
```
|
|
28
|
+
from rquote import get_price
|
|
29
|
+
sid, nm, df = get_price('sh000001')
|
|
30
|
+
df.head() # 数据为pandas dataframe
|
|
31
|
+
```
|
|
32
|
+
> open close high low vol
|
|
33
|
+
> date
|
|
34
|
+
> 2024-02-06 2680.48 2789.49 2802.93 2669.67 502849313.0
|
|
35
|
+
> 2024-02-07 2791.51 2829.70 2829.70 2770.53 547117439.0
|
|
36
|
+
> 2024-02-08 2832.49 2865.90 2867.47 2827.90 531108893.0
|
|
37
|
+
> 2024-02-19 2886.59 2910.54 2910.54 2867.71 458967704.0
|
|
38
|
+
> 2024-02-20 2902.88 2922.73 2927.31 2887.47 350138735.0
|
|
39
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
rquote/__init__.py,sha256=kduE8uV71sC878gc4HpRqlzfAJc4cHEKk6HsQkHQ52s,373
|
|
2
|
+
rquote/main.py,sha256=CoZ1EBGSbSJ1422wotxWKXAjaGjVAZwMDnmIsz9iAeE,17108
|
|
3
|
+
rquote/plots.py,sha256=N8uvD6ju9tow0DllPQiXiM7EoPC2bK8X7QF6NQainKs,2342
|
|
4
|
+
rquote/utils.py,sha256=ztDIlW9UyJQHPO_Q-DCzp0sOsKT24sFdHhpizw3P9x8,6781
|
|
5
|
+
rquote-0.2.4.dist-info/METADATA,sha256=fgz-6BYaAoNPavmoT_N3kTelOOa7N8Z1-vugUtQPJtM,1429
|
|
6
|
+
rquote-0.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
+
rquote-0.2.4.dist-info/top_level.txt,sha256=CehAiaZx7Fo8HGoV2zd5GhILUW1jQEN8YS-cWMlrK9Y,7
|
|
8
|
+
rquote-0.2.4.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rquote
|