rquote 0.2.2__tar.gz → 0.2.5__tar.gz

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-0.2.5/PKG-INFO ADDED
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.4
2
+ Name: rquote
3
+ Version: 0.2.5
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
+ Dynamic: author
13
+ Dynamic: author-email
14
+ Dynamic: home-page
15
+ Dynamic: requires-python
16
+
17
+ # rquote
18
+
19
+ `get_price`方法提供 A股/港股/美股/ETF基金/期货 日线(及其他周期)历史数据获取
20
+
21
+ 使用新浪/腾讯的id形式,如`sh000001`表示上证指数,`sz000001`表示深市000001股票`平安银行`,`sh510050`表示上证50指数ETF,`hk00700`表示港股腾讯。
22
+ 期货代码需加`fu`前缀,如`fuAP2110`,美股需加对应交易所后缀,如`usBABA.N`,`usC.N`,`usAAPL.OQ`等
23
+ BTC/USD放在fu下面,i.e.`fuBTC`
24
+
25
+ e.g.
26
+ ```
27
+ from rquote import get_price
28
+ sid, nm, df = get_price('sh000001')
29
+ df.head() # 数据为pandas dataframe
30
+ ```
31
+ > open close high low vol
32
+ > date
33
+ > 2024-02-06 2680.48 2789.49 2802.93 2669.67 502849313.0
34
+ > 2024-02-07 2791.51 2829.70 2829.70 2770.53 547117439.0
35
+ > 2024-02-08 2832.49 2865.90 2867.47 2827.90 531108893.0
36
+ > 2024-02-19 2886.59 2910.54 2910.54 2867.71 458967704.0
37
+ > 2024-02-20 2902.88 2922.73 2927.31 2887.47 350138735.0
38
+
39
+ Notes:
40
+ 使用GenImage需要补充依赖
41
+ > kaleido>=1.0.0rc0
42
+ > plolty>=5.0.0
rquote-0.2.5/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # rquote
2
+
3
+ `get_price`方法提供 A股/港股/美股/ETF基金/期货 日线(及其他周期)历史数据获取
4
+
5
+ 使用新浪/腾讯的id形式,如`sh000001`表示上证指数,`sz000001`表示深市000001股票`平安银行`,`sh510050`表示上证50指数ETF,`hk00700`表示港股腾讯。
6
+ 期货代码需加`fu`前缀,如`fuAP2110`,美股需加对应交易所后缀,如`usBABA.N`,`usC.N`,`usAAPL.OQ`等
7
+ BTC/USD放在fu下面,i.e.`fuBTC`
8
+
9
+ e.g.
10
+ ```
11
+ from rquote import get_price
12
+ sid, nm, df = get_price('sh000001')
13
+ df.head() # 数据为pandas dataframe
14
+ ```
15
+ > open close high low vol
16
+ > date
17
+ > 2024-02-06 2680.48 2789.49 2802.93 2669.67 502849313.0
18
+ > 2024-02-07 2791.51 2829.70 2829.70 2770.53 547117439.0
19
+ > 2024-02-08 2832.49 2865.90 2867.47 2827.90 531108893.0
20
+ > 2024-02-19 2886.59 2910.54 2910.54 2867.71 458967704.0
21
+ > 2024-02-20 2902.88 2922.73 2927.31 2887.47 350138735.0
22
+
23
+ Notes:
24
+ 使用GenImage需要补充依赖
25
+ > kaleido>=1.0.0rc0
26
+ > plolty>=5.0.0
@@ -0,0 +1,14 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel", "httpx>=0.20.0", "pandas>=1.0.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "rquote"
7
+ version = "0.2.5"
8
+ description = "Mostly day quotes of cn/hk/us/fund/future markets, side with quote list fetch"
9
+ readme = "README.md"
10
+ requires-python = ">=3.4"
11
+ dependencies = [
12
+ "httpx>=0.20.0",
13
+ "pandas>=1.0.0",
14
+ ]
@@ -0,0 +1,13 @@
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
11
+ from .main import get_all_concepts, get_all_industries
12
+ from .utils import WebUtils, BasicFactors
13
+ from .plots import PlotUtils
@@ -1,20 +1,11 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- import os
4
- import requests
5
3
  import json
6
4
  import time
7
- import random
8
5
  import re
9
- import sys
10
6
  import base64
11
- import logging
12
7
  import pandas as pd
13
- from .utils import WebUtils, reqget
14
- # logging.getLogger().setLevel(logging.INFO)
15
- logging.basicConfig(filename='/tmp/rquote.log',
16
- format='%(asctime)-15s:%(lineno)s %(message)s',
17
- level=logging.INFO)
8
+ from .utils import WebUtils, hget, logger
18
9
 
19
10
 
20
11
  def make_tgts(mkts=['ch', 'hk', 'us', 'fund', 'future'], money_min=2e8) -> []:
