fidelity-api 0.0.3__tar.gz → 0.0.5__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.3/fidelity_api.egg-info → fidelity-api-0.0.5}/PKG-INFO +1 -1
- {fidelity-api-0.0.3 → fidelity-api-0.0.5}/fidelity/fidelity.py +127 -96
- {fidelity-api-0.0.3 → fidelity-api-0.0.5/fidelity_api.egg-info}/PKG-INFO +1 -1
- {fidelity-api-0.0.3 → fidelity-api-0.0.5}/setup.py +1 -1
- {fidelity-api-0.0.3 → fidelity-api-0.0.5}/LICENSE +0 -0
- {fidelity-api-0.0.3 → fidelity-api-0.0.5}/README.md +0 -0
- {fidelity-api-0.0.3 → fidelity-api-0.0.5}/fidelity/__init__.py +0 -0
- {fidelity-api-0.0.3 → fidelity-api-0.0.5}/fidelity_api.egg-info/SOURCES.txt +0 -0
- {fidelity-api-0.0.3 → fidelity-api-0.0.5}/fidelity_api.egg-info/dependency_links.txt +0 -0
- {fidelity-api-0.0.3 → fidelity-api-0.0.5}/fidelity_api.egg-info/requires.txt +0 -0
- {fidelity-api-0.0.3 → fidelity-api-0.0.5}/fidelity_api.egg-info/top_level.txt +0 -0
- {fidelity-api-0.0.3 → fidelity-api-0.0.5}/setup.cfg +0 -0
|
@@ -263,103 +263,131 @@ class FidelityAutomation:
|
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
```
|
|
266
|
+
None
|
|
267
|
+
If an error occured
|
|
266
268
|
"""
|
|
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
|
|
269
|
+
try:
|
|
270
|
+
# Go to positions page
|
|
271
|
+
self.page.wait_for_load_state(state="load")
|
|
272
|
+
self.page.goto("https://digital.fidelity.com/ftgw/digital/portfolio/positions")
|
|
329
273
|
|
|
330
|
-
#
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
last_price = 0
|
|
274
|
+
# This double wait is necessary. If you remove it, I'll kill you
|
|
275
|
+
self.wait_for_loading_sign()
|
|
276
|
+
self.page.wait_for_timeout(1000)
|
|
277
|
+
self.wait_for_loading_sign()
|
|
278
|
+
|
|
279
|
+
# Download the positions as a csv #
|
|
280
|
+
# See if new UI is present
|
|
281
|
+
new_ui = True
|
|
339
282
|
try:
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
283
|
+
self.page.get_by_role("button", name="Available Actions").click(timeout=2000)
|
|
284
|
+
with self.page.expect_download() as download_info:
|
|
285
|
+
self.page.get_by_role("menuitem", name="Download").click()
|
|
286
|
+
download = download_info.value
|
|
287
|
+
except PlaywrightTimeoutError:
|
|
288
|
+
new_ui = False
|
|
289
|
+
if not new_ui:
|
|
290
|
+
try:
|
|
291
|
+
# Use the old UI
|
|
292
|
+
with self.page.expect_download() as download_info:
|
|
293
|
+
self.page.get_by_label("Download Positions").click()
|
|
294
|
+
download = download_info.value
|
|
295
|
+
except PlaywrightTimeoutError:
|
|
296
|
+
return None
|
|
297
|
+
# Get absolute path to file
|
|
298
|
+
cur = os.getcwd()
|
|
299
|
+
positions_csv = os.path.join(cur, download.suggested_filename)
|
|
300
|
+
# Create a copy to work on with the proper file name known
|
|
301
|
+
download.save_as(positions_csv)
|
|
302
|
+
|
|
303
|
+
csv_file = open(positions_csv, newline="", encoding="utf-8-sig")
|
|
304
|
+
|
|
305
|
+
reader = csv.DictReader(csv_file)
|
|
306
|
+
# Ensure all fields we want are present
|
|
307
|
+
required_elements = [
|
|
308
|
+
"Account Number",
|
|
309
|
+
"Account Name",
|
|
310
|
+
"Symbol",
|
|
311
|
+
"Description",
|
|
312
|
+
"Quantity",
|
|
313
|
+
"Last Price",
|
|
314
|
+
"Last Price Change",
|
|
315
|
+
"Current Value",
|
|
316
|
+
]
|
|
317
|
+
intersection_set = set(reader.fieldnames).intersection(set(required_elements))
|
|
318
|
+
if len(intersection_set) != len(required_elements):
|
|
319
|
+
raise Exception("Not enough elements in fidelity positions csv")
|
|
320
|
+
|
|
321
|
+
for row in reader:
|
|
322
|
+
# Skip empty rows
|
|
323
|
+
if row["Account Number"] is None:
|
|
324
|
+
continue
|
|
325
|
+
# Last couple of rows have some disclaimers, filter those out
|
|
326
|
+
if "and" in row["Account Number"]:
|
|
327
|
+
break
|
|
328
|
+
# Skip accounts that start with 'Y' (Fidelity managed)
|
|
329
|
+
if row["Account Number"][0] == "Y":
|
|
330
|
+
continue
|
|
331
|
+
# Get the value and remove '$' from it
|
|
332
|
+
cur_val = str(row["Current Value"]).replace("$", "").replace("-", "")
|
|
333
|
+
# Get the last price
|
|
334
|
+
last_price = str(row["Last Price"]).replace("$", "").replace("-", "")
|
|
335
|
+
# Get the last price change
|
|
336
|
+
last_price_change = str(row["Last Price Change"]).replace("$", "")
|
|
337
|
+
# Get quantity
|
|
338
|
+
quantity = str(row["Quantity"]).replace("-", "")
|
|
339
|
+
# Get ticker
|
|
340
|
+
ticker = str(row["Symbol"])
|
|
341
|
+
|
|
342
|
+
# Catch any pending activity with special handling
|
|
343
|
+
if "Pending" in ticker:
|
|
344
|
+
cur_val = last_price_change
|
|
345
|
+
# If the value isn't present, move to next row
|
|
346
|
+
if len(cur_val) == 0:
|
|
347
|
+
continue
|
|
348
|
+
# If the last price isn't available, just use the current value
|
|
349
|
+
if len(last_price) == 0:
|
|
350
|
+
last_price = cur_val
|
|
351
|
+
# If the quantity is missing set it to 1 (For SPAXX or any other cash position)
|
|
352
|
+
if len(quantity) == 0:
|
|
353
|
+
quantity = 1
|
|
354
|
+
|
|
355
|
+
# Check for anything that isn't a number
|
|
356
|
+
try:
|
|
357
|
+
float(cur_val)
|
|
358
|
+
except ValueError:
|
|
359
|
+
cur_val = 0
|
|
360
|
+
try:
|
|
361
|
+
float(last_price)
|
|
362
|
+
except ValueError:
|
|
363
|
+
last_price = 0
|
|
364
|
+
try:
|
|
365
|
+
float(quantity)
|
|
366
|
+
except ValueError:
|
|
367
|
+
quantity = 0
|
|
368
|
+
|
|
369
|
+
# Create list of dictionary for stock found
|
|
370
|
+
stock_list = [create_stock_dict(ticker, float(quantity), float(last_price), float(cur_val))]
|
|
371
|
+
# Try setting in the account dict without overwrite
|
|
372
|
+
if not self.set_account_dict(
|
|
373
|
+
account_num=row["Account Number"],
|
|
374
|
+
balance=float(cur_val),
|
|
375
|
+
nickname=row["Account Name"],
|
|
376
|
+
stocks=stock_list,
|
|
377
|
+
overwrite=False,
|
|
378
|
+
):
|
|
379
|
+
# If the account exists already, add to it
|
|
380
|
+
self.add_stock_to_account_dict(row["Account Number"], stock_list[0])
|
|
356
381
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
382
|
+
# Close the file
|
|
383
|
+
csv_file.close()
|
|
384
|
+
# Delete the file
|
|
385
|
+
os.remove(positions_csv)
|
|
361
386
|
|
|
362
|
-
|
|
387
|
+
return self.account_dict
|
|
388
|
+
except Exception as e:
|
|
389
|
+
print(f"Error in getAccountInfo: {e}")
|
|
390
|
+
return None
|
|
363
391
|
|
|
364
392
|
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
393
|
"""
|
|
@@ -414,8 +442,8 @@ class FidelityAutomation:
|
|
|
414
442
|
|
|
415
443
|
# Use the info given
|
|
416
444
|
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,
|
|
445
|
+
"balance": round(balance, 2) if balance is not None else 0.0,
|
|
446
|
+
"withdrawal_balance": round(withdrawal_balance, 2) if withdrawal_balance is not None else 0.0,
|
|
419
447
|
"nickname": nickname,
|
|
420
448
|
"stocks": stocks if stocks is not None else []
|
|
421
449
|
}
|
|
@@ -440,10 +468,10 @@ class FidelityAutomation:
|
|
|
440
468
|
if account_num in self.account_dict:
|
|
441
469
|
if overwrite:
|
|
442
470
|
self.account_dict[account_num]["stocks"] = [stock]
|
|
443
|
-
self.account_dict[account_num]["balance"] = stock["value"]
|
|
471
|
+
self.account_dict[account_num]["balance"] = round(stock["value"], 2)
|
|
444
472
|
else:
|
|
445
473
|
self.account_dict[account_num]["stocks"].append(stock)
|
|
446
|
-
self.account_dict[account_num]["balance"] += stock["value"]
|
|
474
|
+
self.account_dict[account_num]["balance"] += round(stock["value"], 2)
|
|
447
475
|
return True
|
|
448
476
|
return False
|
|
449
477
|
|
|
@@ -1057,6 +1085,7 @@ class FidelityAutomation:
|
|
|
1057
1085
|
available_balance = float(available_balance.replace("$", "").replace(",", ""))
|
|
1058
1086
|
|
|
1059
1087
|
# Check if there's enough balance
|
|
1088
|
+
transfer_amount = round(transfer_amount, 2)
|
|
1060
1089
|
if transfer_amount > available_balance:
|
|
1061
1090
|
print(f"Insufficient funds. Available: ${available_balance}, Attempted transfer: ${transfer_amount}")
|
|
1062
1091
|
return False
|
|
@@ -1244,6 +1273,7 @@ class FidelityAutomation:
|
|
|
1244
1273
|
signs = [self.page.locator("div:nth-child(2) > .loading-spinner-mask-after").first,
|
|
1245
1274
|
self.page.locator(".pvd-spinner__mask-inner").first,
|
|
1246
1275
|
self.page.locator("pvd-loading-spinner").first,
|
|
1276
|
+
self.page.locator(".pvd3-spinner-root > .pvd-spinner__spinner > .pvd-spinner__visual > div > .pvd-spinner__mask-inner").first,
|
|
1247
1277
|
]
|
|
1248
1278
|
for sign in signs:
|
|
1249
1279
|
sign.wait_for(timeout=timeout, state="hidden")
|
|
@@ -1291,6 +1321,7 @@ class FidelityAutomation:
|
|
|
1291
1321
|
|
|
1292
1322
|
# Click it
|
|
1293
1323
|
selected_entry.click()
|
|
1324
|
+
self.page.wait_for_timeout(50)
|
|
1294
1325
|
|
|
1295
1326
|
# Click the rename button
|
|
1296
1327
|
self.page.get_by_role("button", name="Rename").click()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|