rquote 0.3.3__py3-none-any.whl → 0.3.5__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 +53 -5
- rquote/api/__init__.py +32 -0
- rquote/api/lists.py +175 -0
- rquote/api/price.py +73 -0
- rquote/api/stock_info.py +49 -0
- rquote/api/tick.py +47 -0
- rquote/cache/__init__.py +9 -0
- rquote/cache/base.py +26 -0
- rquote/cache/memory.py +77 -0
- rquote/config.py +42 -0
- rquote/data_sources/__init__.py +10 -0
- rquote/data_sources/base.py +21 -0
- rquote/data_sources/sina.py +92 -0
- rquote/data_sources/tencent.py +90 -0
- rquote/exceptions.py +40 -0
- rquote/factors/__init__.py +8 -0
- rquote/factors/technical.py +150 -0
- rquote/markets/__init__.py +14 -0
- rquote/markets/base.py +49 -0
- rquote/markets/cn_stock.py +186 -0
- rquote/markets/factory.py +82 -0
- rquote/markets/future.py +92 -0
- rquote/markets/hk_stock.py +46 -0
- rquote/markets/us_stock.py +69 -0
- rquote/parsers/__init__.py +8 -0
- rquote/parsers/kline.py +104 -0
- rquote/plots.py +1 -1
- rquote/utils/__init__.py +13 -0
- rquote/utils/date.py +40 -0
- rquote/utils/helpers.py +23 -0
- rquote/utils/http.py +112 -0
- rquote/utils/logging.py +25 -0
- rquote/utils/web.py +104 -0
- rquote/utils.py +9 -195
- rquote-0.3.5.dist-info/METADATA +486 -0
- rquote-0.3.5.dist-info/RECORD +38 -0
- rquote/main.py +0 -497
- rquote-0.3.3.dist-info/METADATA +0 -286
- rquote-0.3.3.dist-info/RECORD +0 -8
- {rquote-0.3.3.dist-info → rquote-0.3.5.dist-info}/WHEEL +0 -0
- {rquote-0.3.3.dist-info → rquote-0.3.5.dist-info}/top_level.txt +0 -0
rquote/utils/web.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Web工具
|
|
4
|
+
"""
|
|
5
|
+
import random
|
|
6
|
+
import uuid
|
|
7
|
+
import httpx
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WebUtils:
|
|
14
|
+
"""Web工具类"""
|
|
15
|
+
|
|
16
|
+
UA_LIST = [
|
|
17
|
+
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101',
|
|
18
|
+
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122',
|
|
19
|
+
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71',
|
|
20
|
+
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95',
|
|
21
|
+
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71',
|
|
22
|
+
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)',
|
|
23
|
+
'Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50',
|
|
24
|
+
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0',
|
|
25
|
+
'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',
|
|
26
|
+
'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',
|
|
27
|
+
'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'
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def ua():
|
|
32
|
+
"""获取随机User-Agent"""
|
|
33
|
+
return random.choice(WebUtils.UA_LIST)
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def headers(cls):
|
|
37
|
+
"""获取请求头"""
|
|
38
|
+
return {
|
|
39
|
+
'referer': str(uuid.uuid4()),
|
|
40
|
+
'user-agent': cls.ua()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def http_get(cls, url, headers, method, proxy=None):
|
|
45
|
+
"""
|
|
46
|
+
HTTP GET请求(已废弃,建议使用HTTPClient)
|
|
47
|
+
"""
|
|
48
|
+
headers['user-agent'] = cls.ua()
|
|
49
|
+
try:
|
|
50
|
+
r = httpx.get(url, allow_redirects=True, headers=headers)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
logger.error('Fetch url {} err: {}'.format(url, e))
|
|
53
|
+
return None
|
|
54
|
+
if r:
|
|
55
|
+
if method == 'text':
|
|
56
|
+
return r.text
|
|
57
|
+
elif method == 'content':
|
|
58
|
+
return r.content
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def test_proxy(cls, proxy: str):
|
|
62
|
+
"""
|
|
63
|
+
测试代理是否可用
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
proxy: 代理地址,格式 'ip:port'
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
1表示可用,0表示不可用
|
|
70
|
+
"""
|
|
71
|
+
try:
|
|
72
|
+
proxies = {'http://': proxy, 'https://': proxy} if '://' not in proxy else proxy
|
|
73
|
+
with httpx.Client(proxies=proxies, timeout=2) as client:
|
|
74
|
+
r = client.get('https://baidu.com', timeout=2)
|
|
75
|
+
if r.status_code == 200:
|
|
76
|
+
logger.info(f'test proxy {proxy} positive')
|
|
77
|
+
return 1
|
|
78
|
+
else:
|
|
79
|
+
logger.info(f'test proxy {proxy} negative (status: {r.status_code})')
|
|
80
|
+
return 0
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.info(f'test proxy {proxy} negative: {e}')
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class hget:
|
|
87
|
+
"""
|
|
88
|
+
HTTP GET请求类(向后兼容)
|
|
89
|
+
|
|
90
|
+
注意:建议使用HTTPClient替代
|
|
91
|
+
"""
|
|
92
|
+
def __init__(self, url, *args, **kwargs):
|
|
93
|
+
self.url = url
|
|
94
|
+
try:
|
|
95
|
+
r = httpx.get(
|
|
96
|
+
self.url, follow_redirects=True, headers=WebUtils.headers(),
|
|
97
|
+
*args, **kwargs)
|
|
98
|
+
self.text = r.text
|
|
99
|
+
self.content = r.content
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.error(f'fetch {self.url} err: {e}')
|
|
102
|
+
self.text = ''
|
|
103
|
+
self.content = b''
|
|
104
|
+
|
rquote/utils.py
CHANGED
|
@@ -1,197 +1,11 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
logger = logging.getLogger('rquote')
|
|
12
|
-
if not logger.handlers:
|
|
13
|
-
logger.setLevel(logging.INFO)
|
|
14
|
-
file_handler = logging.FileHandler('/tmp/rquote.log')
|
|
15
|
-
|
|
16
|
-
formatter = logging.Formatter('%(asctime)-15s:%(lineno)s %(message)s')
|
|
17
|
-
file_handler.setFormatter(formatter)
|
|
18
|
-
|
|
19
|
-
logger.addHandler(file_handler)
|
|
20
|
-
logger.addHandler(logging.StreamHandler())
|
|
21
|
-
|
|
22
|
-
return logger
|
|
23
|
-
|
|
24
|
-
logger = setup_logger()
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class WebUtils:
|
|
28
|
-
@staticmethod
|
|
29
|
-
def ua():
|
|
30
|
-
ua_list = [
|
|
31
|
-
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101',
|
|
32
|
-
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122',
|
|
33
|
-
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71',
|
|
34
|
-
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95',
|
|
35
|
-
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71',
|
|
36
|
-
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)',
|
|
37
|
-
'Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50',
|
|
38
|
-
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0',
|
|
39
|
-
'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',
|
|
40
|
-
'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',
|
|
41
|
-
'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'
|
|
42
|
-
]
|
|
43
|
-
return random.choice(ua_list)
|
|
44
|
-
|
|
45
|
-
@classmethod
|
|
46
|
-
def headers(cls):
|
|
47
|
-
header = {
|
|
48
|
-
'referer': str(uuid.uuid4()),
|
|
49
|
-
'user-agent': cls.ua()
|
|
50
|
-
}
|
|
51
|
-
return header
|
|
52
|
-
|
|
53
|
-
@classmethod
|
|
54
|
-
def http_get(cls, url, headers, method, proxy=None):
|
|
55
|
-
'''
|
|
56
|
-
request.get() wrapper
|
|
57
|
-
'''
|
|
58
|
-
headers['user-agent'] = cls.ua
|
|
59
|
-
try:
|
|
60
|
-
r = httpx.get(url, allow_redirects=True)
|
|
61
|
-
except Exception as e:
|
|
62
|
-
logger.error('Fetch url {} err: {}'.format(url, e))
|
|
63
|
-
return None
|
|
64
|
-
if r:
|
|
65
|
-
if method == 'text':
|
|
66
|
-
return r.text
|
|
67
|
-
elif method == 'content':
|
|
68
|
-
return r.content
|
|
69
|
-
|
|
70
|
-
@classmethod
|
|
71
|
-
def test_proxy(cls, proxy: str):
|
|
72
|
-
'''
|
|
73
|
-
proxy format 'ip:port'
|
|
74
|
-
test baidu.com for cn
|
|
75
|
-
# test google.com for non-cn (not effective due to DNS hijacking)
|
|
76
|
-
'''
|
|
77
|
-
try:
|
|
78
|
-
with httpx.Client(proxies=proxy) as client:
|
|
79
|
-
r = client.get('https://baidu.com', timeout=2)
|
|
80
|
-
if r.ok:
|
|
81
|
-
return 1
|
|
82
|
-
else:
|
|
83
|
-
return 0
|
|
84
|
-
except Exception as e:
|
|
85
|
-
logger.info(f'test proxy {proxy} negative')
|
|
86
|
-
return 0
|
|
87
|
-
if r.ok:
|
|
88
|
-
logger.info(f'test proxy {proxy} positive')
|
|
89
|
-
return 1
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class BasicFactors:
|
|
93
|
-
|
|
94
|
-
@staticmethod
|
|
95
|
-
def break_rise(d) -> float:
|
|
96
|
-
if d.open[-1] / d.close[-2] > 1.002 and d.close[-1] > d.open[-1]:
|
|
97
|
-
return round((d.open[-1] - d.close[-2]) / d.close[-2], 2)
|
|
98
|
-
else:
|
|
99
|
-
return 0
|
|
100
|
-
|
|
101
|
-
@staticmethod
|
|
102
|
-
def min_resist(d) -> float:
|
|
103
|
-
sup, pre, pcur = 0, 0, d.close[-1]
|
|
104
|
-
for i in d.iterrows():
|
|
105
|
-
p = (i[1].open + i[1].close) / 2
|
|
106
|
-
if p > pcur:
|
|
107
|
-
pre += i[1].vol
|
|
108
|
-
if p < pcur:
|
|
109
|
-
sup += i[1].vol
|
|
110
|
-
minres = (sup - pre) / (sup + pre)
|
|
111
|
-
if abs(minres - 1) < .01 and d.close[-2] < max(d.close[:-2]):
|
|
112
|
-
minres += .2
|
|
113
|
-
minres = round(minres, 2)
|
|
114
|
-
return minres
|
|
115
|
-
|
|
116
|
-
@staticmethod
|
|
117
|
-
def vol_extreme(d):
|
|
118
|
-
d = d.vol
|
|
119
|
-
v60max = d.rolling(60).max()
|
|
120
|
-
v60min = d.rolling(60).min()
|
|
121
|
-
# any in last 3days
|
|
122
|
-
for i in range(1, 3):
|
|
123
|
-
if d[-i] > v60max[-i - 1]:
|
|
124
|
-
return round(d[-i] / v60max[-i - 1], 2)
|
|
125
|
-
if d[-i] < v60min[-i - 1]:
|
|
126
|
-
return round(-d[-i] / v60min[-i - 1], 2)
|
|
127
|
-
else:
|
|
128
|
-
return 0
|
|
129
|
-
|
|
130
|
-
@staticmethod
|
|
131
|
-
def bias_rate_over_ma60(d):
|
|
132
|
-
r60 = d.close - d.close.rolling(60).mean()
|
|
133
|
-
if r60[-1] > 0:
|
|
134
|
-
return round(r60[-1] / r60.rolling(60).max()[-1], 2)
|
|
135
|
-
else:
|
|
136
|
-
return round(-r60[-1] / r60.rolling(60).min()[-1], 2)
|
|
137
|
-
|
|
138
|
-
@staticmethod
|
|
139
|
-
def op_ma(d) -> float:
|
|
140
|
-
''' op: ma score'''
|
|
141
|
-
if len(d) < 22:
|
|
142
|
-
return
|
|
143
|
-
d['mv5'] = d.close.rolling(5).mean()
|
|
144
|
-
d['mv10'] = d.close.rolling(10).mean()
|
|
145
|
-
d['mv20'] = d.close.rolling(20).mean()
|
|
146
|
-
d['mv60'] = d.close.rolling(60).mean()
|
|
147
|
-
|
|
148
|
-
def ma20(d):
|
|
149
|
-
ret = 0
|
|
150
|
-
# .2 for over ma60
|
|
151
|
-
if d.close[-1] > d.mv60[-1]:
|
|
152
|
-
ret += 0.2
|
|
153
|
-
# .2 for all upwards ma's
|
|
154
|
-
if (d.mv5[-1] > d.mv5[-2] and d.mv10[-1] >
|
|
155
|
-
d.mv10[-2] and d.mv20[-1] > d.mv20[-2]):
|
|
156
|
-
ret += 0.2
|
|
157
|
-
for j in range(1, 3):
|
|
158
|
-
if not (d.close[-j] > d.mv5[-j] and d.close[-j]
|
|
159
|
-
> d.mv10[-j] and d.close[-j] > d.mv20[-j]):
|
|
160
|
-
return ret
|
|
161
|
-
for j in range(3, 5):
|
|
162
|
-
if (d.close[-j] > d.mv5[-j] and d.close[-j] >
|
|
163
|
-
d.mv10[-j] and d.close[-j] > d.mv20[-j]):
|
|
164
|
-
return ret
|
|
165
|
-
# .2 for just rush over ma's (fresh score)
|
|
166
|
-
ret += 0.2
|
|
167
|
-
return ret
|
|
168
|
-
return ma20(d)
|
|
169
|
-
|
|
170
|
-
@staticmethod
|
|
171
|
-
def op_cnt(d, cont_min=3) -> (int):
|
|
172
|
-
''' op: count continous bulling days over index'''
|
|
173
|
-
d.index = pd.DatetimeIndex(d.index)
|
|
174
|
-
td = (d.p_change_on_sh.rolling(cont_min).min() > 0).astype(int) * \
|
|
175
|
-
(d.p_change.rolling(cont_min).min() > 0).astype(int)
|
|
176
|
-
ret = 0 if td[-1] <= 0 else td[-1]
|
|
177
|
-
# is_first_day = True if td[-2] <= 0 else False
|
|
178
|
-
return ret
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
class hget:
|
|
182
|
-
'''
|
|
183
|
-
class version request.get wrapper
|
|
184
|
-
'''
|
|
185
|
-
def __init__(self, url, *args, **kwargs):
|
|
186
|
-
self.url = url
|
|
187
|
-
try:
|
|
188
|
-
r = httpx.get(
|
|
189
|
-
self.url, follow_redirects=True, headers=WebUtils.headers(),
|
|
190
|
-
*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''
|
|
2
|
+
"""
|
|
3
|
+
工具模块(向后兼容)
|
|
4
|
+
"""
|
|
5
|
+
# 从新模块导入,保持向后兼容
|
|
6
|
+
from .utils.logging import logger, setup_logger
|
|
7
|
+
from .utils.web import WebUtils, hget
|
|
8
|
+
from .factors.technical import BasicFactors
|
|
9
|
+
|
|
10
|
+
__all__ = ['WebUtils', 'BasicFactors', 'hget', 'logger', 'setup_logger']
|
|
197
11
|
|