@@ -26,7 +17,7 @@ def get_cn_stock_list(money_min=2e8):
26
17
  Return sorted stock list ordered by latest amount of money, cut at `money_min`
27
18
  item in returned list are [code, name, change, amount, mktcap]
28
19
  '''
29
- a = reqget(
20
+ a = hget(
30
21
  base64.b64decode('aHR0cDovLzM4LnB1c2gyLmVhc3Rtb25leS5jb20vYXBpL3F0L2Ns'+
31
22
  'aXN0L2dldD9jYj1qUXVlcnkxMTI0MDk0NTg3NjE4NDQzNzQ4MDFfMTYyNzI4ODQ4O'+
32
23
  'Tk2MSZwbj0xJnB6PTEwMDAwJnBvPTEmbnA9MSZ1dD1iZDFkOWRkYjA0MDg5NzAwY2'+
@@ -54,7 +45,7 @@ def get_cn_stock_list(money_min=2e8):
54
45
 
55
46
 
56
47
  # def get_hk_stocks_hotest80():
57
- # a = reqget(
48
+ # a = hget(
58
49
  # 'http://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php' +
59
50
  # '/Market_Center.getHKStockData?page=1&num=80&sort=amount&asc=0&node=' +
60
51
  # 'qbgg_hk&_s_r_a=sort').text
@@ -67,12 +58,12 @@ def get_cn_stock_list(money_min=2e8):
67
58
  def get_us_stocks_biggest(k=60):
68
59
  # return list of [symbol, name, price, volume, mktcap, pe]
69
60
  uscands = []
70
- a = reqget(
61
+ a = hget(
71
62
  "https://stock.finance.sina.com.cn/usstock/api/jsonp.php/IO.XSRV2."+
72
63
  "CallbackList['f0j3ltzVzdo2Fo4p']/US_CategoryService.getList?page=1"+
73
64
  "&num=60&sort=&asc=0&market=&id=", headers=WebUtils.headers()).text
74
65
  if a:
75
- uslist = json.loads(a.split('(')[1][:-2])['data']
66
+ uslist = json.loads(a.split('(',1)[1][:-2])['data']
76
67
  # Warning: symbol not fitted
77
68
  uscands = [('us' + i['symbol'], i['name'], i['price'], i['volume'],
78
69
  i['mktcap']) for i in uslist]
@@ -84,7 +75,7 @@ def get_cn_fund_list():
84
75
  Return sorted etf list (ordered by latest amount of money),
85
76
  of [code, name, change, amount, price]
86
77
  '''
