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 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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ rquote