schwabdev 2.2.0__tar.gz → 2.2.2__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.
- {schwabdev-2.2.0 → schwabdev-2.2.2}/PKG-INFO +4 -1
- {schwabdev-2.2.0 → schwabdev-2.2.2}/README.md +1 -0
- {schwabdev-2.2.0 → schwabdev-2.2.2}/schwabdev/api.py +62 -61
- {schwabdev-2.2.0 → schwabdev-2.2.2}/schwabdev/stream.py +31 -18
- {schwabdev-2.2.0 → schwabdev-2.2.2}/schwabdev.egg-info/PKG-INFO +4 -1
- {schwabdev-2.2.0 → schwabdev-2.2.2}/schwabdev.egg-info/SOURCES.txt +0 -1
- {schwabdev-2.2.0 → schwabdev-2.2.2}/setup.py +1 -1
- schwabdev-2.2.0/schwabdev/color_print.py +0 -19
- {schwabdev-2.2.0 → schwabdev-2.2.2}/LICENSE.txt +0 -0
- {schwabdev-2.2.0 → schwabdev-2.2.2}/schwabdev/__init__.py +0 -0
- {schwabdev-2.2.0 → schwabdev-2.2.2}/schwabdev.egg-info/dependency_links.txt +0 -0
- {schwabdev-2.2.0 → schwabdev-2.2.2}/schwabdev.egg-info/requires.txt +0 -0
- {schwabdev-2.2.0 → schwabdev-2.2.2}/schwabdev.egg-info/top_level.txt +0 -0
- {schwabdev-2.2.0 → schwabdev-2.2.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: schwabdev
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: An easy and lightweight wrapper for using the Charles Schwab API.
|
|
5
5
|
Author: Tyler Bowers
|
|
6
6
|
Author-email: tylerebowers@gmail.com
|
|
@@ -19,6 +19,8 @@ Classifier: Natural Language :: English
|
|
|
19
19
|
Requires-Python: >=3.11
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE.txt
|
|
22
|
+
Requires-Dist: requests
|
|
23
|
+
Requires-Dist: websockets
|
|
22
24
|
|
|
23
25
|
# Schwab-API-Python
|
|
24
26
|
   [](https://www.paypal.com/donate/?business=8VDFKHMBFSC2Q&no_recurring=0¤cy_code=USD) 
|
|
@@ -54,6 +56,7 @@ print(client.account_linked().json()) #make api calls
|
|
|
54
56
|
- Functions for all api functions (examples in `examples/api_demo.py`)
|
|
55
57
|
- Stream real-time data with a customizable response handler (examples in `examples/stream_demo.py`)
|
|
56
58
|
### TBD
|
|
59
|
+
- Paper trading client
|
|
57
60
|
- Automatic refresh token updates. (Waiting for Schwab implementation)
|
|
58
61
|
### Notes
|
|
59
62
|
The schwabdev folder contains code for main operations:
|
|
@@ -32,6 +32,7 @@ print(client.account_linked().json()) #make api calls
|
|
|
32
32
|
- Functions for all api functions (examples in `examples/api_demo.py`)
|
|
33
33
|
- Stream real-time data with a customizable response handler (examples in `examples/stream_demo.py`)
|
|
34
34
|
### TBD
|
|
35
|
+
- Paper trading client
|
|
35
36
|
- Automatic refresh token updates. (Waiting for Schwab implementation)
|
|
36
37
|
### Notes
|
|
37
38
|
The schwabdev folder contains code for main operations:
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import time
|
|
3
3
|
import base64
|
|
4
|
+
import datetime
|
|
4
5
|
import requests
|
|
5
6
|
import threading
|
|
6
7
|
import webbrowser
|
|
7
8
|
import urllib.parse
|
|
8
9
|
from .stream import Stream
|
|
9
|
-
from datetime import datetime
|
|
10
|
-
|
|
11
10
|
|
|
12
11
|
class Client:
|
|
13
12
|
|
|
@@ -75,8 +74,8 @@ class Client:
|
|
|
75
74
|
self._access_token_issued = at_issued
|
|
76
75
|
self._refresh_token_issued = rt_issued
|
|
77
76
|
if self.verbose:
|
|
78
|
-
print(self._access_token_issued.strftime("Access token last updated: %Y-%m-%d %H:%M:%S") + f" (expires in {self._access_token_timeout - (datetime.now() - self._access_token_issued).seconds} seconds)")
|
|
79
|
-
print(self._refresh_token_issued.strftime("Refresh token last updated: %Y-%m-%d %H:%M:%S") + f" (expires in {self._refresh_token_timeout - (datetime.now() - self._refresh_token_issued).days} days)")
|
|
77
|
+
print(self._access_token_issued.strftime("Access token last updated: %Y-%m-%d %H:%M:%S") + f" (expires in {self._access_token_timeout - (datetime.datetime.now(datetime.timezone.utc) - self._access_token_issued).seconds} seconds)")
|
|
78
|
+
print(self._refresh_token_issued.strftime("Refresh token last updated: %Y-%m-%d %H:%M:%S") + f" (expires in {self._refresh_token_timeout - (datetime.datetime.now(datetime.timezone.utc) - self._refresh_token_issued).days} days)")
|
|
80
79
|
# check if tokens need to be updated and update if needed
|
|
81
80
|
self.update_tokens()
|
|
82
81
|
else:
|
|
@@ -98,7 +97,7 @@ class Client:
|
|
|
98
97
|
print("Warning: Tokens will not be updated automatically.")
|
|
99
98
|
|
|
100
99
|
if self.verbose:
|
|
101
|
-
print("Initialization Complete")
|
|
100
|
+
print("Schwabdev Client Initialization Complete")
|
|
102
101
|
|
|
103
102
|
def update_tokens(self, force=False):
|
|
104
103
|
"""
|
|
@@ -106,11 +105,11 @@ class Client:
|
|
|
106
105
|
:param force: force update of refresh token (also updates access token)
|
|
107
106
|
:type force: bool
|
|
108
107
|
"""
|
|
109
|
-
if (datetime.now() - self._refresh_token_issued).days >= (self._refresh_token_timeout - 1) or force: # check if we need to update refresh (and access) token
|
|
108
|
+
if (datetime.datetime.now(datetime.timezone.utc) - self._refresh_token_issued).days >= (self._refresh_token_timeout - 1) or force: # check if we need to update refresh (and access) token
|
|
110
109
|
print("The refresh token has expired, please update!")
|
|
111
110
|
self._update_refresh_token()
|
|
112
|
-
elif ((datetime.now() - self._access_token_issued).days >= 1) or (
|
|
113
|
-
(datetime.now() - self._access_token_issued).seconds > (self._access_token_timeout - 61)): # check if we need to update access token
|
|
111
|
+
elif ((datetime.datetime.now(datetime.timezone.utc) - self._access_token_issued).days >= 1) or (
|
|
112
|
+
(datetime.datetime.now(datetime.timezone.utc) - self._access_token_issued).seconds > (self._access_token_timeout - 61)): # check if we need to update access token
|
|
114
113
|
if self.verbose: print("The access token has expired, updating automatically.")
|
|
115
114
|
self._update_access_token()
|
|
116
115
|
|
|
@@ -129,7 +128,7 @@ class Client:
|
|
|
129
128
|
response = self._post_oauth_token('refresh_token', token_dictionary_old.get("refresh_token"))
|
|
130
129
|
if response.ok:
|
|
131
130
|
# get and update to the new access token
|
|
132
|
-
self._access_token_issued = datetime.now()
|
|
131
|
+
self._access_token_issued = datetime.datetime.now(datetime.timezone.utc)
|
|
133
132
|
self._refresh_token_issued = refresh_token_issued
|
|
134
133
|
new_td = response.json()
|
|
135
134
|
self.access_token = new_td.get("access_token")
|
|
@@ -140,6 +139,7 @@ class Client:
|
|
|
140
139
|
print(f"Access token updated: {self._access_token_issued}")
|
|
141
140
|
break
|
|
142
141
|
else:
|
|
142
|
+
print(response.text)
|
|
143
143
|
print(f"Could not get new access token ({i+1} of 3).")
|
|
144
144
|
time.sleep(10)
|
|
145
145
|
|
|
@@ -159,7 +159,7 @@ class Client:
|
|
|
159
159
|
response = self._post_oauth_token('authorization_code', code)
|
|
160
160
|
if response.ok:
|
|
161
161
|
# update token file and variables
|
|
162
|
-
self._access_token_issued = self._refresh_token_issued = datetime.now()
|
|
162
|
+
self._access_token_issued = self._refresh_token_issued = datetime.datetime.now(datetime.timezone.utc)
|
|
163
163
|
new_td = response.json()
|
|
164
164
|
self.access_token = new_td.get("access_token")
|
|
165
165
|
self.refresh_token = new_td.get("refresh_token")
|
|
@@ -168,6 +168,7 @@ class Client:
|
|
|
168
168
|
self._write_tokens_file(self._access_token_issued, self._refresh_token_issued, new_td)
|
|
169
169
|
if self.verbose: print("Refresh and Access tokens updated")
|
|
170
170
|
else:
|
|
171
|
+
print(response.text)
|
|
171
172
|
print("Could not get new refresh and access tokens, check these:\n 1. App status is "
|
|
172
173
|
"\"Ready For Use\".\n 2. App key and app secret are valid.\n 3. You pasted the "
|
|
173
174
|
"whole url within 30 seconds. (it has a quick expiration)")
|
|
@@ -198,9 +199,9 @@ class Client:
|
|
|
198
199
|
"""
|
|
199
200
|
Writes token file
|
|
200
201
|
:param at_issued: access token issued
|
|
201
|
-
:type at_issued: datetime
|
|
202
|
+
:type at_issued: datetime.pyi
|
|
202
203
|
:param rt_issued: refresh token issued
|
|
203
|
-
:type rt_issued: datetime
|
|
204
|
+
:type rt_issued: datetime.pyi
|
|
204
205
|
:param token_dictionary: token dictionary
|
|
205
206
|
:type token_dictionary: dict
|
|
206
207
|
"""
|
|
@@ -218,12 +219,12 @@ class Client:
|
|
|
218
219
|
"""
|
|
219
220
|
Reads token file
|
|
220
221
|
:return: access token issued, refresh token issued, token dictionary
|
|
221
|
-
:rtype: datetime, datetime, dict
|
|
222
|
+
:rtype: datetime.pyi, datetime.pyi, dict
|
|
222
223
|
"""
|
|
223
224
|
try:
|
|
224
225
|
with open(self._tokens_file, 'r') as f:
|
|
225
226
|
d = json.load(f)
|
|
226
|
-
return datetime.fromisoformat(d.get("access_token_issued")), datetime.fromisoformat(d.get("refresh_token_issued")), d.get("token_dictionary")
|
|
227
|
+
return datetime.datetime.fromisoformat(d.get("access_token_issued")), datetime.datetime.fromisoformat(d.get("refresh_token_issued")), d.get("token_dictionary")
|
|
227
228
|
except Exception as e:
|
|
228
229
|
print(e)
|
|
229
230
|
return None, None, None
|
|
@@ -243,8 +244,8 @@ class Client:
|
|
|
243
244
|
def _time_convert(self, dt=None, form="8601"):
|
|
244
245
|
"""
|
|
245
246
|
Convert time to the correct format, passthrough if a string, preserve None if None for params parser
|
|
246
|
-
:param dt: datetime object to convert
|
|
247
|
-
:type dt: datetime
|
|
247
|
+
:param dt: datetime.pyi object to convert
|
|
248
|
+
:type dt: datetime.pyi
|
|
248
249
|
:param form: what to convert input to
|
|
249
250
|
:type form: str
|
|
250
251
|
:return: converted time or passthrough
|
|
@@ -253,7 +254,7 @@ class Client:
|
|
|
253
254
|
if dt is None or isinstance(dt, str):
|
|
254
255
|
return dt
|
|
255
256
|
elif form == "8601": # assume datetime object from here on
|
|
256
|
-
return f'{dt.isoformat()[:-
|
|
257
|
+
return f'{dt.isoformat()[:-9]}Z'
|
|
257
258
|
elif form == "epoch":
|
|
258
259
|
return int(dt.timestamp())
|
|
259
260
|
elif form == "epoch_ms":
|
|
@@ -263,7 +264,7 @@ class Client:
|
|
|
263
264
|
else:
|
|
264
265
|
return dt
|
|
265
266
|
|
|
266
|
-
def _format_list(self, l):
|
|
267
|
+
def _format_list(self, l: list | str | None):
|
|
267
268
|
"""
|
|
268
269
|
Convert python list to string or passthough if already a string i.e ["a", "b"] -> "a,b"
|
|
269
270
|
:param l: list to convert
|
|
@@ -284,7 +285,7 @@ class Client:
|
|
|
284
285
|
Accounts and Trading Production
|
|
285
286
|
"""
|
|
286
287
|
|
|
287
|
-
def account_linked(self):
|
|
288
|
+
def account_linked(self) -> requests.Response:
|
|
288
289
|
"""
|
|
289
290
|
Account numbers in plain text cannot be used outside of headers or request/response bodies. As the first step consumers must invoke this service to retrieve the list of plain text/encrypted value pairs, and use encrypted account values for all subsequent calls for any accountNumber request.
|
|
290
291
|
:return: All linked account numbers and hashes
|
|
@@ -294,7 +295,7 @@ class Client:
|
|
|
294
295
|
headers={'Authorization': f'Bearer {self.access_token}'},
|
|
295
296
|
timeout=self.timeout)
|
|
296
297
|
|
|
297
|
-
def account_details_all(self, fields=None):
|
|
298
|
+
def account_details_all(self, fields=None) -> requests.Response:
|
|
298
299
|
"""
|
|
299
300
|
All the linked account information for the user logged in. The balances on these accounts are displayed by default however the positions on these accounts will be displayed based on the "positions" flag.
|
|
300
301
|
:param fields: fields to return (options: "positions")
|
|
@@ -307,7 +308,7 @@ class Client:
|
|
|
307
308
|
params=self._params_parser({'fields': fields}),
|
|
308
309
|
timeout=self.timeout)
|
|
309
310
|
|
|
310
|
-
def account_details(self, accountHash, fields=None):
|
|
311
|
+
def account_details(self, accountHash: str, fields=None) -> requests.Response:
|
|
311
312
|
"""
|
|
312
313
|
Specific account information with balances and positions. The balance information on these accounts is displayed by default but Positions will be returned based on the "positions" flag.
|
|
313
314
|
:param accountHash: account hash from account_linked()
|
|
@@ -322,15 +323,15 @@ class Client:
|
|
|
322
323
|
params=self._params_parser({'fields': fields}),
|
|
323
324
|
timeout=self.timeout)
|
|
324
325
|
|
|
325
|
-
def account_orders(self, accountHash, fromEnteredTime, toEnteredTime, maxResults=None, status=None):
|
|
326
|
+
def account_orders(self, accountHash: str, fromEnteredTime: 'datetime | str', toEnteredTime: 'datetime | str', maxResults=None, status=None) -> requests.Response:
|
|
326
327
|
"""
|
|
327
328
|
All orders for a specific account. Orders retrieved can be filtered based on input parameters below. Maximum date range is 1 year.
|
|
328
329
|
:param accountHash: account hash from account_linked()
|
|
329
330
|
:type accountHash: str
|
|
330
331
|
:param fromEnteredTime: from entered time
|
|
331
|
-
:type fromEnteredTime: datetime | str
|
|
332
|
+
:type fromEnteredTime: datetime.pyi | str
|
|
332
333
|
:param toEnteredTime: to entered time
|
|
333
|
-
:type toEnteredTime: datetime | str
|
|
334
|
+
:type toEnteredTime: datetime.pyi | str
|
|
334
335
|
:param maxResults: maximum number of results
|
|
335
336
|
:type maxResults: int
|
|
336
337
|
:param status: status ("AWAITING_PARENT_ORDER"|"AWAITING_CONDITION"|"AWAITING_STOP_CONDITION"|"AWAITING_MANUAL_REVIEW"|"ACCEPTED"|"AWAITING_UR_OUT"|"PENDING_ACTIVATION"|"QUEUED"|"WORKING"|"REJECTED"|"PENDING_CANCEL"|"CANCELED"|"PENDING_REPLACE"|"REPLACED"|"FILLED"|"EXPIRED"|"NEW"|"AWAITING_RELEASE_TIME"|"PENDING_ACKNOWLEDGEMENT"|"PENDING_RECALL"|"UNKNOWN")
|
|
@@ -345,7 +346,7 @@ class Client:
|
|
|
345
346
|
'toEnteredTime': self._time_convert(toEnteredTime, "8601"), 'status': status}),
|
|
346
347
|
timeout=self.timeout)
|
|
347
348
|
|
|
348
|
-
def order_place(self, accountHash, order):
|
|
349
|
+
def order_place(self, accountHash: str, order: dict) -> requests.Response:
|
|
349
350
|
"""
|
|
350
351
|
Place an order for a specific account.
|
|
351
352
|
:param accountHash: account hash from account_linked()
|
|
@@ -361,13 +362,13 @@ class Client:
|
|
|
361
362
|
json=order,
|
|
362
363
|
timeout=self.timeout)
|
|
363
364
|
|
|
364
|
-
def order_details(self, accountHash, orderId):
|
|
365
|
+
def order_details(self, accountHash:str, orderId: int | str) -> requests.Response:
|
|
365
366
|
"""
|
|
366
367
|
Get a specific order by its ID, for a specific account
|
|
367
368
|
:param accountHash: account hash from account_linked()
|
|
368
369
|
:type accountHash: str
|
|
369
370
|
:param orderId: order id
|
|
370
|
-
:type orderId: str
|
|
371
|
+
:type orderId: int | str
|
|
371
372
|
:return: order details
|
|
372
373
|
:rtype: request.Response
|
|
373
374
|
"""
|
|
@@ -375,13 +376,13 @@ class Client:
|
|
|
375
376
|
headers={'Authorization': f'Bearer {self.access_token}'},
|
|
376
377
|
timeout=self.timeout)
|
|
377
378
|
|
|
378
|
-
def order_cancel(self, accountHash, orderId):
|
|
379
|
+
def order_cancel(self, accountHash: str, orderId: int | str) -> requests.Response:
|
|
379
380
|
"""
|
|
380
381
|
Cancel a specific order by its ID, for a specific account
|
|
381
382
|
:param accountHash: account hash from account_linked()
|
|
382
383
|
:type accountHash: str
|
|
383
384
|
:param orderId: order id
|
|
384
|
-
:type orderId: str
|
|
385
|
+
:type orderId: str|int
|
|
385
386
|
:return: response code
|
|
386
387
|
:rtype: request.Response
|
|
387
388
|
"""
|
|
@@ -389,13 +390,13 @@ class Client:
|
|
|
389
390
|
headers={'Authorization': f'Bearer {self.access_token}'},
|
|
390
391
|
timeout=self.timeout)
|
|
391
392
|
|
|
392
|
-
def order_replace(self, accountHash, orderId, order):
|
|
393
|
+
def order_replace(self, accountHash: str, orderId: int | str, order: dict) -> requests.Response:
|
|
393
394
|
"""
|
|
394
395
|
Replace an existing order for an account. The existing order will be replaced by the new order. Once replaced, the old order will be canceled and a new order will be created.
|
|
395
396
|
:param accountHash: account hash from account_linked()
|
|
396
397
|
:type accountHash: str
|
|
397
398
|
:param orderId: order id
|
|
398
|
-
:type orderId: str
|
|
399
|
+
:type orderId: str|int
|
|
399
400
|
:param order: order dictionary, examples in Schwab docs
|
|
400
401
|
:type order: dict
|
|
401
402
|
:return: response code
|
|
@@ -407,13 +408,13 @@ class Client:
|
|
|
407
408
|
json=order,
|
|
408
409
|
timeout=self.timeout)
|
|
409
410
|
|
|
410
|
-
def account_orders_all(self, fromEnteredTime, toEnteredTime, maxResults=None, status=None):
|
|
411
|
+
def account_orders_all(self, fromEnteredTime: 'datetime | str', toEnteredTime: 'datetime | str', maxResults=None, status=None) -> requests.Response:
|
|
411
412
|
"""
|
|
412
413
|
Get all orders for all accounts
|
|
413
414
|
:param fromEnteredTime: start date
|
|
414
|
-
:type fromEnteredTime: datetime | str
|
|
415
|
+
:type fromEnteredTime: datetime.pyi | str
|
|
415
416
|
:param toEnteredTime: end date
|
|
416
|
-
:type toEnteredTime: datetime | str
|
|
417
|
+
:type toEnteredTime: datetime.pyi | str
|
|
417
418
|
:param maxResults: maximum number of results (set to None for default 3000)
|
|
418
419
|
:type maxResults: int
|
|
419
420
|
:param status: status ("AWAITING_PARENT_ORDER"|"AWAITING_CONDITION"|"AWAITING_STOP_CONDITION"|"AWAITING_MANUAL_REVIEW"|"ACCEPTED"|"AWAITING_UR_OUT"|"PENDING_ACTIVATION"|"QUEUED"|"WORKING"|"REJECTED"|"PENDING_CANCEL"|"CANCELED"|"PENDING_REPLACE"|"REPLACED"|"FILLED"|"EXPIRED"|"NEW"|"AWAITING_RELEASE_TIME"|"PENDING_ACKNOWLEDGEMENT"|"PENDING_RECALL"|"UNKNOWN")
|
|
@@ -429,22 +430,22 @@ class Client:
|
|
|
429
430
|
timeout=self.timeout)
|
|
430
431
|
|
|
431
432
|
"""
|
|
432
|
-
def order_preview(self, accountHash, orderObject):
|
|
433
|
+
def order_preview(self, accountHash, orderObject) -> requests.Response:
|
|
433
434
|
#COMING SOON (waiting on Schwab)
|
|
434
435
|
return requests.post(f'{self._base_api_url}/trader/v1/accounts/{accountHash}/previewOrder',
|
|
435
436
|
headers={'Authorization': f'Bearer {self.access_token}',
|
|
436
437
|
"Content-Type": "application.json"}, data=orderObject)
|
|
437
438
|
"""
|
|
438
439
|
|
|
439
|
-
def transactions(self, accountHash, startDate, endDate, types, symbol=None):
|
|
440
|
+
def transactions(self, accountHash: str, startDate: 'datetime | str', endDate: 'datetime | str', types: str, symbol=None) -> requests.Response:
|
|
440
441
|
"""
|
|
441
442
|
All transactions for a specific account. Maximum number of transactions in response is 3000. Maximum date range is 1 year.
|
|
442
443
|
:param accountHash: account hash number
|
|
443
444
|
:type accountHash: str
|
|
444
445
|
:param startDate: start date
|
|
445
|
-
:type startDate: datetime | str
|
|
446
|
+
:type startDate: datetime.pyi | str
|
|
446
447
|
:param endDate: end date
|
|
447
|
-
:type endDate: datetime | str
|
|
448
|
+
:type endDate: datetime.pyi | str
|
|
448
449
|
:param types: transaction type ("TRADE, RECEIVE_AND_DELIVER, DIVIDEND_OR_INTEREST, ACH_RECEIPT, ACH_DISBURSEMENT, CASH_RECEIPT, CASH_DISBURSEMENT, ELECTRONIC_FUND, WIRE_OUT, WIRE_IN, JOURNAL, MEMORANDUM, MARGIN_CALL, MONEY_MARKET, SMA_ADJUSTMENT")
|
|
449
450
|
:type types: str
|
|
450
451
|
:param symbol: symbol
|
|
@@ -458,13 +459,13 @@ class Client:
|
|
|
458
459
|
'endDate': self._time_convert(endDate, "8601"), 'symbol': symbol, 'types': types}),
|
|
459
460
|
timeout=self.timeout)
|
|
460
461
|
|
|
461
|
-
def transaction_details(self, accountHash, transactionId):
|
|
462
|
+
def transaction_details(self, accountHash: str, transactionId: str | int) -> requests.Response:
|
|
462
463
|
"""
|
|
463
464
|
Get specific transaction information for a specific account
|
|
464
465
|
:param accountHash: account hash number
|
|
465
466
|
:type accountHash: str
|
|
466
467
|
:param transactionId: transaction id
|
|
467
|
-
:type transactionId: int
|
|
468
|
+
:type transactionId: str|int
|
|
468
469
|
:return: transaction details of transaction id using accountHash
|
|
469
470
|
:rtype: request.Response
|
|
470
471
|
"""
|
|
@@ -473,7 +474,7 @@ class Client:
|
|
|
473
474
|
params={'accountNumber': accountHash, 'transactionId': transactionId},
|
|
474
475
|
timeout=self.timeout)
|
|
475
476
|
|
|
476
|
-
def preferences(self):
|
|
477
|
+
def preferences(self) -> requests.Response:
|
|
477
478
|
"""
|
|
478
479
|
Get user preference information for the logged in user.
|
|
479
480
|
:return: User Preferences and Streaming Info
|
|
@@ -487,7 +488,7 @@ class Client:
|
|
|
487
488
|
Market Data
|
|
488
489
|
"""
|
|
489
490
|
|
|
490
|
-
def quotes(self, symbols=None, fields=None, indicative=False):
|
|
491
|
+
def quotes(self, symbols=None, fields=None, indicative=False) -> requests.Response:
|
|
491
492
|
"""
|
|
492
493
|
Get quotes for a list of tickers
|
|
493
494
|
:param symbols: list of symbols strings (e.g. "AMD,INTC" or ["AMD", "INTC"])
|
|
@@ -505,7 +506,7 @@ class Client:
|
|
|
505
506
|
{'symbols': self._format_list(symbols), 'fields': fields, 'indicative': indicative}),
|
|
506
507
|
timeout=self.timeout)
|
|
507
508
|
|
|
508
|
-
def quote(self, symbol_id, fields=None):
|
|
509
|
+
def quote(self, symbol_id: str, fields=None) -> requests.Response:
|
|
509
510
|
"""
|
|
510
511
|
Get quote for a single symbol
|
|
511
512
|
:param symbol_id: ticker symbol
|
|
@@ -520,9 +521,9 @@ class Client:
|
|
|
520
521
|
params=self._params_parser({'fields': fields}),
|
|
521
522
|
timeout=self.timeout)
|
|
522
523
|
|
|
523
|
-
def option_chains(self, symbol, contractType=None, strikeCount=None, includeUnderlyingQuote=None, strategy=None,
|
|
524
|
+
def option_chains(self, symbol: str, contractType=None, strikeCount=None, includeUnderlyingQuote=None, strategy=None,
|
|
524
525
|
interval=None, strike=None, range=None, fromDate=None, toDate=None, volatility=None, underlyingPrice=None,
|
|
525
|
-
interestRate=None, daysToExpiration=None, expMonth=None, optionType=None, entitlement=None):
|
|
526
|
+
interestRate=None, daysToExpiration=None, expMonth=None, optionType=None, entitlement=None) -> requests.Response:
|
|
526
527
|
"""
|
|
527
528
|
Get Option Chain including information on options contracts associated with each expiration for a ticker.
|
|
528
529
|
:param symbol: ticker symbol
|
|
@@ -542,9 +543,9 @@ class Client:
|
|
|
542
543
|
:param range: range ("ITM"|"NTM"|"OTM"...)
|
|
543
544
|
:type range: str
|
|
544
545
|
:param fromDate: from date
|
|
545
|
-
:type fromDate: datetime | str
|
|
546
|
+
:type fromDate: datetime.pyi | str
|
|
546
547
|
:param toDate: to date
|
|
547
|
-
:type toDate: datetime | str
|
|
548
|
+
:type toDate: datetime.pyi | str
|
|
548
549
|
:param volatility: volatility
|
|
549
550
|
:type volatility: float
|
|
550
551
|
:param underlyingPrice: underlying price
|
|
@@ -573,7 +574,7 @@ class Client:
|
|
|
573
574
|
'expMonth': expMonth, 'optionType': optionType, 'entitlement': entitlement}),
|
|
574
575
|
timeout=self.timeout)
|
|
575
576
|
|
|
576
|
-
def option_expiration_chain(self, symbol):
|
|
577
|
+
def option_expiration_chain(self, symbol: str) -> requests.Response:
|
|
577
578
|
"""
|
|
578
579
|
Get an option expiration chain for a ticker
|
|
579
580
|
:param symbol: ticker symbol
|
|
@@ -586,8 +587,8 @@ class Client:
|
|
|
586
587
|
params=self._params_parser({'symbol': symbol}),
|
|
587
588
|
timeout=self.timeout)
|
|
588
589
|
|
|
589
|
-
def price_history(self, symbol, periodType=None, period=None, frequencyType=None, frequency=None, startDate=None,
|
|
590
|
-
endDate=None, needExtendedHoursData=None, needPreviousClose=None):
|
|
590
|
+
def price_history(self, symbol: str, periodType=None, period=None, frequencyType=None, frequency=None, startDate=None,
|
|
591
|
+
endDate=None, needExtendedHoursData=None, needPreviousClose=None) -> requests.Response:
|
|
591
592
|
"""
|
|
592
593
|
Get price history for a ticker
|
|
593
594
|
:param symbol: ticker symbol
|
|
@@ -601,9 +602,9 @@ class Client:
|
|
|
601
602
|
:param frequency: frequency (1|5|10|15|30)
|
|
602
603
|
:type frequency: int
|
|
603
604
|
:param startDate: start date
|
|
604
|
-
:type startDate: datetime | str
|
|
605
|
+
:type startDate: datetime.pyi | str
|
|
605
606
|
:param endDate: end date
|
|
606
|
-
:type endDate: datetime | str
|
|
607
|
+
:type endDate: datetime.pyi | str
|
|
607
608
|
:param needExtendedHoursData: need extended hours data (True|False)
|
|
608
609
|
:type needExtendedHoursData: boolean
|
|
609
610
|
:param needPreviousClose: need previous close (True|False)
|
|
@@ -621,7 +622,7 @@ class Client:
|
|
|
621
622
|
'needPreviousClose': needPreviousClose}),
|
|
622
623
|
timeout=self.timeout)
|
|
623
624
|
|
|
624
|
-
def movers(self, symbol, sort=None, frequency=None):
|
|
625
|
+
def movers(self, symbol: str, sort=None, frequency=None) -> requests.Response:
|
|
625
626
|
"""
|
|
626
627
|
Get movers in a specific index and direction
|
|
627
628
|
:param symbol: symbol ("$DJI"|"$COMPX"|"$SPX"|"NYSE"|"NASDAQ"|"OTCBB"|"INDEX_ALL"|"EQUITY_ALL"|"OPTION_ALL"|"OPTION_PUT"|"OPTION_CALL")
|
|
@@ -634,17 +635,17 @@ class Client:
|
|
|
634
635
|
:rtype: request.Response
|
|
635
636
|
"""
|
|
636
637
|
return requests.get(f'{self._base_api_url}/marketdata/v1/movers/{symbol}',
|
|
637
|
-
headers={'Authorization': f'Bearer {self.access_token}'},
|
|
638
|
+
headers={"accept": "application/json", 'Authorization': f'Bearer {self.access_token}'},
|
|
638
639
|
params=self._params_parser({'sort': sort, 'frequency': frequency}),
|
|
639
640
|
timeout=self.timeout)
|
|
640
641
|
|
|
641
|
-
def market_hours(self, symbols, date=None):
|
|
642
|
+
def market_hours(self, symbols, date=None) -> requests.Response:
|
|
642
643
|
"""
|
|
643
644
|
Get Market Hours for dates in the future across different markets.
|
|
644
645
|
:param symbols: list of market symbols ("equity", "option", "bond", "future", "forex")
|
|
645
646
|
:type symbols: list
|
|
646
647
|
:param date: date
|
|
647
|
-
:type date: datetime | str
|
|
648
|
+
:type date: datetime.pyi | str
|
|
648
649
|
:return: market hours
|
|
649
650
|
:rtype: request.Response
|
|
650
651
|
"""
|
|
@@ -655,13 +656,13 @@ class Client:
|
|
|
655
656
|
'date': self._time_convert(date, 'YYYY-MM-DD')}),
|
|
656
657
|
timeout=self.timeout)
|
|
657
658
|
|
|
658
|
-
def market_hour(self, market_id, date=None):
|
|
659
|
+
def market_hour(self, market_id: str, date=None) -> requests.Response:
|
|
659
660
|
"""
|
|
660
661
|
Get Market Hours for dates in the future for a single market.
|
|
661
662
|
:param market_id: market id ("equity"|"option"|"bond"|"future"|"forex")
|
|
662
663
|
:type market_id: str
|
|
663
664
|
:param date: date
|
|
664
|
-
:type date: datetime | str
|
|
665
|
+
:type date: datetime.pyi | str
|
|
665
666
|
:return: market hours
|
|
666
667
|
:rtype: request.Response
|
|
667
668
|
"""
|
|
@@ -670,7 +671,7 @@ class Client:
|
|
|
670
671
|
params=self._params_parser({'date': self._time_convert(date, 'YYYY-MM-DD')}),
|
|
671
672
|
timeout=self.timeout)
|
|
672
673
|
|
|
673
|
-
def instruments(self, symbol, projection):
|
|
674
|
+
def instruments(self, symbol: str, projection) -> requests.Response:
|
|
674
675
|
"""
|
|
675
676
|
Get instruments for a list of symbols
|
|
676
677
|
:param symbol: symbol
|
|
@@ -685,11 +686,11 @@ class Client:
|
|
|
685
686
|
params={'symbol': symbol, 'projection': projection},
|
|
686
687
|
timeout=self.timeout)
|
|
687
688
|
|
|
688
|
-
def instrument_cusip(self, cusip_id):
|
|
689
|
+
def instrument_cusip(self, cusip_id: str | int) -> requests.Response:
|
|
689
690
|
"""
|
|
690
691
|
Get instrument for a single cusip
|
|
691
692
|
:param cusip_id: cusip id
|
|
692
|
-
:type cusip_id: str
|
|
693
|
+
:type cusip_id: str|int
|
|
693
694
|
:return: instrument
|
|
694
695
|
:rtype: request.Response
|
|
695
696
|
"""
|
|
@@ -52,13 +52,18 @@ class Stream:
|
|
|
52
52
|
print("Could not get streamerInfo")
|
|
53
53
|
|
|
54
54
|
# start the stream
|
|
55
|
+
start_time = datetime.now()
|
|
55
56
|
while True:
|
|
56
57
|
try:
|
|
58
|
+
start_time = datetime.now()
|
|
59
|
+
if self.verbose: print("Connecting to streaming server...")
|
|
57
60
|
async with websockets.connect(self._streamer_info.get('streamerSocketUrl'), ping_interval=None) as self._websocket:
|
|
58
|
-
if self.verbose: print("Connected to streaming server.")
|
|
59
|
-
|
|
60
61
|
# send login payload
|
|
61
|
-
login_payload = self.basic_request(service="ADMIN",
|
|
62
|
+
login_payload = self.basic_request(service="ADMIN",
|
|
63
|
+
command="LOGIN",
|
|
64
|
+
parameters={"Authorization": self._client.access_token,
|
|
65
|
+
"SchwabClientChannel": self._streamer_info.get("schwabClientChannel"),
|
|
66
|
+
"SchwabClientFunctionId": self._streamer_info.get("schwabClientFunctionId")})
|
|
62
67
|
await self._websocket.send(json.dumps(login_payload))
|
|
63
68
|
receiver_func(await self._websocket.recv(), *args, **kwargs)
|
|
64
69
|
self.active = True
|
|
@@ -67,7 +72,10 @@ class Stream:
|
|
|
67
72
|
for service, subs in self.subscriptions.items():
|
|
68
73
|
reqs = []
|
|
69
74
|
for key, fields in subs.items():
|
|
70
|
-
reqs.append(self.basic_request(service=service,
|
|
75
|
+
reqs.append(self.basic_request(service=service,
|
|
76
|
+
command="ADD",
|
|
77
|
+
parameters={"keys": key,
|
|
78
|
+
"fields": Stream._list_to_string(fields)}))
|
|
71
79
|
if reqs:
|
|
72
80
|
await self._websocket.send(json.dumps({"requests": reqs}))
|
|
73
81
|
receiver_func(await self._websocket.recv(), *args, **kwargs)
|
|
@@ -84,6 +92,9 @@ class Stream:
|
|
|
84
92
|
elif e is websockets.exceptions.ConnectionClosedError or str(e) == "no close frame received or sent": # catch no subscriptions kick
|
|
85
93
|
if self.verbose: print(f"Stream closed (likely no subscriptions): {e}")
|
|
86
94
|
break
|
|
95
|
+
elif (datetime.now() - start_time).seconds <= 90:
|
|
96
|
+
if self.verbose: print("Stream has crashed within 90 seconds, likely no subscriptions or invalid login.")
|
|
97
|
+
break
|
|
87
98
|
else: # stream has quit unexpectedly, try to reconnect
|
|
88
99
|
if self.verbose: print(f"{e}")
|
|
89
100
|
if self.verbose: print("Connection lost to server, reconnecting...")
|
|
@@ -100,7 +111,7 @@ class Stream:
|
|
|
100
111
|
|
|
101
112
|
self._thread = threading.Thread(target=_start_async, daemon=False)
|
|
102
113
|
self._thread.start()
|
|
103
|
-
|
|
114
|
+
# if the thread does not start in time then the main program may close before the streamer starts
|
|
104
115
|
else:
|
|
105
116
|
print("Stream already active.")
|
|
106
117
|
|
|
@@ -126,6 +137,8 @@ class Stream:
|
|
|
126
137
|
while True:
|
|
127
138
|
in_hours = (start <= datetime.now().time() <= end) and (0 <= datetime.now().weekday() <= 4)
|
|
128
139
|
if in_hours and not self.active:
|
|
140
|
+
if len(self.subscriptions) == 0:
|
|
141
|
+
if self.verbose: print("No subscriptions, starting stream anyways.")
|
|
129
142
|
self.start(receiver=receiver)
|
|
130
143
|
elif not in_hours and self.active:
|
|
131
144
|
if self.verbose: print("Stopping Stream.")
|
|
@@ -260,7 +273,7 @@ class Stream:
|
|
|
260
273
|
if type(ls) is str: return ls
|
|
261
274
|
elif type(ls) is list: return ",".join(map(str, ls))
|
|
262
275
|
|
|
263
|
-
def level_one_equities(self, keys, fields, command="ADD"):
|
|
276
|
+
def level_one_equities(self, keys: str | list, fields: str | list, command="ADD") -> dict:
|
|
264
277
|
"""
|
|
265
278
|
Level one equities
|
|
266
279
|
:param keys: list of keys to use (e.g. ["AMD", "INTC"])
|
|
@@ -274,7 +287,7 @@ class Stream:
|
|
|
274
287
|
"""
|
|
275
288
|
return self.basic_request("LEVELONE_EQUITIES", command, parameters={"keys": Stream._list_to_string(keys), "fields": Stream._list_to_string(fields)})
|
|
276
289
|
|
|
277
|
-
def level_one_options(self, keys, fields, command="ADD"):
|
|
290
|
+
def level_one_options(self, keys: str | list, fields: str | list, command="ADD") -> dict:
|
|
278
291
|
"""
|
|
279
292
|
Level one options, key format: [Underlying Symbol (6 characters including spaces) | Expiration (6 characters) | Call/Put (1 character) | Strike Price (5+3=8 characters)]
|
|
280
293
|
:param keys: list of keys to use (e.g. ["GOOG 240809C00095000", "AAPL 240517P00190000"])
|
|
@@ -288,7 +301,7 @@ class Stream:
|
|
|
288
301
|
"""
|
|
289
302
|
return self.basic_request("LEVELONE_OPTIONS", command, parameters={"keys": Stream._list_to_string(keys), "fields": Stream._list_to_string(fields)})
|
|
290
303
|
|
|
291
|
-
def level_one_futures(self, keys, fields, command="ADD"):
|
|
304
|
+
def level_one_futures(self, keys: str | list, fields: str | list, command="ADD") -> dict:
|
|
292
305
|
"""
|
|
293
306
|
Level one futures, key format: '/' + 'root symbol' + 'month code' + 'year code'; month code is 1 character: (F: Jan, G: Feb, H: Mar, J: Apr, K: May, M: Jun, N: Jul, Q: Aug, U: Sep, V: Oct, X: Nov, Z: Dec), year code is 2 characters (i.e. 2024 = 24)
|
|
294
307
|
:param keys: list of keys to use (e.g. ["/ESF24", "/GCG24"])
|
|
@@ -302,7 +315,7 @@ class Stream:
|
|
|
302
315
|
"""
|
|
303
316
|
return self.basic_request("LEVELONE_FUTURES", command, parameters={"keys": Stream._list_to_string(keys), "fields": Stream._list_to_string(fields)})
|
|
304
317
|
|
|
305
|
-
def level_one_futures_options(self, keys, fields, command="ADD"):
|
|
318
|
+
def level_one_futures_options(self, keys: str | list, fields: str | list, command="ADD") -> dict:
|
|
306
319
|
"""
|
|
307
320
|
Level one futures options, key format: '.' + '/' + 'root symbol' + 'month code' + 'year code' + 'Call/Put code' + 'Strike Price'
|
|
308
321
|
:param keys: list of keys to use (e.g. ["./OZCZ23C565"])
|
|
@@ -316,7 +329,7 @@ class Stream:
|
|
|
316
329
|
"""
|
|
317
330
|
return self.basic_request("LEVELONE_FUTURES_OPTIONS", command, parameters={"keys": Stream._list_to_string(keys), "fields": Stream._list_to_string(fields)})
|
|
318
331
|
|
|
319
|
-
def level_one_forex(self, keys, fields, command="ADD"):
|
|
332
|
+
def level_one_forex(self, keys: str | list, fields: str | list, command="ADD") -> dict:
|
|
320
333
|
"""
|
|
321
334
|
Level one forex, key format: 'from currency' + '/' + 'to currency'
|
|
322
335
|
:param keys: list of keys to use (e.g. ["EUR/USD", "JPY/USD"])
|
|
@@ -330,7 +343,7 @@ class Stream:
|
|
|
330
343
|
"""
|
|
331
344
|
return self.basic_request("LEVELONE_FOREX", command, parameters={"keys": Stream._list_to_string(keys), "fields": Stream._list_to_string(fields)})
|
|
332
345
|
|
|
333
|
-
def nyse_book(self, keys, fields, command="ADD"):
|
|
346
|
+
def nyse_book(self, keys: str | list, fields: str | list, command="ADD") -> dict:
|
|
334
347
|
"""
|
|
335
348
|
NYSE book orders
|
|
336
349
|
:param keys: list of keys to use (e.g. ["NIO", "F"])
|
|
@@ -344,7 +357,7 @@ class Stream:
|
|
|
344
357
|
"""
|
|
345
358
|
return self.basic_request("NYSE_BOOK", command, parameters={"keys": Stream._list_to_string(keys), "fields": Stream._list_to_string(fields)})
|
|
346
359
|
|
|
347
|
-
def nasdaq_book(self, keys, fields, command="ADD"):
|
|
360
|
+
def nasdaq_book(self, keys: str | list, fields: str | list, command="ADD") -> dict:
|
|
348
361
|
"""
|
|
349
362
|
NASDAQ book orders
|
|
350
363
|
:param keys: list of keys to use (e.g. ["AMD", "CRWD"])
|
|
@@ -358,7 +371,7 @@ class Stream:
|
|
|
358
371
|
"""
|
|
359
372
|
return self.basic_request("NASDAQ_BOOK", command, parameters={"keys": Stream._list_to_string(keys), "fields": Stream._list_to_string(fields)})
|
|
360
373
|
|
|
361
|
-
def options_book(self, keys, fields, command="ADD"):
|
|
374
|
+
def options_book(self, keys: str | list, fields: str | list, command="ADD") -> dict:
|
|
362
375
|
"""
|
|
363
376
|
Options book orders
|
|
364
377
|
:param keys: list of keys to use (e.g. ["GOOG 240809C00095000", "AAPL 240517P00190000"])
|
|
@@ -372,7 +385,7 @@ class Stream:
|
|
|
372
385
|
"""
|
|
373
386
|
return self.basic_request("OPTIONS_BOOK", command, parameters={"keys": Stream._list_to_string(keys), "fields": Stream._list_to_string(fields)})
|
|
374
387
|
|
|
375
|
-
def chart_equity(self, keys, fields, command="ADD"):
|
|
388
|
+
def chart_equity(self, keys: str | list, fields: str | list, command="ADD") -> dict:
|
|
376
389
|
"""
|
|
377
390
|
Chart equity
|
|
378
391
|
:param keys: list of keys to use (e.g. ["GOOG", "AAPL"])
|
|
@@ -386,7 +399,7 @@ class Stream:
|
|
|
386
399
|
"""
|
|
387
400
|
return self.basic_request("CHART_EQUITY", command, parameters={"keys": Stream._list_to_string(keys), "fields": Stream._list_to_string(fields)})
|
|
388
401
|
|
|
389
|
-
def chart_futures(self, keys, fields, command="ADD"):
|
|
402
|
+
def chart_futures(self, keys: str | list, fields: str | list, command="ADD") -> dict:
|
|
390
403
|
"""
|
|
391
404
|
Chart futures, key format: '/' + 'root symbol' + 'month code' + 'year code'; month code is 1 character: (F: Jan, G: Feb, H: Mar, J: Apr, K: May, M: Jun, N: Jul, Q: Aug, U: Sep, V: Oct, X: Nov, Z: Dec), year code is 2 characters (i.e. 2024 = 24)
|
|
392
405
|
:param keys: list of keys to use (e.g. ["/ESF24", "/GCG24"])
|
|
@@ -400,7 +413,7 @@ class Stream:
|
|
|
400
413
|
"""
|
|
401
414
|
return self.basic_request("CHART_FUTURES", command, parameters={"keys": Stream._list_to_string(keys), "fields": Stream._list_to_string(fields)})
|
|
402
415
|
|
|
403
|
-
def screener_equity(self, keys, fields, command="ADD"):
|
|
416
|
+
def screener_equity(self, keys: str | list, fields: str | list, command="ADD") -> dict:
|
|
404
417
|
"""
|
|
405
418
|
Screener equity, key format: (PREFIX)_(SORTFIELD)_(FREQUENCY); Prefix: ($COMPX, $DJI, $SPX.X, INDEX_AL, NYSE, NASDAQ, OTCBB, EQUITY_ALL); Sortfield: (VOLUME, TRADES, PERCENT_CHANGE_UP, PERCENT_CHANGE_DOWN, AVERAGE_PERCENT_VOLUME), Frequency: (0 (all day), 1, 5, 10, 30 60)
|
|
406
419
|
:param keys: list of keys to use (e.g. ["$DJI_PERCENT_CHANGE_UP_60", "NASDAQ_VOLUME_30"])
|
|
@@ -414,7 +427,7 @@ class Stream:
|
|
|
414
427
|
"""
|
|
415
428
|
return self.basic_request("SCREENER_EQUITY", command, parameters={"keys": Stream._list_to_string(keys), "fields": Stream._list_to_string(fields)})
|
|
416
429
|
|
|
417
|
-
def
|
|
430
|
+
def screener_options(self, keys: str | list, fields: str | list, command="ADD") -> dict:
|
|
418
431
|
"""
|
|
419
432
|
Screener option key format: (PREFIX)_(SORTFIELD)_(FREQUENCY); Prefix: (OPTION_PUT, OPTION_CALL, OPTION_ALL); Sortfield: (VOLUME, TRADES, PERCENT_CHANGE_UP, PERCENT_CHANGE_DOWN, AVERAGE_PERCENT_VOLUME), Frequency: (0 (all day), 1, 5, 10, 30 60)
|
|
420
433
|
:param keys: list of keys to use (e.g. ["OPTION_PUT_PERCENT_CHANGE_UP_60", "OPTION_CALL_TRADES_30"])
|
|
@@ -428,7 +441,7 @@ class Stream:
|
|
|
428
441
|
"""
|
|
429
442
|
return self.basic_request("SCREENER_OPTION", command, parameters={"keys": Stream._list_to_string(keys), "fields": Stream._list_to_string(fields)})
|
|
430
443
|
|
|
431
|
-
def account_activity(self, keys="Account Activity", fields="0,1,2,3", command="SUBS"):
|
|
444
|
+
def account_activity(self, keys="Account Activity", fields="0,1,2,3", command="SUBS") -> dict:
|
|
432
445
|
"""
|
|
433
446
|
Account activity
|
|
434
447
|
:param keys: list of keys to use (e.g. ["Account Activity"])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: schwabdev
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: An easy and lightweight wrapper for using the Charles Schwab API.
|
|
5
5
|
Author: Tyler Bowers
|
|
6
6
|
Author-email: tylerebowers@gmail.com
|
|
@@ -19,6 +19,8 @@ Classifier: Natural Language :: English
|
|
|
19
19
|
Requires-Python: >=3.11
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE.txt
|
|
22
|
+
Requires-Dist: requests
|
|
23
|
+
Requires-Dist: websockets
|
|
22
24
|
|
|
23
25
|
# Schwab-API-Python
|
|
24
26
|
   [](https://www.paypal.com/donate/?business=8VDFKHMBFSC2Q&no_recurring=0¤cy_code=USD) 
|
|
@@ -54,6 +56,7 @@ print(client.account_linked().json()) #make api calls
|
|
|
54
56
|
- Functions for all api functions (examples in `examples/api_demo.py`)
|
|
55
57
|
- Stream real-time data with a customizable response handler (examples in `examples/stream_demo.py`)
|
|
56
58
|
### TBD
|
|
59
|
+
- Paper trading client
|
|
57
60
|
- Automatic refresh token updates. (Waiting for Schwab implementation)
|
|
58
61
|
### Notes
|
|
59
62
|
The schwabdev folder contains code for main operations:
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This file is used to print colored text
|
|
3
|
-
Github: https://github.com/tylerebowers/Schwab-API-Python
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def info(string, end="\n"): print(f"\033[92m{'[INFO]: '}\033[00m{string}", end=end)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def warning(string, end="\n"): print(f"\033[93m{'[WARN]: '}\033[00m{string}", end=end)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def error(string, end="\n"): print(f"\033[91m{'[ERROR]: '}\033[00m{string}", end=end)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def user(string, end="\n"): print(f"\033[94m{'[USER]: '}\033[00m{string}", end=end)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def user_input(string): return input(f"\033[94m{'[INPUT]: '}\033[00m{string}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|