1
1
"""Module that contains Theta Client class."""
2
+ import datetime
2
3
import struct
4
+ import threading
3
5
from decimal import Decimal
4
6
from threading import Thread
5
7
from time import sleep
21
23
from .terminal import check_download , launch_terminal
22
24
23
25
_NOT_CONNECTED_MSG = "You must establish a connection first."
24
- _VERSION = '0.7.7 '
26
+ _VERSION = '0.7.8 '
25
27
26
28
27
29
def _format_strike (strike : float ) -> int :
@@ -193,6 +195,7 @@ def __init__(self):
193
195
self .quote = Quote ()
194
196
self .open_interest = OpenInterest ()
195
197
self .contract = Contract ()
198
+ self .date = None
196
199
197
200
198
201
class ThetaClient :
@@ -223,7 +226,11 @@ def __init__(self, port: int = 11000, timeout: Optional[float] = 60, launch: boo
223
226
self ._stream_server : Optional [socket .socket ] = None # None while disconnected
224
227
self .launch = launch
225
228
self ._stream_impl = None
229
+ self ._stream_responses = {}
230
+ self ._counter_lock = threading .Lock ()
231
+ self ._stream_req_id = 0
226
232
233
+ print ('If you require API support, feel free to join our discord server! https://discord.thetadata.us' )
227
234
if launch :
228
235
if username == "default" or passwd == "default" :
229
236
print ('------------------------------------------------------------------------------------------------' )
@@ -290,56 +297,123 @@ def connect_stream(self, callback):
290
297
def close_stream (self ):
291
298
self ._stream_server .close ()
292
299
293
- def req_full_trade_stream_opt (self ):
300
+ def req_full_trade_stream_opt (self , timeout : int = 5 ):
294
301
"""from_bytes
295
302
"""
296
303
assert self ._stream_server is not None , _NOT_CONNECTED_MSG
297
304
305
+ with self ._counter_lock :
306
+ req_id = self ._stream_req_id
307
+ self ._stream_responses [req_id ] = None
308
+ self ._stream_req_id += 1
309
+
298
310
# send request
299
- hist_msg = f"MSG_CODE={ MessageType .STREAM_REQ .value } &sec={ SecType .OPTION .value } &req={ OptionReqType .TRADE .value } \n "
311
+ hist_msg = f"MSG_CODE={ MessageType .STREAM_REQ .value } &sec={ SecType .OPTION .value } " \
312
+ f"&req={ OptionReqType .TRADE .value } &id={ req_id } \n "
300
313
self ._stream_server .sendall (hist_msg .encode ("utf-8" ))
301
314
302
- def req_full_open_interest_stream (self ):
315
+ tries = 0
316
+ lim = timeout * 100
317
+ while self ._stream_responses [req_id ] is None : # This is kind of dumb.
318
+ sleep (.01 )
319
+ tries += 1
320
+ if tries >= lim :
321
+ return StreamResponseType .TIMED_OUT
322
+
323
+ if self ._stream_responses [req_id ] is not StreamResponseType .SUBSCRIBED :
324
+ raise PermissionError ("Invalid permissions for stream request: " + self ._stream_responses [req_id ].name )
325
+
326
+ def req_full_open_interest_stream (self , timeout : int = 5 ):
303
327
"""from_bytes
304
328
"""
305
329
assert self ._stream_server is not None , _NOT_CONNECTED_MSG
306
330
331
+ with self ._counter_lock :
332
+ req_id = self ._stream_req_id
333
+ self ._stream_responses [req_id ] = None
334
+ self ._stream_req_id += 1
335
+
307
336
# send request
308
337
hist_msg = f"MSG_CODE={ MessageType .STREAM_REQ .value } &sec={ SecType .OPTION .value } " \
309
- f"&req={ OptionReqType .OPEN_INTEREST .value } \n "
338
+ f"&req={ OptionReqType .OPEN_INTEREST .value } &id= { req_id } \n "
310
339
self ._stream_server .sendall (hist_msg .encode ("utf-8" ))
311
340
312
- def req_trade_stream_opt (self , root : str , exp : date , strike : float , right : OptionRight ):
341
+ tries = 0
342
+ lim = timeout * 100
343
+ while self ._stream_responses [req_id ] is None : # This is kind of dumb.
344
+ sleep (.01 )
345
+ tries += 1
346
+ if tries >= lim :
347
+ return StreamResponseType .TIMED_OUT
348
+
349
+ if self ._stream_responses [req_id ] is not StreamResponseType .SUBSCRIBED :
350
+ raise PermissionError ("Invalid permissions for stream request: " + self ._stream_responses [req_id ].name )
351
+
352
+ def req_trade_stream_opt (self , root : str , exp : date = 0 , strike : float = 0 , right : OptionRight = 'C' , timeout : int = 5 ):
313
353
"""from_bytes
314
354
"""
315
355
assert self ._stream_server is not None , _NOT_CONNECTED_MSG
316
356
# format data
317
357
strike = _format_strike (strike )
318
358
exp_fmt = _format_date (exp )
319
359
360
+ with self ._counter_lock :
361
+ req_id = self ._stream_req_id
362
+ self ._stream_responses [req_id ] = None
363
+ self ._stream_req_id += 1
364
+
320
365
# send request
321
366
hist_msg = f"MSG_CODE={ MessageType .STREAM_REQ .value } &root={ root } &exp={ exp_fmt } &strike={ strike } " \
322
- f"&right={ right .value } &sec={ SecType .OPTION .value } &req={ OptionReqType .TRADE .value } \n "
367
+ f"&right={ right .value } &sec={ SecType .OPTION .value } &req={ OptionReqType .TRADE .value } &id= { req_id } \n "
323
368
self ._stream_server .sendall (hist_msg .encode ("utf-8" ))
324
369
325
- def req_quote_stream_opt (self , root : str , exp : date , strike : float , right : OptionRight ):
370
+ tries = 0
371
+ lim = timeout * 100
372
+ while self ._stream_responses [req_id ] is None : # This is kind of dumb.
373
+ sleep (.01 )
374
+ tries += 1
375
+ if tries >= lim :
376
+ return StreamResponseType .TIMED_OUT
377
+
378
+ if self ._stream_responses [req_id ] is not StreamResponseType .SUBSCRIBED :
379
+ raise PermissionError ("Invalid permissions for stream request: " + self ._stream_responses [req_id ].name )
380
+
381
+ def req_quote_stream_opt (self , root : str , exp : date = 0 , strike : float = 0 , right : OptionRight = 'C' , timeout : int = 5 ):
326
382
"""from_bytes
327
383
"""
328
384
assert self ._stream_server is not None , _NOT_CONNECTED_MSG
329
385
# format data
330
386
strike = _format_strike (strike )
331
387
exp_fmt = _format_date (exp )
332
388
389
+ with self ._counter_lock :
390
+ req_id = self ._stream_req_id
391
+ self ._stream_responses [req_id ] = None
392
+ self ._stream_req_id += 1
393
+
333
394
# send request
334
395
hist_msg = f"MSG_CODE={ MessageType .STREAM_REQ .value } &root={ root } &exp={ exp_fmt } &strike={ strike } " \
335
- f"&right={ right .value } &sec={ SecType .OPTION .value } &req={ OptionReqType .QUOTE .value } \n "
396
+ f"&right={ right .value } &sec={ SecType .OPTION .value } &req={ OptionReqType .QUOTE .value } &id= { req_id } \n "
336
397
self ._stream_server .sendall (hist_msg .encode ("utf-8" ))
337
398
399
+ tries = 0
400
+ lim = timeout * 100
401
+ while self ._stream_responses [req_id ] is None : # This is kind of dumb.
402
+ sleep (.01 )
403
+ tries += 1
404
+ if tries >= lim :
405
+ return StreamResponseType .TIMED_OUT
406
+
407
+ if self ._stream_responses [req_id ] is not StreamResponseType .SUBSCRIBED :
408
+ raise PermissionError ("Invalid permissions for stream request: " + self ._stream_responses [req_id ].name )
409
+
338
410
def _recv_stream (self ):
339
411
"""from_bytes
340
412
"""
341
413
msg = StreamMsg ()
414
+
342
415
parse_int = lambda d : int .from_bytes (d , "big" )
416
+
343
417
while True :
344
418
msg .type = StreamMsgType .from_code (parse_int (self ._read_stream (1 )[:1 ]))
345
419
msg .contract .from_bytes (self ._read_stream (parse_int (self ._read_stream (1 )[:1 ])))
@@ -351,11 +425,20 @@ def _recv_stream(self):
351
425
msg .trade .from_bytes (data )
352
426
elif msg .type == StreamMsgType .PING :
353
427
self ._read_stream (n_bytes = 4 )
428
+ continue
354
429
elif msg .type == StreamMsgType .OPEN_INTEREST :
355
430
data = self ._read_stream (n_bytes = 8 )
356
431
msg .open_interest .from_bytes (data )
357
- else :
432
+ elif msg .type == StreamMsgType .REQ_RESPONSE :
433
+ msg_id = parse_int (self ._read_stream (4 ))
434
+ msg_rep = StreamResponseType .from_code (parse_int (self ._read_stream (4 )))
435
+ self ._stream_responses [msg_id ] = msg_rep
358
436
continue
437
+ elif msg .type == StreamMsgType .STOP or msg .type == StreamMsgType .START :
438
+ msg .date = datetime .strptime (str (parse_int (self ._read_stream (4 ))), "%Y%m%d" ).date ()
439
+ else :
440
+ raise ValueError ('undefined msg type' )
441
+
359
442
self ._stream_impl (msg )
360
443
361
444
def _read_stream (self , n_bytes : int ) -> bytearray :
0 commit comments