25
25
import hashlib
26
26
import json
27
27
import pathlib
28
+ import ssl
28
29
import time
29
30
import urllib .parse
30
31
import uuid
32
+ from http .cookiejar import MozillaCookieJar
33
+
31
34
import certifi
32
- import ssl
33
35
import requests
34
36
import websockets
35
37
from ecdsa import NIST256p , SigningKey
36
38
from ecdsa .util import sigencode_der
37
- from http .cookiejar import MozillaCookieJar
38
39
39
40
from pytr .utils import get_logger
40
41
41
-
42
42
home = pathlib .Path .home ()
43
43
BASE_DIR = home / ".pytr"
44
44
CREDENTIALS_FILE = BASE_DIR / "credentials"
@@ -96,9 +96,7 @@ def __init__(
96
96
self ._locale = locale
97
97
self ._save_cookies = save_cookies
98
98
99
- self ._credentials_file = (
100
- pathlib .Path (credentials_file ) if credentials_file else CREDENTIALS_FILE
101
- )
99
+ self ._credentials_file = pathlib .Path (credentials_file ) if credentials_file else CREDENTIALS_FILE
102
100
103
101
if not (phone_no and pin ):
104
102
try :
@@ -107,18 +105,12 @@ def __init__(
107
105
self .phone_no = lines [0 ].strip ()
108
106
self .pin = lines [1 ].strip ()
109
107
except FileNotFoundError :
110
- raise ValueError (
111
- f"phone_no and pin must be specified explicitly or via { self ._credentials_file } "
112
- )
108
+ raise ValueError (f"phone_no and pin must be specified explicitly or via { self ._credentials_file } " )
113
109
else :
114
110
self .phone_no = phone_no
115
111
self .pin = pin
116
112
117
- self ._cookies_file = (
118
- pathlib .Path (cookies_file )
119
- if cookies_file
120
- else BASE_DIR / f"cookies.{ self .phone_no } .txt"
121
- )
113
+ self ._cookies_file = pathlib .Path (cookies_file ) if cookies_file else BASE_DIR / f"cookies.{ self .phone_no } .txt"
122
114
123
115
self .keyfile = keyfile if keyfile else KEY_FILE
124
116
try :
@@ -231,9 +223,7 @@ def complete_weblogin(self, verify_code):
231
223
if not self ._process_id and not self ._websession :
232
224
raise ValueError ("Initiate web login first." )
233
225
234
- r = self ._websession .post (
235
- f"{ self ._host } /api/v1/auth/web/login/{ self ._process_id } /{ verify_code } "
236
- )
226
+ r = self ._websession .post (f"{ self ._host } /api/v1/auth/web/login/{ self ._process_id } /{ verify_code } " )
237
227
r .raise_for_status ()
238
228
self .save_websession ()
239
229
self ._weblogin = True
@@ -270,9 +260,7 @@ def _web_request(self, url_path, payload=None, method="GET"):
270
260
r = self ._websession .get (f"{ self ._host } /api/v1/auth/web/session" )
271
261
r .raise_for_status ()
272
262
self ._web_session_token_expires_at = time .time () + 290
273
- return self ._websession .request (
274
- method = method , url = f"{ self ._host } { url_path } " , data = payload
275
- )
263
+ return self ._websession .request (method = method , url = f"{ self ._host } { url_path } " , data = payload )
276
264
277
265
async def _get_ws (self ):
278
266
if self ._ws and self ._ws .open :
@@ -301,9 +289,7 @@ async def _get_ws(self):
301
289
}
302
290
connect_id = 31
303
291
304
- self ._ws = await websockets .connect (
305
- "wss://api.traderepublic.com" , ssl = ssl_context , extra_headers = extra_headers
306
- )
292
+ self ._ws = await websockets .connect ("wss://api.traderepublic.com" , ssl = ssl_context , extra_headers = extra_headers )
307
293
await self ._ws .send (f"connect { connect_id } { json .dumps (connection_message )} " )
308
294
response = await self ._ws .recv ()
309
295
@@ -354,9 +340,7 @@ async def recv(self):
354
340
355
341
if subscription_id not in self .subscriptions :
356
342
if code != "C" :
357
- self .log .debug (
358
- f"No active subscription for id { subscription_id } , dropping message"
359
- )
343
+ self .log .debug (f"No active subscription for id { subscription_id } , dropping message" )
360
344
continue
361
345
subscription = self .subscriptions [subscription_id ]
362
346
@@ -408,16 +392,12 @@ async def _receive_one(self, fut, timeout):
408
392
subscription_id = await fut
409
393
410
394
try :
411
- return await asyncio .wait_for (
412
- self ._recv_subscription (subscription_id ), timeout
413
- )
395
+ return await asyncio .wait_for (self ._recv_subscription (subscription_id ), timeout )
414
396
finally :
415
397
await self .unsubscribe (subscription_id )
416
398
417
399
def run_blocking (self , fut , timeout = 5.0 ):
418
- return asyncio .get_event_loop ().run_until_complete (
419
- self ._receive_one (fut , timeout = timeout )
420
- )
400
+ return asyncio .get_event_loop ().run_until_complete (self ._receive_one (fut , timeout = timeout ))
421
401
422
402
async def portfolio (self ):
423
403
return await self .subscribe ({"type" : "portfolio" })
@@ -437,21 +417,14 @@ async def cash(self):
437
417
async def available_cash_for_payout (self ):
438
418
return await self .subscribe ({"type" : "availableCashForPayout" })
439
419
440
- async def portfolio_status (self ):
441
- return await self .subscribe ({"type" : "portfolioStatus" })
442
-
443
420
async def portfolio_history (self , timeframe ):
444
- return await self .subscribe (
445
- {"type" : "portfolioAggregateHistory" , "range" : timeframe }
446
- )
421
+ return await self .subscribe ({"type" : "portfolioAggregateHistory" , "range" : timeframe })
447
422
448
423
async def instrument_details (self , isin ):
449
424
return await self .subscribe ({"type" : "instrument" , "id" : isin })
450
425
451
426
async def instrument_suitability (self , isin ):
452
- return await self .subscribe (
453
- {"type" : "instrumentSuitability" , "instrumentId" : isin }
454
- )
427
+ return await self .subscribe ({"type" : "instrumentSuitability" , "instrumentId" : isin })
455
428
456
429
async def stock_details (self , isin ):
457
430
return await self .subscribe ({"type" : "stockDetails" , "id" : isin })
@@ -460,19 +433,15 @@ async def add_watchlist(self, isin):
460
433
return await self .subscribe ({"type" : "addToWatchlist" , "instrumentId" : isin })
461
434
462
435
async def remove_watchlist (self , isin ):
463
- return await self .subscribe (
464
- {"type" : "removeFromWatchlist" , "instrumentId" : isin }
465
- )
436
+ return await self .subscribe ({"type" : "removeFromWatchlist" , "instrumentId" : isin })
466
437
467
438
async def ticker (self , isin , exchange = "LSX" ):
468
439
return await self .subscribe ({"type" : "ticker" , "id" : f"{ isin } .{ exchange } " })
469
440
470
441
async def performance (self , isin , exchange = "LSX" ):
471
442
return await self .subscribe ({"type" : "performance" , "id" : f"{ isin } .{ exchange } " })
472
443
473
- async def performance_history (
474
- self , isin , timeframe , exchange = "LSX" , resolution = None
475
- ):
444
+ async def performance_history (self , isin , timeframe , exchange = "LSX" , resolution = None ):
476
445
parameters = {
477
446
"type" : "aggregateHistory" ,
478
447
"id" : f"{ isin } .{ exchange } " ,
@@ -501,9 +470,7 @@ async def timeline_detail_order(self, order_id):
501
470
return await self .subscribe ({"type" : "timelineDetail" , "orderId" : order_id })
502
471
503
472
async def timeline_detail_savings_plan (self , savings_plan_id ):
504
- return await self .subscribe (
505
- {"type" : "timelineDetail" , "savingsPlanId" : savings_plan_id }
506
- )
473
+ return await self .subscribe ({"type" : "timelineDetail" , "savingsPlanId" : savings_plan_id })
507
474
508
475
async def timeline_transactions (self , after = None ):
509
476
return await self .subscribe ({"type" : "timelineTransactions" , "after" : after })
@@ -518,9 +485,7 @@ async def search_tags(self):
518
485
return await self .subscribe ({"type" : "neonSearchTags" })
519
486
520
487
async def search_suggested_tags (self , query ):
521
- return await self .subscribe (
522
- {"type" : "neonSearchSuggestedTags" , "data" : {"q" : query }}
523
- )
488
+ return await self .subscribe ({"type" : "neonSearchSuggestedTags" , "data" : {"q" : query }})
524
489
525
490
async def search (
526
491
self ,
@@ -546,17 +511,11 @@ async def search(
546
511
if filter_index :
547
512
search_parameters ["filter" ].append ({"key" : "index" , "value" : filter_index })
548
513
if filter_country :
549
- search_parameters ["filter" ].append (
550
- {"key" : "country" , "value" : filter_country }
551
- )
514
+ search_parameters ["filter" ].append ({"key" : "country" , "value" : filter_country })
552
515
if filter_region :
553
- search_parameters ["filter" ].append (
554
- {"key" : "region" , "value" : filter_region }
555
- )
516
+ search_parameters ["filter" ].append ({"key" : "region" , "value" : filter_region })
556
517
if filter_sector :
557
- search_parameters ["filter" ].append (
558
- {"key" : "sector" , "value" : filter_sector }
559
- )
518
+ search_parameters ["filter" ].append ({"key" : "sector" , "value" : filter_sector })
560
519
561
520
search_type = "neonSearch" if not aggregate else "neonSearchAggregations"
562
521
return await self .subscribe ({"type" : search_type , "data" : search_parameters })
@@ -750,17 +709,13 @@ async def change_savings_plan(
750
709
return await self .subscribe (parameters )
751
710
752
711
async def cancel_savings_plan (self , savings_plan_id ):
753
- return await self .subscribe (
754
- {"type" : "cancelSavingsPlan" , "id" : savings_plan_id }
755
- )
712
+ return await self .subscribe ({"type" : "cancelSavingsPlan" , "id" : savings_plan_id })
756
713
757
714
async def price_alarm_overview (self ):
758
715
return await self .subscribe ({"type" : "priceAlarms" })
759
716
760
717
async def create_price_alarm (self , isin , price ):
761
- return await self .subscribe (
762
- {"type" : "createPriceAlarm" , "instrumentId" : isin , "targetPrice" : price }
763
- )
718
+ return await self .subscribe ({"type" : "createPriceAlarm" , "instrumentId" : isin , "targetPrice" : price })
764
719
765
720
async def cancel_price_alarm (self , price_alarm_id ):
766
721
return await self .subscribe ({"type" : "cancelPriceAlarm" , "id" : price_alarm_id })
0 commit comments