87
- a = reqget(base64.b64decode('aHR0cDovL3ZpcC5zdG9jay5maW5hbmNlLnNpbmEuY29tL'+
78
+ a = hget(base64.b64decode('aHR0cDovL3ZpcC5zdG9jay5maW5hbmNlLnNpbmEuY29tL'+
88
79
  'mNuL3F1b3Rlc19zZXJ2aWNlL2FwaS9qc29ucC5waHAvSU8uWFNSVjIuQ2FsbGJhY2tMaX'+
89
80
  'N0WydrMldhekswNk5Rd2xoeVh2J10vTWFya2V0X0NlbnRlci5nZXRIUU5vZGVEYXRhU2l'+
90
81
  'tcGxlP3BhZ2U9MSZudW09MTAwMCZzb3J0PWFtb3VudCZhc2M9MCZub2RlPWV0Zl9ocV9m'+
@@ -104,7 +95,7 @@ def get_cn_future_list():
104
95
  'fuFU2109',
105
96
  ...]
106
97
  '''
107
- a = reqget('https://finance.sina.com.cn/futuremarket/').text
98
+ a = hget('https://finance.sina.com.cn/futuremarket/').text
108
99
  if a:
109
100
  futurelist_active = [
110
101
  'fu' + i for i in re.findall(r'quotes/(.*?\d+).shtml', a)]
@@ -125,9 +116,9 @@ def get_price(i, sdate='', edate='', freq='day', days=320, fq='qfq',
125
116
  a = dd.get(i)
126
117
  if a:
127
118
  n, d = a
128
- logging.debug('loading price from dd {}'.format(i))
119
+ logger.debug('loading price from dd {}'.format(i))
129
120
  return i, n, d
130
- logging.debug('fetching price of {}'.format(i))
121
+ logger.debug('fetching price of {}'.format(i))
131
122
  qtimg_stock = 'http://web.ifzq.gtimg.cn/appstock/app/newfqkline/get?param=' + \
132
123
  '{},{},{},{},{},{}'
133
124
  qtimg_stock_hk = 'http://web.ifzq.gtimg.cn/appstock/app/hkfqkline/get?' + \
@@ -136,25 +127,28 @@ def get_price(i, sdate='', edate='', freq='day', days=320, fq='qfq',
136
127
  'param={},{},{},{},{},{}'
137
128
  sina_future_d = 'https://stock2.finance.sina.com.cn/futures/api/jsonp.php/' + \
138
129
  'var%20t1nf_{}=/InnerFuturesNewService.getDailyKLine?symbol={}'
130
+ sina_btc = 'https://quotes.sina.cn/fx/api/openapi.php/BtcService.getDayKLine?' + \
131
+ 'symbol=btcbtcusd'
132
+
139
133
  # sina_future_d.format('FB0','FB0')
140
134
 
141
135
  if i[:2] == 'BK':
142
136
  try:
143
- a = reqget(base64.b64decode('aHR0cDovL3B1c2gyaGlzLmVhc3' +
137
+ a = hget(base64.b64decode('aHR0cDovL3B1c2gyaGlzLmVhc3' +
144
138
  'Rtb25leS5jb20vYXBpL3F0L3N0b2NrL2tsaW5lL2dldD9jYj1qUX' +
145
139
  'VlcnkxMTI0MDIyNTY2NDQ1ODczNzY2OTcyXzE2MTc4NjQ1NjgxMz' +
146
140
  'Emc2VjaWQ9OTAu').decode() + i +
147
141
  '&fields1=f1%2Cf2%2Cf3%2Cf4%2Cf5' +
148
142
  '&fields2=f51%2Cf52%2Cf53%2Cf54%2Cf55%2Cf56%2Cf57%2Cf58' +
149
- '&klt=101&fqt=0&beg=19900101&end=20220101&_=1',
143
+ '&klt=101&fqt=0&beg=19900101&end=20990101&_=1',
150
144
  headers=WebUtils.headers())
151
145
  if not a:
152
- logging.warning('{} reqget failed: {}'.format(i, a))
146
+ logger.warning('{} hget failed: {}'.format(i, a))
153
147
  return i, 'None', pd.DataFrame([])
154
148
  a = json.loads(a.text.split(
155
149
  'jQuery1124022566445873766972_1617864568131(')[1][:-2])
156
150
  if not a['data']:
157
- logging.warning('{} data empty: {}'.format(i, a))
151
+ logger.warning('{} data empty: {}'.format(i, a))
158
152
  return i, 'None', pd.DataFrame([])
159
153
  name = a['data']['name']
160
154
  d = pd.DataFrame([i.split(',') for i in a['data']['klines']], columns=[
@@ -163,20 +157,27 @@ def get_price(i, sdate='', edate='', freq='day', days=320, fq='qfq',
163
157
  # d.index = pd.DatetimeIndex(d.index)
164
158
  return i, name, d
165
159
  except Exception as e:
166
- logging.warning('error fetching {}, err: {}'.format(i, e))
160
+ logger.warning('error fetching {}, err: {}'.format(i, e))
167
161
  return i, 'None', pd.DataFrame([])
168
162
 
169
163
  if i[:2] == 'fu':
170
164
  try:
171
165
  ix = i[2:] if i[-1]=='0' else i[2:-4]
172
- d = pd.DataFrame(json.loads(reqget(sina_future_d.format(
173
- ix, ix)).text.split('(')[1][:-2]))
174
- d.columns = ['date', 'open', 'high', 'low', 'close', 'vol', 'p', 's']
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']
175
176
  d = d.set_index(['date']).astype(float)
176
177
  # d.index = pd.DatetimeIndex(d.index)
177
- return i, '', d
178
+ return i, ix, d
178
179
  except Exception as e:
179
- logging.warning('error get price {}, err {}'.format(i[2:-4], e))
180
+ logger.warning('error get price {}, err {}'.format(i[2:-4], e))
180
181
  return i, 'None', pd.DataFrame([])
181
182
 
182
183
  if i[0] in ['0', '1', '3', '5', '6']:
@@ -189,8 +190,9 @@ def get_price(i, sdate='', edate='', freq='day', days=320, fq='qfq',
189
190
  url = qtimg_stock_us.format(i, freq, sdate, edate, days, fq)
190
191
  else:
191
192
  raise ValueError('target market not supported')
192
- a = reqget(url)
193
+ a = hget(url)
193
194
  #a = json.loads(a.text.replace('kline_dayqfq=', ''))['data'][i]
195
+ print('=====',url,a)
194
196
  a = json.loads(a.text)['data'][i]
195
197
  name = ''
196
198
  try:
@@ -209,7 +211,7 @@ def get_price(i, sdate='', edate='', freq='day', days=320, fq='qfq',
209
211
  if 'qt' in a:
210
212
  name = a['qt'][i][1]
211
213
  except Exception as e:
212
- logging.warning('error fetching {}, err: {}'.format(i, e))
214
+ logger.warning('error fetching {}, err: {}'.format(i, e))
213
215
  return i, name, b
214
216
 
215
217
 
@@ -246,16 +248,16 @@ def get_tick(tgts=[]):
246
248
  else:
247
249
  raise ValueError('tgt should be list or str, e.g. APPL,')
248
250
 
249
- a = reqget(sina_tick + ','.join(tgts))
251
+ a = hget(sina_tick + ','.join(tgts))
250
252
  if not a:
251
- logging.warning('reqget failed {}'.format(tgts))
253
+ logger.warning('hget failed {}'.format(tgts))
252
254
  return []
253
255
 
254
256
  try:
255
257
  dat = [i.split('"')[1].split(',') for i in a.text.split(';\n') if ',' in i]
256
258
  dat_trim = [{k:i[j] for j,k in enumerate(head_row) if k!='_'} for i in dat]
257
259
  except Exception as e:
258
- logging.warming('data not complete, check tgt be code str or list without'+
260
+ logger.warming('data not complete, check tgt be code str or list without'+
259
261
  ' prefix, your given: {}'.format(tgts))
260
262
  return dat_trim
261
263
 
@@ -270,10 +272,10 @@ def get_stock_concepts(i) -> []:
270
272
  #drop_tails = ['板块', '概念', '0_', '成份', '重仓']
271
273
  url = f10url + i
272
274
  try:
273
- concepts = json.loads(reqget(url).text)[
275
+ concepts = json.loads(hget(url).text)[
274
276
  'hxtc'][0]['ydnr'].split()
275
277
  except Exception as e:
276
- logging.error(str(e))
278
+ logger.error(str(e))
277
279
  concepts = ['']
278
280
  #concepts = [i for i in concepts if i not in drop_cons]
279
281
  #concepts = [i for i in concepts if i[-2:] not in drop_tails]
@@ -291,7 +293,7 @@ def get_concept_stocks(bkid, dc=None):
291
293
  if a:
292
294
  return a
293
295
  bkid = bkid if isinstance(bkid, str) else 'BK' + str(bkid).zfill(4)
294
- a = reqget(
296
+ a = hget(
295
297
  base64.b64decode('aHR0cDovL3B1c2gyLmVhc3Rtb25leS5jb20vYXBpL3F0L2NsaXN0' +
296
298
  'L2dldD9jYj1qUXVlcnkxMTIzMDQwNTcwNTM4NTY5NDcwMTA1XzE2MTgwNDc5OTA2O' +
297
299
  'TAmZmlkPWY2MiZwbz0xJnB6PTUwMCZwbj0xJm5wPTEmZmx0dD0yJmludnQ9MiZmcz' +
@@ -300,7 +302,7 @@ def get_concept_stocks(bkid, dc=None):
300
302
  '&fields=f3%2Cf6%2Cf12%2Cf14%2Cf21').text
301
303
  a = json.loads(
302
304
  a.split('jQuery1123040570538569470105_1618047990690(')[1][:-2])['data']['diff']
303
- logging.debug('get fresh conc {}'.format(bkid))
305
+ logger.debug('get fresh conc {}'.format(bkid))
304
306
  a = [ ['sh'+i['f12'] if i['f12'][0]=='6' else 'sz'+i['f12'],
305
307
  i['f14'], i['f3'], i['f6'], i['f21']] for i in a]
306
308
  return a
@@ -312,7 +314,7 @@ def _east_list_fmt(burl, api_name):
312
314
  Return list of list:
313
315
  [sid, name, rise, amount, mkt]
314
316
  '''
315
- a = reqget(base64.b64decode(burl).decode() +
317
+ a = hget(base64.b64decode(burl).decode() +
316
318
  str(int(time.time()*1e3)))
317
319
  if a:
318
320
  a = a.text
@@ -334,7 +336,7 @@ def get_all_industries():
334
336
  'zQyNjI4MSZmbHR0PTImaW52dD0yJmZpZD1mMyZmcz1tOjkwK3Q6MitmOiE1MCZmaWVsZH'+
335
337
  'M9ZjMsZjYsZjEyLGYxNCxmMjAsZjEwNCxmMTA1Jl89',
336
338
  'jQuery1124037117565571971345_1627047188599')
337
- logging.debug('get industries {}'.format(len(a)))
339
+ logger.debug('get industries {}'.format(len(a)))
338
340
  return a
339
341
 
340
342
 
@@ -349,7 +351,7 @@ def get_all_concepts():
349
351
  'DI2MjgxJmZsdHQ9MiZpbnZ0PTImZmlkPWYzJmZzPW06OTArdDozK2Y6ITUwJmZpZWxkcz'+
350
352
  '1mMyxmNixmMTIsZjE0LGYyMCxmMTA0LGYxMDUmXz0='
351
353
  ,'jQuery112407329841930768979_1627109460633')
352
- logging.debug('get concepts {}'.format(len(a)))
354
+ logger.debug('get concepts {}'.format(len(a)))
353
355
  return a
354
356
 
355
357
 
@@ -365,7 +367,7 @@ def get_bk_stocks(bkid):
365
367
  bkid + '+f:!50&fields=f3,f6,f12,f14,f20&_='
366
368
  a = _east_list_fmt(base64.b64encode(bytes(url, encoding='utf-8')),
367
369
  'jQuery1124048699630095137714_1627477495064')
368
- logging.debug('get bk stocks {}'.format(len(a)))
370
+ logger.debug('get bk stocks {}'.format(len(a)))
369
371
  return a
370
372
 
371
373
 
@@ -381,7 +383,7 @@ def get_industry_stocks(bkid):
381
383
  bkid + '+f:!50&fields=f3,f6,f12,f14,f20&_='
382
384
  a = _east_list_fmt(base64.b64encode(bytes(url, encoding='utf-8')),
383
385
  'jQuery112408378200074444309_1627824603562')
384
- logging.debug('get industry stocks {}'.format(len(a)))
386
+ logger.debug('get industry stocks {}'.format(len(a)))
385
387
  return a
386
388
 
387
389
 
@@ -397,7 +399,7 @@ def get_hk_stocks_ggt():
397
399
  'DQmZmllbGRzPWYzLGY2LGYxMixmMTQsZjIwJl89',
398
400
  'jQuery1124024362308906615082_1628258931224')
399
401
  a = [ ['hk'+i[0], i[1], i[2], i[3], i[4]] for i in a]
400
- logging.debug('get hk stocks GangGuTong {}'.format(len(a)))
402
+ logger.debug('get hk stocks GangGuTong {}'.format(len(a)))
401
403
  return a
402
404
 
403
405
 
@@ -413,7 +415,7 @@ def get_hk_stocks_hsi():
413
415
  'yxmNixmMTIsZjE0LGYyMCZfPQ==',
414
416
  'jQuery112407888868459479792_1628259564671')
