1
+ import requests
2
+ import time
3
+ import datetime
4
+ import json
5
+ import pandas as pd
6
+ import numpy as np
7
+ from apscheduler .schedulers .background import BackgroundScheduler
8
+ from apscheduler .schedulers .blocking import BlockingScheduler
9
+
10
+ # 订阅的标的列表
11
+ stock_list = []
12
+ session = None
13
+ cookies = None
14
+ headers = {
15
+ 'Accept' :'*/*' ,
16
+ 'Origin' :'https://xueqiu.com' ,
17
+ 'Referer' :'https://xueqiu.com/S/SH600519' ,
18
+ 'Sec-Fetch-Mode' :'cors' ,
19
+ 'User-Agent' :'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
20
+ }
21
+
22
+ NAN_DT = datetime .datetime (2200 , 1 , 1 )
23
+
24
+ class _Global (object ):
25
+
26
+ def __init__ (self , ** kwargs ):
27
+
28
+ for name , value in kwargs .items ():
29
+ setattr (self , name , value )
30
+
31
+ # 通用对象转化
32
+ class _Context (object ):
33
+
34
+ def __init__ (self , ** kwargs ):
35
+
36
+ for name , value in kwargs .items ():
37
+ setattr (self , name , value )
38
+
39
+ class Tick (object ):
40
+ def __init__ (self , security , tick ):
41
+ self ._security = parse_xq_code (security )
42
+ self ._tick = tick
43
+
44
+ @property
45
+ def code (self ):
46
+ return self ._security
47
+
48
+ @property
49
+ def time (self ):
50
+ try :
51
+ return self ._tick ['time' ]
52
+ except :
53
+ return NAN_DT
54
+
55
+ @property
56
+ def current (self ):
57
+ try :
58
+ return self ._tick ['current' ]
59
+ except :
60
+ return np .nan
61
+
62
+ @property
63
+ def high (self ):
64
+ try :
65
+ return self ._tick ['high' ]
66
+ except :
67
+ return np .nan
68
+
69
+ @property
70
+ def low (self ):
71
+ try :
72
+ return self ._tick ['low' ]
73
+ except :
74
+ return np .nan
75
+
76
+ @property
77
+ def trade_volume (self ):
78
+ try :
79
+ return self ._tick ['trade_volume' ]
80
+ except :
81
+ return np .nan
82
+
83
+ @property
84
+ def volume (self ):
85
+ try :
86
+ return self ._tick ['volume' ]
87
+ except :
88
+ return np .nan
89
+
90
+ @property
91
+ def money (self ):
92
+ try :
93
+ return self ._tick ['money' ]
94
+ except :
95
+ return np .nan
96
+
97
+ # 通用对象转化
98
+ class CurrentDict (object ):
99
+
100
+ def __init__ (self , ** kwargs ):
101
+
102
+ for name , value in kwargs .items ():
103
+ setattr (self , name , value )
104
+
105
+ # 当前行情对象
106
+ class _CurrentDic (dict ):
107
+
108
+ def __init__ (self , date ):
109
+ pass
110
+
111
+ def __missing__ (self , code ):
112
+ info = _global ['session' ].get ('https://stock.xueqiu.com/v5/stock/quote.json?extend=detail&symbol=' + parse_code (code ), cookies = _global ['cookies' ], headers = headers ).json ()
113
+ quote = info ['data' ]['quote' ]
114
+ stock = quote ['symbol' ]
115
+ result = {
116
+ 'name' : quote ['name' ],
117
+ 'time' : time .strftime ("%Y-%m-%d %H:%M:%S" , time .localtime (quote ['timestamp' ] / 1000 )),
118
+ 'current' : quote ['current' ],
119
+ 'high' : quote ['high' ],
120
+ 'low' : quote ['low' ],
121
+ 'volume' : quote ['volume' ],
122
+ 'money' : quote ['amount' ],
123
+ 'day_open' : quote ['open' ],
124
+ 'high_limit' : quote ['limit_up' ],
125
+ 'low_limit' : quote ['limit_down' ],
126
+ 'industry_code' : quote ['type' ],
127
+ 'is_st' : quote ['status' ] == 2
128
+ }
129
+
130
+ return parse (result )
131
+
132
+ # 初始化实时行情模块
133
+ # 主要工作是 初始化爬虫cookie 初始化tick定时器 完善全局对象
134
+ def init_current_bundle (initialize , before_trading_start , after_trading_end , handle_tick ):
135
+ _global ['initialize' ] = initialize
136
+ _global ['before_trading_start' ] = before_trading_start
137
+ _global ['after_trading_end' ] = after_trading_end
138
+ _global ['handle_tick' ] = handle_tick
139
+ cookies = get_cookie ()
140
+
141
+ # 执行初始化函数
142
+ initialize (_global ['context' ])
143
+
144
+ # 初始化tick定时器
145
+ init_schedudler ()
146
+
147
+ # 创建定时器
148
+ # 完成开盘 收盘 盘中3秒批量查询一次最新tick数据 等默认事件
149
+ def init_schedudler ():
150
+ schedudler = BlockingScheduler ()
151
+ schedudler .add_job (func = _global ['before_trading_start' ], args = [_global ['context' ]], trigger = 'cron' , hour = 9 , minute = 9 , day_of_week = 'mon-fri' )
152
+ schedudler .add_job (func = _global ['after_trading_end' ], args = [_global ['context' ]], trigger = 'cron' , hour = 15 , minute = 30 , day_of_week = 'mon-fri' )
153
+ schedudler .add_job (_get_current_tick , 'cron' , second = '*/3' )
154
+ schedudler .start ()
155
+
156
+ def get_cookie ():
157
+ cookies = requests .cookies .RequestsCookieJar ()
158
+ _global ['session' ] = requests .session ()
159
+ r = _global ['session' ].get ('https://xueqiu.com/k?q=SZ131810' , headers = headers )
160
+ _global ['cookies' ] = r .cookies
161
+ return cookies
162
+
163
+ # 抓取当日历史tick数据
164
+ def get_ticks (security , end_dt , count , start_dt = None ):
165
+ print ('### 自定义 get_ticks ###' )
166
+ result = []
167
+
168
+ # 大于100取东方财富的 稍后实现
169
+ if count > 100 :
170
+ pass
171
+ else :
172
+ info = _global ['session' ].get ('https://stock.xueqiu.com/v5/stock/history/trade.json?symbol=' + parse_code (security ) + '&count=' + str (count ), cookies = _global ['cookies' ], headers = headers ).json ()
173
+ ticks = info ['data' ]['items' ]
174
+
175
+ for tick in ticks :
176
+ result .append ({
177
+ 'time' : time .strftime ("%Y-%m-%d %H:%M:%S" , time .localtime (tick ['timestamp' ] / 1000 )),
178
+ 'current' : tick ['current' ],
179
+ 'trade_volume' : tick ['trade_volume' ],
180
+ })
181
+
182
+ return result
183
+
184
+ # 获取最新tick数据
185
+ def get_current_tick (stock , df = False ):
186
+ info = _global ['session' ].get ('https://stock.xueqiu.com/v5/stock/realtime/quotec.json?symbol=' + parse_code (stock ), cookies = _global ['cookies' ], headers = headers ).json ()
187
+ quote = info ['data' ][0 ]
188
+ stock = quote ['symbol' ]
189
+ result = {
190
+ 'time' : time .strftime ("%Y-%m-%d %H:%M:%S" , time .localtime (quote ['timestamp' ] / 1000 )),
191
+ 'current' : quote ['current' ],
192
+ 'high' : quote ['high' ],
193
+ 'low' : quote ['low' ],
194
+ 'trade_volume' : quote ['trade_volume' ],
195
+ 'volume' : quote ['volume' ],
196
+ 'money' : quote ['amount' ]
197
+ }
198
+
199
+ return Tick (stock , result )
200
+
201
+ # 获取当日最新数据 包含涨停跌停等
202
+ def get_current_data ():
203
+ print ('### 自定义 get_current_data ###' )
204
+ current = _CurrentDic ({})
205
+ return current
206
+
207
+ # 获取最新tick数据
208
+ def _get_current_tick ():
209
+ stocks = []
210
+
211
+ if len (stock_list ):
212
+
213
+ for stock in stock_list :
214
+ stocks .append (parse_code (stock ))
215
+
216
+ info = _global ['session' ].get ('https://stock.xueqiu.com/v5/stock/realtime/quotec.json?symbol=' + ',' .join (stocks ), cookies = _global ['cookies' ], headers = headers ).json ()
217
+ quotes = info ['data' ]
218
+
219
+ for quote in quotes :
220
+ stock = quote ['symbol' ]
221
+ result = {
222
+ 'time' : time .strftime ("%Y-%m-%d %H:%M:%S" , time .localtime (quote ['timestamp' ] / 1000 )),
223
+ 'current' : quote ['current' ],
224
+ 'high' : quote ['high' ],
225
+ 'low' : quote ['low' ],
226
+ 'trade_volume' : quote ['trade_volume' ],
227
+ 'volume' : quote ['volume' ],
228
+ 'money' : quote ['amount' ]
229
+ }
230
+ _global ['handle_tick' ](_global ['context' ], Tick (stock , result ))
231
+
232
+ def parse_code (code ):
233
+
234
+ if code .endswith ('XSHE' ):
235
+ return 'SZ' + code .split ('.' )[0 ]
236
+ elif code .endswith ('XSHG' ):
237
+ return 'SH' + code .split ('.' )[0 ]
238
+
239
+ def parse_xq_code (code ):
240
+
241
+ if code .startswith ('SZ' ):
242
+ return code [2 :8 ] + '.XSHE'
243
+ elif code .startswith ('SH' ):
244
+ return code [2 :8 ] + '.XSHG'
245
+
246
+ # 要暴露的函数
247
+ def subscribe (security , frequency ):
248
+ print ('### 自定义 subscribe ###' )
249
+
250
+ # 加入队列
251
+ stock_list .append (security )
252
+ print ('添加标的到队列 => ' , security )
253
+ # print('当前订阅的标的队列 => ', stock_list)
254
+
255
+ # 取消订阅标的的 tick 事件
256
+ def unsubcribe (security , frequency ):
257
+ print ('### 自定义 unsubcribe ###' )
258
+
259
+ if security in stock_list :
260
+ stock_list .remove (security )
261
+
262
+ # 取消订阅所有 tick 事件
263
+ def unsubscribe_all ():
264
+ print ('### 自定义 unsubscribe_all ###' )
265
+ stock_list = []
266
+
267
+ # 定时执行任务
268
+ def run_daily (event , time ):
269
+ _time = time .split (':' )
270
+ hour = int (_time [0 ])
271
+ minute = int (_time [1 ])
272
+ schedudler = BackgroundScheduler ()
273
+ schedudler .add_job (func = event , args = [_global ['context' ]], trigger = 'cron' , hour = hour , minute = minute , day_of_week = 'mon-fri' )
274
+ schedudler .start ()
275
+
276
+ ## 格式化返回的数据
277
+ def parse (data ):
278
+ result = json .loads (json .dumps (data ), object_hook = lambda d : CurrentDict (** d ))
279
+ return result
280
+
281
+ ## 格式化返回的数据
282
+ def _parse_global (data ):
283
+ result = json .loads (json .dumps (data ), object_hook = lambda d : _Global (** d ))
284
+ return result
285
+
286
+ ## 格式化返回的数据
287
+ def _parse_context (data ):
288
+ result = json .loads (json .dumps (data ), object_hook = lambda d : _Context (** d ))
289
+ return result
290
+
291
+ # 暴露的全局变量
292
+ g = _parse_global ({})
293
+ context = _parse_context ({})
294
+
295
+ # 保存一些内容
296
+ _global = {
297
+ 'session' : None ,
298
+ 'cookies' : None ,
299
+ 'context' : context ,
300
+ 'g' : g
301
+ }
0 commit comments