fidelity-api 0.0.4__tar.gz → 0.0.6__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.
- {fidelity-api-0.0.4/fidelity_api.egg-info → fidelity-api-0.0.6}/PKG-INFO +2 -1
- {fidelity-api-0.0.4 → fidelity-api-0.0.6}/README.md +1 -0
- {fidelity-api-0.0.4 → fidelity-api-0.0.6}/fidelity/fidelity.py +142 -100
- {fidelity-api-0.0.4 → fidelity-api-0.0.6/fidelity_api.egg-info}/PKG-INFO +2 -1
- {fidelity-api-0.0.4 → fidelity-api-0.0.6}/setup.py +1 -1
- {fidelity-api-0.0.4 → fidelity-api-0.0.6}/LICENSE +0 -0
- {fidelity-api-0.0.4 → fidelity-api-0.0.6}/fidelity/__init__.py +0 -0
- {fidelity-api-0.0.4 → fidelity-api-0.0.6}/fidelity_api.egg-info/SOURCES.txt +0 -0
- {fidelity-api-0.0.4 → fidelity-api-0.0.6}/fidelity_api.egg-info/dependency_links.txt +0 -0
- {fidelity-api-0.0.4 → fidelity-api-0.0.6}/fidelity_api.egg-info/requires.txt +0 -0
- {fidelity-api-0.0.4 → fidelity-api-0.0.6}/fidelity_api.egg-info/top_level.txt +0 -0
- {fidelity-api-0.0.4 → fidelity-api-0.0.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fidelity-api
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: An unofficial API for Fidelity
|
|
5
5
|
Home-page: https://github.com/kennyboy106/fidelity-api
|
|
6
6
|
Author: Kenneth Tang
|
|
@@ -21,6 +21,7 @@ gather account positions, nickname accounts, etc.
|
|
|
21
21
|
|
|
22
22
|
Supports 2FA!
|
|
23
23
|
|
|
24
|
+
Made with the help of Jinhui Zhen
|
|
24
25
|
## Disclaimer
|
|
25
26
|
I am not a financial advisor and not affiliated with Fidelity in any way. Use this tool at your own risk. I am
|
|
26
27
|
not responsible for any losses or damages you may incur by using this project. This tool is provided as-is
|
|
@@ -10,6 +10,7 @@ gather account positions, nickname accounts, etc.
|
|
|
10
10
|
|
|
11
11
|
Supports 2FA!
|
|
12
12
|
|
|
13
|
+
Made with the help of Jinhui Zhen
|
|
13
14
|
## Disclaimer
|
|
14
15
|
I am not a financial advisor and not affiliated with Fidelity in any way. Use this tool at your own risk. I am
|
|
15
16
|
not responsible for any losses or damages you may incur by using this project. This tool is provided as-is
|
|
@@ -223,6 +223,8 @@ class FidelityAutomation:
|
|
|
223
223
|
-------
|
|
224
224
|
all_stock_dict (dict)
|
|
225
225
|
A dict of stocks that the account has.
|
|
226
|
+
The dict is keyed by stocks and only has the quantity.
|
|
227
|
+
`all_stock_dict[stock] = quantity (int)`
|
|
226
228
|
"""
|
|
227
229
|
if account_number in self.account_dict:
|
|
228
230
|
all_stock_dict = {}
|
|
@@ -253,7 +255,8 @@ class FidelityAutomation:
|
|
|
253
255
|
```
|
|
254
256
|
{
|
|
255
257
|
'balance': float: Total account balance
|
|
256
|
-
'
|
|
258
|
+
'nickname': str: The account nickname or default name
|
|
259
|
+
"withdrawal_balance": Use get_list_of_accounts() to populate
|
|
257
260
|
'stocks': list: A list of dictionaries for each stock found. The dict has:
|
|
258
261
|
{
|
|
259
262
|
'ticker': str: The ticker of the stock held
|
|
@@ -263,103 +266,132 @@ class FidelityAutomation:
|
|
|
263
266
|
}
|
|
264
267
|
}
|
|
265
268
|
```
|
|
269
|
+
None
|
|
270
|
+
If an error occurred
|
|
266
271
|
"""
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
# Download the positions as a csv
|
|
273
|
-
with self.page.expect_download() as download_info:
|
|
274
|
-
self.page.get_by_label("Download Positions").click()
|
|
275
|
-
download = download_info.value
|
|
276
|
-
cur = os.getcwd()
|
|
277
|
-
positions_csv = os.path.join(cur, download.suggested_filename)
|
|
278
|
-
# Create a copy to work on with the proper file name known
|
|
279
|
-
download.save_as(positions_csv)
|
|
280
|
-
|
|
281
|
-
csv_file = open(positions_csv, newline="", encoding="utf-8-sig")
|
|
282
|
-
|
|
283
|
-
reader = csv.DictReader(csv_file)
|
|
284
|
-
# Ensure all fields we want are present
|
|
285
|
-
required_elements = [
|
|
286
|
-
"Account Number",
|
|
287
|
-
"Account Name",
|
|
288
|
-
"Symbol",
|
|
289
|
-
"Description",
|
|
290
|
-
"Quantity",
|
|
291
|
-
"Last Price",
|
|
292
|
-
"Current Value",
|
|
293
|
-
]
|
|
294
|
-
intersection_set = set(reader.fieldnames).intersection(set(required_elements))
|
|
295
|
-
if len(intersection_set) != len(required_elements):
|
|
296
|
-
raise Exception("Not enough elements in fidelity positions csv")
|
|
297
|
-
|
|
298
|
-
for row in reader:
|
|
299
|
-
# Skip empty rows
|
|
300
|
-
if row["Account Number"] is None:
|
|
301
|
-
continue
|
|
302
|
-
# Last couple of rows have some disclaimers, filter those out
|
|
303
|
-
if "and" in row["Account Number"]:
|
|
304
|
-
break
|
|
305
|
-
# Skip accounts that start with 'Y' (Fidelity managed)
|
|
306
|
-
if row["Account Number"][0] == "Y":
|
|
307
|
-
continue
|
|
308
|
-
# Get the value and remove '$' from it
|
|
309
|
-
val = str(row["Current Value"]).replace("$", "").replace("-", "")
|
|
310
|
-
# Get the last price
|
|
311
|
-
last_price = str(row["Last Price"]).replace("$", "").replace("-", "")
|
|
312
|
-
# Get quantity
|
|
313
|
-
quantity = str(row["Quantity"]).replace("-", "")
|
|
314
|
-
# Get ticker
|
|
315
|
-
ticker = str(row["Symbol"])
|
|
316
|
-
|
|
317
|
-
# Don't include this if present
|
|
318
|
-
if "Pending" in ticker:
|
|
319
|
-
continue
|
|
320
|
-
# If the value isn't present, move to next row
|
|
321
|
-
if len(val) == 0:
|
|
322
|
-
continue
|
|
323
|
-
# If the last price isn't available, just use the current value
|
|
324
|
-
if len(last_price) == 0:
|
|
325
|
-
last_price = val
|
|
326
|
-
# If the quantity is missing set it to 1 (For SPAXX or any other cash position)
|
|
327
|
-
if len(quantity) == 0:
|
|
328
|
-
quantity = 1
|
|
272
|
+
try:
|
|
273
|
+
# Go to positions page
|
|
274
|
+
self.page.wait_for_load_state(state="load")
|
|
275
|
+
self.page.goto("https://digital.fidelity.com/ftgw/digital/portfolio/positions")
|
|
329
276
|
|
|
330
|
-
#
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
last_price = 0
|
|
277
|
+
# This double wait is necessary. If you remove it, I'll kill you
|
|
278
|
+
self.wait_for_loading_sign()
|
|
279
|
+
self.page.wait_for_timeout(1000)
|
|
280
|
+
self.wait_for_loading_sign()
|
|
281
|
+
|
|
282
|
+
# Download the positions as a csv #
|
|
283
|
+
# See if new UI is present
|
|
284
|
+
new_ui = True
|
|
339
285
|
try:
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
286
|
+
self.page.get_by_role("button", name="Available Actions").click(timeout=8000)
|
|
287
|
+
with self.page.expect_download() as download_info:
|
|
288
|
+
self.page.get_by_role("menuitem", name="Download").click()
|
|
289
|
+
download = download_info.value
|
|
290
|
+
except PlaywrightTimeoutError:
|
|
291
|
+
new_ui = False
|
|
292
|
+
if not new_ui:
|
|
293
|
+
try:
|
|
294
|
+
# Use the old UI
|
|
295
|
+
with self.page.expect_download() as download_info:
|
|
296
|
+
self.page.get_by_label("Download Positions").click(timeout=8000)
|
|
297
|
+
download = download_info.value
|
|
298
|
+
except PlaywrightTimeoutError:
|
|
299
|
+
print("Could not get positions csv")
|
|
300
|
+
return None
|
|
301
|
+
# Get absolute path to file
|
|
302
|
+
cur = os.getcwd()
|
|
303
|
+
positions_csv = os.path.join(cur, download.suggested_filename)
|
|
304
|
+
# Create a copy to work on with the proper file name known
|
|
305
|
+
download.save_as(positions_csv)
|
|
306
|
+
|
|
307
|
+
csv_file = open(positions_csv, newline="", encoding="utf-8-sig")
|
|
308
|
+
|
|
309
|
+
reader = csv.DictReader(csv_file)
|
|
310
|
+
# Ensure all fields we want are present
|
|
311
|
+
required_elements = [
|
|
312
|
+
"Account Number",
|
|
313
|
+
"Account Name",
|
|
314
|
+
"Symbol",
|
|
315
|
+
"Description",
|
|
316
|
+
"Quantity",
|
|
317
|
+
"Last Price",
|
|
318
|
+
"Last Price Change",
|
|
319
|
+
"Current Value",
|
|
320
|
+
]
|
|
321
|
+
intersection_set = set(reader.fieldnames).intersection(set(required_elements))
|
|
322
|
+
if len(intersection_set) != len(required_elements):
|
|
323
|
+
raise Exception("Not enough elements in fidelity positions csv")
|
|
324
|
+
|
|
325
|
+
for row in reader:
|
|
326
|
+
# Skip empty rows
|
|
327
|
+
if row["Account Number"] is None:
|
|
328
|
+
continue
|
|
329
|
+
# Last couple of rows have some disclaimers, filter those out
|
|
330
|
+
if "and" in row["Account Number"]:
|
|
331
|
+
break
|
|
332
|
+
# Skip accounts that start with 'Y' (Fidelity managed)
|
|
333
|
+
if row["Account Number"][0] == "Y":
|
|
334
|
+
continue
|
|
335
|
+
# Get the value and remove '$' from it
|
|
336
|
+
cur_val = str(row["Current Value"]).replace("$", "").replace("-", "")
|
|
337
|
+
# Get the last price
|
|
338
|
+
last_price = str(row["Last Price"]).replace("$", "").replace("-", "")
|
|
339
|
+
# Get the last price change
|
|
340
|
+
last_price_change = str(row["Last Price Change"]).replace("$", "")
|
|
341
|
+
# Get quantity
|
|
342
|
+
quantity = str(row["Quantity"]).replace("-", "")
|
|
343
|
+
# Get ticker
|
|
344
|
+
ticker = str(row["Symbol"])
|
|
345
|
+
|
|
346
|
+
# Catch any pending activity with special handling
|
|
347
|
+
if "Pending" in ticker:
|
|
348
|
+
cur_val = last_price_change
|
|
349
|
+
# If the value isn't present, move to next row
|
|
350
|
+
if len(cur_val) == 0:
|
|
351
|
+
continue
|
|
352
|
+
# If the last price isn't available, just use the current value
|
|
353
|
+
if len(last_price) == 0:
|
|
354
|
+
last_price = cur_val
|
|
355
|
+
# If the quantity is missing set it to 1 (For SPAXX or any other cash position)
|
|
356
|
+
if len(quantity) == 0:
|
|
357
|
+
quantity = 1
|
|
358
|
+
|
|
359
|
+
# Check for anything that isn't a number
|
|
360
|
+
try:
|
|
361
|
+
float(cur_val)
|
|
362
|
+
except ValueError:
|
|
363
|
+
cur_val = 0
|
|
364
|
+
try:
|
|
365
|
+
float(last_price)
|
|
366
|
+
except ValueError:
|
|
367
|
+
last_price = 0
|
|
368
|
+
try:
|
|
369
|
+
float(quantity)
|
|
370
|
+
except ValueError:
|
|
371
|
+
quantity = 0
|
|
372
|
+
|
|
373
|
+
# Create list of dictionary for stock found
|
|
374
|
+
stock_list = [create_stock_dict(ticker, float(quantity), float(last_price), float(cur_val))]
|
|
375
|
+
# Try setting in the account dict without overwrite
|
|
376
|
+
if not self.set_account_dict(
|
|
377
|
+
account_num=row["Account Number"],
|
|
378
|
+
balance=float(cur_val),
|
|
379
|
+
nickname=row["Account Name"],
|
|
380
|
+
stocks=stock_list,
|
|
381
|
+
overwrite=False,
|
|
382
|
+
):
|
|
383
|
+
# If the account exists already, add to it
|
|
384
|
+
self.add_stock_to_account_dict(row["Account Number"], stock_list[0])
|
|
356
385
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
386
|
+
# Close the file
|
|
387
|
+
csv_file.close()
|
|
388
|
+
# Delete the file
|
|
389
|
+
os.remove(positions_csv)
|
|
361
390
|
|
|
362
|
-
|
|
391
|
+
return self.account_dict
|
|
392
|
+
except Exception as e:
|
|
393
|
+
print(f"Error in getAccountInfo: {e}")
|
|
394
|
+
return None
|
|
363
395
|
|
|
364
396
|
def set_account_dict(self, account_num: str, balance: float = None, withdrawal_balance: float = None, nickname: str = None, stocks: list = None, overwrite: bool = False):
|
|
365
397
|
"""
|
|
@@ -414,8 +446,8 @@ class FidelityAutomation:
|
|
|
414
446
|
|
|
415
447
|
# Use the info given
|
|
416
448
|
self.account_dict[account_num] = {
|
|
417
|
-
"balance": balance if balance is not None else 0.0,
|
|
418
|
-
"withdrawal_balance": withdrawal_balance if withdrawal_balance is not None else 0.0,
|
|
449
|
+
"balance": round(balance, 2) if balance is not None else 0.0,
|
|
450
|
+
"withdrawal_balance": round(withdrawal_balance, 2) if withdrawal_balance is not None else 0.0,
|
|
419
451
|
"nickname": nickname,
|
|
420
452
|
"stocks": stocks if stocks is not None else []
|
|
421
453
|
}
|
|
@@ -440,10 +472,10 @@ class FidelityAutomation:
|
|
|
440
472
|
if account_num in self.account_dict:
|
|
441
473
|
if overwrite:
|
|
442
474
|
self.account_dict[account_num]["stocks"] = [stock]
|
|
443
|
-
self.account_dict[account_num]["balance"] = stock["value"]
|
|
475
|
+
self.account_dict[account_num]["balance"] = round(stock["value"], 2)
|
|
444
476
|
else:
|
|
445
477
|
self.account_dict[account_num]["stocks"].append(stock)
|
|
446
|
-
self.account_dict[account_num]["balance"] += stock["value"]
|
|
478
|
+
self.account_dict[account_num]["balance"] += round(stock["value"], 2)
|
|
447
479
|
return True
|
|
448
480
|
return False
|
|
449
481
|
|
|
@@ -733,10 +765,13 @@ class FidelityAutomation:
|
|
|
733
765
|
|
|
734
766
|
def transaction(self, stock: str, quantity: float, action: str, account: str, dry: bool = True) -> bool:
|
|
735
767
|
"""
|
|
736
|
-
Process an order (transaction) using the dedicated trading page.
|
|
768
|
+
Process an order (transaction) using the dedicated trading page. Support extended hour trading.
|
|
737
769
|
|
|
738
770
|
`NOTE`: If you use this function repeatedly but change the stock between ANY call,
|
|
739
|
-
RELOAD the page before calling this
|
|
771
|
+
RELOAD the page before calling this. You can do this like so:
|
|
772
|
+
```
|
|
773
|
+
FidelityAutomation.page.reload()
|
|
774
|
+
```
|
|
740
775
|
|
|
741
776
|
For buying:
|
|
742
777
|
If the price of the security is below $1, it will choose limit order and go off of the last price + a little
|
|
@@ -984,7 +1019,6 @@ class FidelityAutomation:
|
|
|
984
1019
|
# If new account is found, collect and return
|
|
985
1020
|
if new_dict_acc not in old_dict:
|
|
986
1021
|
self.new_account_number = new_dict_acc
|
|
987
|
-
print(self.new_account_number)
|
|
988
1022
|
return True
|
|
989
1023
|
|
|
990
1024
|
# No new account number was found, return false
|
|
@@ -1245,6 +1279,7 @@ class FidelityAutomation:
|
|
|
1245
1279
|
signs = [self.page.locator("div:nth-child(2) > .loading-spinner-mask-after").first,
|
|
1246
1280
|
self.page.locator(".pvd-spinner__mask-inner").first,
|
|
1247
1281
|
self.page.locator("pvd-loading-spinner").first,
|
|
1282
|
+
self.page.locator(".pvd3-spinner-root > .pvd-spinner__spinner > .pvd-spinner__visual > div > .pvd-spinner__mask-inner").first,
|
|
1248
1283
|
]
|
|
1249
1284
|
for sign in signs:
|
|
1250
1285
|
sign.wait_for(timeout=timeout, state="hidden")
|
|
@@ -1259,6 +1294,11 @@ class FidelityAutomation:
|
|
|
1259
1294
|
The account number for the account to be nicknamed. Ex: `Z12345678`
|
|
1260
1295
|
nickname (str)
|
|
1261
1296
|
The nickname to use
|
|
1297
|
+
|
|
1298
|
+
Returns
|
|
1299
|
+
-------
|
|
1300
|
+
Success (bool)
|
|
1301
|
+
True if successful, false otherwise
|
|
1262
1302
|
"""
|
|
1263
1303
|
try:
|
|
1264
1304
|
# Get to summary page
|
|
@@ -1291,7 +1331,9 @@ class FidelityAutomation:
|
|
|
1291
1331
|
return False
|
|
1292
1332
|
|
|
1293
1333
|
# Click it
|
|
1334
|
+
self.page.wait_for_timeout(500)
|
|
1294
1335
|
selected_entry.click()
|
|
1336
|
+
self.page.wait_for_timeout(500)
|
|
1295
1337
|
|
|
1296
1338
|
# Click the rename button
|
|
1297
1339
|
self.page.get_by_role("button", name="Rename").click()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fidelity-api
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: An unofficial API for Fidelity
|
|
5
5
|
Home-page: https://github.com/kennyboy106/fidelity-api
|
|
6
6
|
Author: Kenneth Tang
|
|
@@ -21,6 +21,7 @@ gather account positions, nickname accounts, etc.
|
|
|
21
21
|
|
|
22
22
|
Supports 2FA!
|
|
23
23
|
|
|
24
|
+
Made with the help of Jinhui Zhen
|
|
24
25
|
## Disclaimer
|
|
25
26
|
I am not a financial advisor and not affiliated with Fidelity in any way. Use this tool at your own risk. I am
|
|
26
27
|
not responsible for any losses or damages you may incur by using this project. This tool is provided as-is
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|