415
417
  a = [ ['hk'+i[0], i[1], i[2], i[3], i[4]] for i in a]
416
- logging.debug('get hk stocks HSI {}'.format(len(a)))
418
+ logger.debug('get hk stocks HSI {}'.format(len(a)))
417
419
  return a
418
420
 
419
421
 
@@ -1,16 +1,13 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- import os
4
- import time
5
- import logging
6
- import plotly.graph_objs as go
7
- from .rquote import get_price
8
- # logging.getLogger().setLevel(logging.INFO)
9
- logging.basicConfig(filename='/tmp/rquote.log',
10
- format='%(asctime)-15s:%(lineno)s %(message)s',
11
- level=logging.INFO)
3
+ from .main import get_price
4
+
5
+
12
6
  class PlotUtils:
7
+
13
8
  def plot_candle(i, sdate='', edate='', dsh=False, vol=True):
9
+
10
+ import plotly.graph_objs as go
14
11
  '''
15
12
  Plot candles of i
16
13
  Input: id
@@ -29,6 +26,7 @@ class PlotUtils:
29
26
  icolor, dcolor = 'red', 'green'
30
27
  _, n, v = get_price(i, sdate=sdate, edate=edate)
31
28
  v = (v - v.low.min()) * 10 / (v.high.max() - v.low.min()) + 2
29
+ v = v.round(3) # compress html data
32
30
 
33
31
  dt += [
34
32
  go.Candlestick(
@@ -1,43 +1,28 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- import re
4
- import os
5
3
  import time
6
- import json
7
4
  import random
8
5
  import logging
9
- import requests
6
+ import httpx
10
7
  import numpy as np
11
8
  import pandas as pd
9
+ import uuid
12
10
 
13
- logger = logging.getLogger(__name__)
14
- hdl = logging.FileHandler('/tmp/rquote.log')
15
- hdl.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
16
- logger.addHandler(hdl)
17
- logger.addHandler(logging.StreamHandler())
18
- logger.setLevel(logging.DEBUG)
19
-
20
-
21
- class CommonUtils:
22
- @staticmethod
23
- def rand_string():
24
- return ''.join([chr(random.choice(range(97, 123))) for _ in
25
- range(random.choice(range(3, 7)))])
26
-
27
- @staticmethod
28
- def yesterday_of(day):
29
- '''return 2020-12-31 if day = 2021-01-01'''
30
- return time.strftime('%Y-%m-%d', time.localtime(time.mktime(
31
- time.strptime(day, '%Y-%m-%d')) - 24 * 60 * 60))
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
32
24
 
33
- @staticmethod
34
- def sample_dates(year_earliest=2010, year_range=2):
35
- y = random.randint(year_earliest, 2021 - 2)
36
- m = str(random.randint(1, 12)).zfill(2)
37
- d = str(random.randint(1, 28)).zfill(2)
38
- date_begin = '{}-{}-{}'.format(y, m, d)
39
- date_end = '{}-{}-{}'.format(y + 2, m, d)
40
- return date_begin, date_end
25
+ logger = setup_logger()
41
26
 
42
27
 
43
28
  class WebUtils:
@@ -61,24 +46,19 @@ class WebUtils:
61
46
  @classmethod
62
47
  def headers(cls):
63
48
  header = {
64
- 'referer': CommonUtils.rand_string(),
49
+ 'referer': str(uuid.uuid4()),
65
50
  'user-agent': cls.ua()
66
51
  }
67
52
  return header
68
53
 
69
54
  @classmethod
70
- def reqget(cls, url, headers, method, proxy=None, proxy_type='http'):
55
+ def reqget(cls, url, headers, method, proxy=None):
71
56
  '''
