rquote 0.2.3__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 +42 -0
- rquote-0.2.5/README.md +26 -0
- rquote-0.2.5/pyproject.toml +14 -0
- rquote-0.2.5/rquote/__init__.py +13 -0
- rquote-0.2.3/rquote/rquote.py → rquote-0.2.5/rquote/main.py +44 -42
- {rquote-0.2.3 → rquote-0.2.5}/rquote/plots.py +6 -9
- {rquote-0.2.3 → rquote-0.2.5}/rquote/utils.py +33 -151
- rquote-0.2.5/rquote.egg-info/PKG-INFO +42 -0
- {rquote-0.2.3 → rquote-0.2.5}/rquote.egg-info/SOURCES.txt +2 -3
- rquote-0.2.5/rquote.egg-info/requires.txt +2 -0
- {rquote-0.2.3 → rquote-0.2.5}/setup.py +6 -3
- rquote-0.2.3/MANIFEST.in +0 -1
- rquote-0.2.3/PKG-INFO +0 -28
- rquote-0.2.3/README.md +0 -7
- rquote-0.2.3/requirements.txt +0 -2
- rquote-0.2.3/rquote/__init__.py +0 -13
- rquote-0.2.3/rquote.egg-info/PKG-INFO +0 -28
- rquote-0.2.3/rquote.egg-info/requires.txt +0 -2
- {rquote-0.2.3 → rquote-0.2.5}/rquote.egg-info/dependency_links.txt +0 -0
- {rquote-0.2.3 → rquote-0.2.5}/rquote.egg-info/top_level.txt +0 -0
- {rquote-0.2.3 → rquote-0.2.5}/setup.cfg +0 -0
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,
|
|
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 =
|
|
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 =
|
|
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,7 +58,7 @@ 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 =
|
|
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
|
|
@@ -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 =
|
|
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 =
|
|
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
|
-
|
|
119
|
+
logger.debug('loading price from dd {}'.format(i))
|
|
129
120
|
return i, n, d
|
|
130
|
-
|
|
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,11 +127,14 @@ 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 =
|
|
137
|
+
a = hget(base64.b64decode('aHR0cDovL3B1c2gyaGlzLmVhc3' +
|
|
144
138
|
'Rtb25leS5jb20vYXBpL3F0L3N0b2NrL2tsaW5lL2dldD9jYj1qUX' +
|
|
145
139
|
'VlcnkxMTI0MDIyNTY2NDQ1ODczNzY2OTcyXzE2MTc4NjQ1NjgxMz' +
|
|
146
140
|
'Emc2VjaWQ9OTAu').decode() + i +
|
|
@@ -149,12 +143,12 @@ def get_price(i, sdate='', edate='', freq='day', days=320, fq='qfq',
|
|
|
149
143
|
'&klt=101&fqt=0&beg=19900101&end=20990101&_=1',
|
|
150
144
|
headers=WebUtils.headers())
|
|
151
145
|
if not a:
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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,
|
|
178
|
+
return i, ix, d
|
|
178
179
|
except Exception as e:
|
|
179
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
251
|
+
a = hget(sina_tick + ','.join(tgts))
|
|
250
252
|
if not a:
|
|
251
|
-
|
|
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
|
-
|
|
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(
|
|
275
|
+
concepts = json.loads(hget(url).text)[
|
|
274
276
|
'hxtc'][0]['ydnr'].split()
|
|
275
277
|
except Exception as e:
|
|
276
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
@@ -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
|
|
6
|
+
import httpx
|
|
10
7
|
import numpy as np
|
|
11
8
|
import pandas as pd
|
|
9
|
+
import uuid
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
logger.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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':
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
100
|
-
'https://baidu.com',
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
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
|
-
|
|
308
|
-
self.url,
|
|
309
|
-
self.text =
|
|
310
|
-
self.content =
|
|
311
|
-
except
|
|
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,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):
|
|
@@ -27,7 +25,9 @@ setup(
|
|
|
27
25
|
packages=[
|
|
28
26
|
'rquote'
|
|
29
27
|
],
|
|
30
|
-
install_requires=
|
|
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.3/MANIFEST.in
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
include requirements.txt
|
rquote-0.2.3/PKG-INFO
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: rquote
|
|
3
|
-
Version: 0.2.3
|
|
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.3/README.md
DELETED
rquote-0.2.3/requirements.txt
DELETED
rquote-0.2.3/rquote/__init__.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
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 .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.3
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|