72
57
  request.get() wrapper
73
58
  '''
74
59
  headers['user-agent'] = cls.ua
75
60
  try:
76
- if proxy is not None:
77
- r = requests.get(
78
- url, allow_redirects=True, proxies={
79
- proxy_type: proxy})
80
- else:
81
- r = requests.get(url, allow_redirects=True)
61
+ r = httpx.get(url, allow_redirects=True)
82
62
  except Exception as e:
83
63
  logger.error('Fetch url {} err: {}'.format(url, e))
84
64
  return None
@@ -89,18 +69,19 @@ class WebUtils:
89
69
  return r.content
90
70
 
91
71
  @classmethod
92
- def test_proxy(cls, proxy: str, proxy_type='http'):
72
+ def test_proxy(cls, proxy: str):
93
73
  '''
94
74
  proxy format 'ip:port'
95
75
  test baidu.com for cn
96
76
  # test google.com for non-cn (not effective due to DNS hijacking)
97
77
  '''
98
78
  try:
99
- r = requests.get(
100
- 'https://baidu.com',
101
- proxies={
102
- proxy_type: proxy},
103
- timeout=2)
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
104
85
  except Exception as e:
105
86
  logger.info(f'test proxy {proxy} negative')
106
87
  return 0
@@ -133,23 +114,6 @@ class BasicFactors:
133
114
  minres = round(minres, 2)
134
115
  return minres
135
116
 
136
- @staticmethod
137
- def ma_divergence(d) -> float:
138
- '''moving averages diverging rate'''
139
- d = d.close
140
- dlog5 = np.log(d.rolling(5).mean())
141
- dlog10 = np.log(d.rolling(10).mean())
142
- dlog20 = np.log(d.rolling(20).mean())
143
- dlog60 = np.log(d.rolling(60).mean())
144
- logmas = pd.DataFrame({'dl5': dlog5, 'dl10': dlog10, 'dl20': dlog20})
145
- mstd20 = logmas.std(1).rolling(5).mean()
146
- logmas = pd.DataFrame({'dl5': dlog5, 'dl10': dlog10,
147
- 'dl20': dlog20, 'dl60': dlog60})
148
- # mstd60=logmas.std(1).rolling(5).mean()
149
- mv = logmas.max(1) + logmas.min(1) - \
150
- logmas.max(1).shift(1) - logmas.min(1).shift(1)
151
- return round(mv[-1] / mstd20[-2], 2)
152
-
153
117
  @staticmethod
154
118
  def vol_extreme(d):
155
119
  d = d.vol
@@ -215,101 +179,19 @@ class BasicFactors:
215
179
  return ret
216
180
 
217
181
 
218
- class DataFormatter:
219
- @staticmethod
220
- def s_join_sh_close_change(d, dsh=None, sdate='', edate=''):
221
- '''
222
- join 2 DataFrames, used in single stock with sh index;
223
- keeping columns of 'close' and 'change'
224
- '''
225
- dsh['p_change'] = (dsh.close - dsh.close.shift(1)) * 100 / dsh.close.shift(1)
226
- d['p_change'] = (d.close - d.close.shift(1)) * 100 / d.close.shift(1)
227
- if not len(d) or dsh is None:
228
- return d
229
- d = d.join(dsh[['open', 'close', 'p_change']], rsuffix='_sh').sort_index()
230
- d['p_change_on_sh'] = d['p_change'] - d['p_change_sh']
231
- return d
232
-
233
- @staticmethod
234
- def sort_keys_by_cossim(df):
235
- '''
236
- Input:
237
- DataFrame with index column as keys
238
- sort by dataframe values cosine similarity
239
- '''
240
- from sklearn.metrics.pairwise import cosine_similarity
241
- keys = list(df.index)
242
- cs = cosine_similarity(df)
243
-
244
- to_sort_keys = [i for i in keys[1:]]
245
- sorted_keys = [keys[0]]
246
-
247
- cid = 0
248
- for i in range(len(keys)-1):
249
- for j in np.argsort(cs[cid])[::-1]:
250
- if keys[j] not in sorted_keys:
251
- sorted_keys.append(keys[j])
252
- to_sort_keys.remove(keys[j])
253
- cid = j
254
- break
255
- return sorted_keys
256
-
257
- @staticmethod
258
- def join_stock_concepts(nhe, nhb, dc):
259
- '''
260
- merge stock df with concept df with summerized result
261
- nhe: stock df with sid, sname
262
- nhb: concept df with sid, sname
263
- dc: dict of concept {concept: [stock]}
264
- TODO abstract it
265
- '''
266
- from collections import Counter
267
- nhb.index = nhb.sid
268
- nhe.index = nhe.sname
269
- nhe['conc'] = ''
270
- nhb['list'] = [''] * len(nhb)
271
- stks = nhe.sname.tolist() # candidates
272
- stkinconc = []
273
- for i, j in nhb.sort_values('imf2', ascending=False)[
274
- ['sid', 'sname']].iterrows():
275
- ti = []
276
- n = dc.get(j.sid)
277
- for s in n:
278
- if s in stks:
279
- stkinconc.append(s)
280
- ti.append(s)
281
- nhe.loc[s, 'conc'] = nhe.loc[s, 'conc'] + ',' + j.sname
282
- if ti:
283
- nhb.loc[j.sid,
284
- 'list'] = '{}/{}:{}'.format(len(ti),
285
- len(n),
286
- ';'.join(ti))
287
- stkr = Counter(stkinconc).most_common(200)
288
- stkr = pd.DataFrame(stkr, columns=['sname', 'concs'])
289
- nhe.index = nhe.sid
290
- nhe = nhe.merge(
291
- stkr,
292
- on='sname',
293
- how='left',
294
- suffixes=(
295
- '',
296
- '_')).fillna('')
297
- return nhe, nhb
298
-
299
-
300
- class reqget:
182
+ class hget:
301
183
  '''
302
184
  class version request.get wrapper
303
185
  '''
304
186
  def __init__(self, url, *args, **kwargs):
305
187
  self.url = url
306
188
  try:
307
- self.r = requests.get(
308
- self.url, allow_redirects=True, *args, **kwargs)
309
- self.text = self.r.text
310
- self.content = self.r.content
311
- except BaseException:
312
- logger.error(f'fetch {self.url} err')
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}')
313
195
  self.text = ''
314
196
  self.content = b''
315
197
 
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.4
2
+ Name: rquote
3
+ Version: 0.2.5
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
+ Dynamic: author
13
+ Dynamic: author-email
14
+ Dynamic: home-page
15
+ Dynamic: requires-python
16
+
17
+ # rquote
18
+
19
+ `get_price`方法提供 A股/港股/美股/ETF基金/期货 日线(及其他周期)历史数据获取
20
+
21
+ 使用新浪/腾讯的id形式,如`sh000001`表示上证指数,`sz000001`表示深市000001股票`平安银行`,`sh510050`表示上证50指数ETF,`hk00700`表示港股腾讯。
22
+ 期货代码需加`fu`前缀,如`fuAP2110`,美股需加对应交易所后缀,如`usBABA.N`,`usC.N`,`usAAPL.OQ`等
23
+ BTC/USD放在fu下面,i.e.`fuBTC`
24
+
25
+ e.g.
26
+ ```
27
+ from rquote import get_price
28
+ sid, nm, df = get_price('sh000001')
29
+ df.head() # 数据为pandas dataframe
30
+ ```
31
+ > open close high low vol
32
+ > date
33
+ > 2024-02-06 2680.48 2789.49 2802.93 2669.67 502849313.0
34
+ > 2024-02-07 2791.51 2829.70 2829.70 2770.53 547117439.0
35
+ > 2024-02-08 2832.49 2865.90 2867.47 2827.90 531108893.0
36
+ > 2024-02-19 2886.59 2910.54 2910.54 2867.71 458967704.0
37
+ > 2024-02-20 2902.88 2922.73 2927.31 2887.47 350138735.0
38
+
39
+ Notes:
40
+ 使用GenImage需要补充依赖
41
+ > kaleido>=1.0.0rc0
42
+ > plolty>=5.0.0
@@ -1,10 +1,9 @@
1
- MANIFEST.in
2
1
  README.md
3
- requirements.txt
2
+ pyproject.toml
4
3
  setup.py
5
4
  rquote/__init__.py
5
+ rquote/main.py
6
6
  rquote/plots.py
7
- rquote/rquote.py
8
7
  rquote/utils.py
9
8
  rquote.egg-info/PKG-INFO
10
9
  rquote.egg-info/SOURCES.txt
@@ -0,0 +1,2 @@
1
+ httpx>=0.20.0
2
+ pandas>=1.0.0
@@ -1,8 +1,6 @@
1
1
  from os import path as os_path
2
2
  from setuptools import setup
3
3
 
4
- import rquote
5
-
6
4
  this_directory = os_path.abspath(os_path.dirname(__file__))
7
5
 
8
6
  def read_file(filename):
@@ -17,7 +15,7 @@ def read_requirements(filename):
17
15
  setup(
18
16
  name='rquote',
19
17
  python_requires='>=3.4.0',
20
- version='0.2.2',
18
+ version='0.2.3',
21
19
  description='Mostly day quotes of cn/hk/us/fund/future markets, side with quote list fetch',
22
20
  long_description=read_file('README.md'),
23
21
  long_description_content_type="text/markdown",
@@ -27,7 +25,9 @@ setup(
27
25
  packages=[
28
26
  'rquote'
29
27
  ],
30
- install_requires=read_requirements('requirements.txt'),
28
+ install_requires=[
29
+ "httpx>=0.20.0",
30
+ "pandas>=1.0.0"],
31
31
  include_package_data=True,
32
32
  license="MIT",
33
33
  keywords=['quotes', 'stock', 'rquote'],
@@ -40,5 +40,8 @@ setup(
40
40
  'Programming Language :: Python :: 3.6',
41
41
  'Programming Language :: Python :: 3.7',
42
42
  'Programming Language :: Python :: 3.8',
43
+ 'Programming Language :: Python :: 3.9',
44
+ 'Programming Language :: Python :: 3.10',
45
+ 'Programming Language :: Python :: 3.11',
43
46
  ],
44
47
  )
rquote-0.2.2/MANIFEST.in DELETED
@@ -1 +0,0 @@
1
- include requirements.txt
rquote-0.2.2/PKG-INFO DELETED
@@ -1,28 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: rquote
3
- Version: 0.2.2
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
- License: MIT
9
- Description: # rquote
10
-
11
- `get_price`方法提供 A股/港股/美股/ETF基金/期货 日线历史数据获取
12
-
13
- 使用新浪/腾讯的id形式,如`sh000001`表示上证指数,`sz000001`表示深市000001股票`平安银行`,`sh510050`表示上证50指数ETF,`hk00700`表示港股腾讯。
14
- 期货代码需加`fu`前缀,如`fuAP2110`,美股需加对应交易所后缀,如`usBABA.N `,`usC.N` ,`usAAPL.OQ`等
15
-
16
-
17
- Keywords: quotes,stock,rquote
18
- Platform: UNKNOWN
19
- Classifier: Intended Audience :: Developers
20
- Classifier: License :: OSI Approved :: MIT License
21
- Classifier: Natural Language :: English
22
- Classifier: Programming Language :: Python :: 3.4
23
- Classifier: Programming Language :: Python :: 3.5
24
- Classifier: Programming Language :: Python :: 3.6
25
- Classifier: Programming Language :: Python :: 3.7
26
- Classifier: Programming Language :: Python :: 3.8
27
- Requires-Python: >=3.4.0
28
- Description-Content-Type: text/markdown
rquote-0.2.2/README.md DELETED
@@ -1,7 +0,0 @@
1
- # rquote
2
-
3
- `get_price`方法提供 A股/港股/美股/ETF基金/期货 日线历史数据获取
4
-
5
- 使用新浪/腾讯的id形式,如`sh000001`表示上证指数,`sz000001`表示深市000001股票`平安银行`,`sh510050`表示上证50指数ETF,`hk00700`表示港股腾讯。
6
- 期货代码需加`fu`前缀,如`fuAP2110`,美股需加对应交易所后缀,如`usBABA.N `,`usC.N` ,`usAAPL.OQ`等
7
-
@@ -1,2 +0,0 @@
1
- requests
2
- pandas
@@ -1,13 +0,0 @@
1
- '''
2
- rquote
3
-
4
- A stock history data api
5
-
6
- Copyright (c) 2021 Roi ZHAO
7
-
8
- '''
9
-
10
- from .rquote import get_price, get_stock_concepts, get_concept_stocks
11
- from .rquote import get_all_concepts, get_all_industries
12
- from .utils import CommonUtils, WebUtils, BasicFactors, DataFormatter, reqget
13
- from .plots import PlotUtils
@@ -1,28 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: rquote
3
- Version: 0.2.2
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
- License: MIT
9
- Description: # rquote
10
-
11
- `get_price`方法提供 A股/港股/美股/ETF基金/期货 日线历史数据获取
12
-
13
- 使用新浪/腾讯的id形式,如`sh000001`表示上证指数,`sz000001`表示深市000001股票`平安银行`,`sh510050`表示上证50指数ETF,`hk00700`表示港股腾讯。
14
- 期货代码需加`fu`前缀,如`fuAP2110`,美股需加对应交易所后缀,如`usBABA.N `,`usC.N` ,`usAAPL.OQ`等
15
-
16
-
17
- Keywords: quotes,stock,rquote
18
- Platform: UNKNOWN
19
- Classifier: Intended Audience :: Developers
20
- Classifier: License :: OSI Approved :: MIT License
21
- Classifier: Natural Language :: English
22
- Classifier: Programming Language :: Python :: 3.4
23
- Classifier: Programming Language :: Python :: 3.5
24
- Classifier: Programming Language :: Python :: 3.6
25
- Classifier: Programming Language :: Python :: 3.7
26
- Classifier: Programming Language :: Python :: 3.8
27
- Requires-Python: >=3.4.0
28
- Description-Content-Type: text/markdown
@@ -1,2 +0,0 @@
1
- requests
2
- pandas
File without changes