From 261d1e75ed0959e15a3f7db1127952544d7442ab Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Sat, 14 Oct 2023 13:11:40 -0400 Subject: [PATCH 01/36] add firsttrade --- autoRSA.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autoRSA.py b/autoRSA.py index d4ee3493..9d79c2d0 100644 --- a/autoRSA.py +++ b/autoRSA.py @@ -13,6 +13,7 @@ # Custom API libraries from fidelityAPI import * + from firstradeAPI import * from helperAPI import killDriver, stockOrder, updater from robinhoodAPI import * from schwabAPI import * @@ -28,7 +29,7 @@ # Global variables -SUPPORTED_BROKERS = ["fidelity", "robinhood", "schwab", "tastytrade", "tradier"] +SUPPORTED_BROKERS = ["fidelity", "firsttrade", "robinhood", "schwab", "tastytrade", "tradier"] DISCORD_BOT = False DOCKER_MODE = False DANGER_MODE = False From c1fd183ee8afb32f4f916e961b69c8500db74fbc Mon Sep 17 00:00:00 2001 From: Donald Ryan Gullett <45299186+MaxxRK@users.noreply.github.com> Date: Sun, 29 Oct 2023 17:27:48 -0500 Subject: [PATCH 02/36] finish firstrade --- .env.example | 4 ++ README.md | 6 ++ autoRSA.py | 5 +- firstradeAPI.py | 141 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 firstradeAPI.py diff --git a/.env.example b/.env.example index 47bfc5af..cce9a8eb 100644 --- a/.env.example +++ b/.env.example @@ -41,3 +41,7 @@ TRADIER= # Tastytrade # TASTYTRADE=TASTYTRADE_USERNAME:TASTYTRADE_PASSWORD TASTYTRADE= + +# Firstrade +# FIRSTRADE=FIRSTRADE_USERNAME:FIRSTRADE_PASSWORD:FIRSTRADE_PIN:PERSISTENT_SESSION +FIRSTRADE= \ No newline at end of file diff --git a/README.md b/README.md index c336fedf..01b95210 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,12 @@ Required `.env` variables: `.env` file format: - `TASTYTRADE=TASTYTRADE_USERNAME:TASTYTRADE_PASSWORD` +### Firstrade +Made by [MaxxRK](https://github.com/MaxxRK/) using the [firstrade-api](https://github.com/MaxxRK/firstrade-api). Go give them a ⭐ + +`.env` file format: +- `FIRSTRADE=FIRSTRADE_USERNAME:FIRSTRADE_PASSWORD:FIRSTRADE_PIN:PERSISTENT_SESSION` + ### 🤷‍♂️ Maybe future brokerages 🤷‍♀️ #### Ally Ally disabled their official API, so all Ally packages don't work. I am attempting to reverse engineer their API, which you can track [here](https://github.com/NelsonDane/ally-api). Once I get it working, I will add it to this project. diff --git a/autoRSA.py b/autoRSA.py index f54589be..a93c2576 100644 --- a/autoRSA.py +++ b/autoRSA.py @@ -19,6 +19,7 @@ from schwabAPI import * from tastyAPI import * from tradierAPI import * + from firstradeAPI import * except Exception as e: print(f"Error importing libraries: {e}") print("Please run 'pip install -r requirements.txt'") @@ -29,7 +30,7 @@ # Global variables -SUPPORTED_BROKERS = ["fidelity", "robinhood", "schwab", "tastytrade", "tradier"] +SUPPORTED_BROKERS = ["fidelity", "robinhood", "schwab", "tastytrade", "tradier", "firstrade"] DAY1_BROKERS = ["robinhood", "schwab", "tastytrade", "tradier"] DISCORD_BOT = False DOCKER_MODE = False @@ -42,6 +43,8 @@ def nicknames(broker): return "robinhood" if broker == "tasty": return "tastytrade" + if broker == "ft": + return "firstrade" return broker diff --git a/firstradeAPI.py b/firstradeAPI.py new file mode 100644 index 00000000..19c13db7 --- /dev/null +++ b/firstradeAPI.py @@ -0,0 +1,141 @@ +# Donald Ryan Gullett(MaxxRK) +# Firstrade API + +from firstrade import account as ft_account, symbols, order +import os +import pprint +from time import sleep +from dotenv import load_dotenv + +from helperAPI import Brokerage, printAndDiscord, printHoldings, stockOrder + + +def firstrade_init(FIRSTRADE_EXTERNAL=None): + # Initialize .env file + load_dotenv() + # Import Tradier account + if not os.getenv("FIRSTRADE") and FIRSTRADE_EXTERNAL is None: + print("Firstrade not found, skipping...") + return None + accounts = ( + os.environ["FIRSTRADE"].strip().split(",") + if FIRSTRADE_EXTERNAL is None + else FIRSTRADE_EXTERNAL.strip().split(",") + ) + # Log in to Firstrade account + print("Logging in to Firstrade...") + firstrade_obj = Brokerage("Firstrade") + for account in accounts: + index = accounts.index(account) + 1 + name = f"Firstrade {index}" + try: + account = account.split(":") + if account[3].lower() == "true": + persistent_session = True + else: + persistent_session = False + firstrade = ft_account.FTSession( + username=account[0], + password=account[1], + pin=account[2], + persistent_session=persistent_session + ) + account_info = ft_account.FTAccountData(firstrade) + account_list = account_info.account_numbers + print(f"The following Firstrade accounts were found: {account_list}") + print("Logged in to Firstrade!") + firstrade_obj.set_logged_in_object(name, firstrade) + for i, account in enumerate(account_list): + firstrade_obj.set_account_number(name, account) + firstrade_obj.set_account_totals( + name, account, str(account_info.account_balances[i]) + ) + except Exception as e: + print(f"Error logging in to Firstrade: {e}") + return None + return firstrade_obj + + +def firstrade_holdings(firstrade_o: Brokerage, loop=None): + # Get holdings on each account + for key in firstrade_o.get_account_numbers(): + for account in firstrade_o.get_account_numbers(key): + obj: Firstrade = firstrade_o.get_logged_in_objects(key) + try: + data = ft_account.FTAccountData(obj).get_positions(account=account) + for item in data: + sym = item + if sym == "": + sym = "Unknown" + qty = float(data[item]["quantity"]) + current_price = float(data[item]["price"]) + firstrade_o.set_holdings(key, account, sym, qty, current_price) + except Exception as e: + printAndDiscord(f"{key} {account}: Error getting holdings: {e}", loop) + continue + printHoldings(firstrade_o, loop) + + +def firstrade_transaction(firstrade_o: Brokerage, orderObj: stockOrder, loop=None): + print() + print("==============================") + print("Firstrade") + print("==============================") + print() + # Buy on each account + for s in orderObj.get_stocks(): + for key in firstrade_o.get_account_numbers(): + printAndDiscord( + f"{key} {orderObj.get_action()}ing {orderObj.get_amount()} {s} @ {orderObj.get_price()}", + loop, + ) + for account in firstrade_o.get_account_numbers(key): + obj: Firstrade = firstrade_o.get_logged_in_objects(key) + print(f"{key} Account: {account}") + # If DRY is True, don't actually make the transaction + if orderObj.get_dry(): + printAndDiscord( + "Running in DRY mode. No transactions will be made.", loop + ) + try: + symbol_data = symbols.SymbolQuote(obj, s) + if symbol_data.last < 1.00: + price_type = order.PriceType.LIMIT + if orderObj.get_action().capitalize() == "Buy": + price = symbol_data.bid + 0.01 + else: + price = symbol_data.ask - 0.01 + else: + price_type = order.PriceType.MARKET + price = 0.00 + ft_order = order.Order(obj) + ft_order.place_order( + account=account, + symbol=s, + order_type=price_type, + quantity=int(orderObj.get_amount()), + duration=order.Duration.DAY, + price=price, + dry_run=orderObj.get_dry(), + ) + print("The order verification produced the following messages: ") + pprint.pprint(ft_order.order_confirmation) + printAndDiscord( + f"{key} account {account}: The order verification was " + + "successful" + if ft_order.order_confirmation["success"] == 'Yes' + else "unsuccessful", + loop, + ) + if not ft_order.order_confirmation["success"] == 'Yes': + printAndDiscord( + f"{key} account {account}: The order verification produced the following messages: {ft_order.order_confirmation['actiondata']}", + loop, + ) + except Exception as e: + printAndDiscord( + f"{key} {account}: Error submitting order: {e}", loop + ) + continue + sleep(1) + print() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 47db2013..2332bbae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ schwab-api==0.2.3 selenium==4.12.0 tastytrade==6.1 webdriver-manager==4.0.0 +firstrade==0.0.4 From 2151826063d02e81cd9b216555be9522f3107dda Mon Sep 17 00:00:00 2001 From: Donald Ryan Gullett <45299186+MaxxRK@users.noreply.github.com> Date: Sun, 29 Oct 2023 18:29:44 -0500 Subject: [PATCH 03/36] add firstrade to dockerfile --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index bbea096d..c8cceed0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,6 +49,7 @@ COPY ./schwabAPI.py . COPY ./tradierAPI.py . COPY ./helperAPI.py . COPY ./tastyAPI.py . +COPY ./firstradeAPI.py . COPY ./entrypoint.sh . # Make the entrypoint executable From 5711ef92b7845b6ed7bb09db3c093e9fed627e35 Mon Sep 17 00:00:00 2001 From: Donald Ryan Gullett <45299186+MaxxRK@users.noreply.github.com> Date: Sun, 29 Oct 2023 19:04:49 -0500 Subject: [PATCH 04/36] remove duplicate import --- autoRSA.py | 1 - 1 file changed, 1 deletion(-) diff --git a/autoRSA.py b/autoRSA.py index a93c2576..8999303f 100644 --- a/autoRSA.py +++ b/autoRSA.py @@ -19,7 +19,6 @@ from schwabAPI import * from tastyAPI import * from tradierAPI import * - from firstradeAPI import * except Exception as e: print(f"Error importing libraries: {e}") print("Please run 'pip install -r requirements.txt'") From 80731e6762218403193eff9d98762f38fce39154 Mon Sep 17 00:00:00 2001 From: Donald Ryan Gullett <45299186+MaxxRK@users.noreply.github.com> Date: Mon, 30 Oct 2023 19:17:41 -0500 Subject: [PATCH 05/36] fix order --- .env.example | 10 +++++----- Dockerfile | 2 +- README.md | 12 ++++++------ autoRSA.py | 8 ++++---- firstradeAPI.py | 7 +------ requirements.txt | 2 +- 6 files changed, 18 insertions(+), 23 deletions(-) diff --git a/.env.example b/.env.example index cce9a8eb..83f48747 100644 --- a/.env.example +++ b/.env.example @@ -19,6 +19,10 @@ DANGER_MODE="false" # FIDELITY=FIDELITY_USERNAME:FIDELITY_PASSWORD FIDELITY= +# Firstrade +# FIRSTRADE=FIRSTRADE_USERNAME:FIRSTRADE_PASSWORD:FIRSTRADE_PIN +FIRSTRADE= + # Robinhood # If 2fa is enabled: # ROBINHOOD=ROBINHOOD_USERNAME:ROBINHOOD_PASSWORD:ROBINHOOD_TOTP:ROBINHOOD_IRA_1:ROBINHOOD_IRA_2 @@ -40,8 +44,4 @@ TRADIER= # Tastytrade # TASTYTRADE=TASTYTRADE_USERNAME:TASTYTRADE_PASSWORD -TASTYTRADE= - -# Firstrade -# FIRSTRADE=FIRSTRADE_USERNAME:FIRSTRADE_PASSWORD:FIRSTRADE_PIN:PERSISTENT_SESSION -FIRSTRADE= \ No newline at end of file +TASTYTRADE= \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c8cceed0..abff2add 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,12 +44,12 @@ RUN playwright install && \ # Grab needed files COPY ./autoRSA.py . COPY ./fidelityAPI.py . +COPY ./firstradeAPI.py . COPY ./robinhoodAPI.py . COPY ./schwabAPI.py . COPY ./tradierAPI.py . COPY ./helperAPI.py . COPY ./tastyAPI.py . -COPY ./firstradeAPI.py . COPY ./entrypoint.sh . # Make the entrypoint executable diff --git a/README.md b/README.md index 01b95210..5e5260a0 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,12 @@ Required `.env` variables: `.env` file format: - `FIDELITY=FIDELITY_USERNAME:FIDELITY_PASSWORD` +### Firstrade +Made by [MaxxRK](https://github.com/MaxxRK/) using the [firstrade-api](https://github.com/MaxxRK/firstrade-api). Go give them a ⭐ + +`.env` file format: +- `FIRSTRADE=FIRSTRADE_USERNAME:FIRSTRADE_PASSWORD:FIRSTRADE_PIN` + ### Robinhood Made using [robin_stocks](https://github.com/jmfernandes/robin_stocks). Go give them a ⭐ @@ -165,12 +171,6 @@ Required `.env` variables: `.env` file format: - `TASTYTRADE=TASTYTRADE_USERNAME:TASTYTRADE_PASSWORD` - -### Firstrade -Made by [MaxxRK](https://github.com/MaxxRK/) using the [firstrade-api](https://github.com/MaxxRK/firstrade-api). Go give them a ⭐ - -`.env` file format: -- `FIRSTRADE=FIRSTRADE_USERNAME:FIRSTRADE_PASSWORD:FIRSTRADE_PIN:PERSISTENT_SESSION` ### 🤷‍♂️ Maybe future brokerages 🤷‍♀️ #### Ally diff --git a/autoRSA.py b/autoRSA.py index 8999303f..9fd82d3a 100644 --- a/autoRSA.py +++ b/autoRSA.py @@ -29,8 +29,8 @@ # Global variables -SUPPORTED_BROKERS = ["fidelity", "robinhood", "schwab", "tastytrade", "tradier", "firstrade"] -DAY1_BROKERS = ["robinhood", "schwab", "tastytrade", "tradier"] +SUPPORTED_BROKERS = ["fidelity", "firstrade", "robinhood", "schwab", "tastytrade", "tradier"] +DAY1_BROKERS = ["robinhood", "firstrade", "schwab", "tastytrade", "tradier"] DISCORD_BOT = False DOCKER_MODE = False DANGER_MODE = False @@ -38,12 +38,12 @@ # Account nicknames def nicknames(broker): + if broker == "ft": + return "firstrade" if broker == "rh": return "robinhood" if broker == "tasty": return "tastytrade" - if broker == "ft": - return "firstrade" return broker diff --git a/firstradeAPI.py b/firstradeAPI.py index 19c13db7..597b8f2a 100644 --- a/firstradeAPI.py +++ b/firstradeAPI.py @@ -30,15 +30,10 @@ def firstrade_init(FIRSTRADE_EXTERNAL=None): name = f"Firstrade {index}" try: account = account.split(":") - if account[3].lower() == "true": - persistent_session = True - else: - persistent_session = False firstrade = ft_account.FTSession( username=account[0], password=account[1], - pin=account[2], - persistent_session=persistent_session + pin=account[2] ) account_info = ft_account.FTAccountData(firstrade) account_list = account_info.account_numbers diff --git a/requirements.txt b/requirements.txt index 2332bbae..424a760f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ asyncio==3.4.3 discord.py==2.3.2 +firstrade==0.0.5 GitPython==3.1.34 pandas==2.1.0 pyotp==2.9.0 @@ -10,4 +11,3 @@ schwab-api==0.2.3 selenium==4.12.0 tastytrade==6.1 webdriver-manager==4.0.0 -firstrade==0.0.4 From 53d75c83b3e975f73b78a939945fc4f45c4c3a90 Mon Sep 17 00:00:00 2001 From: Donald Ryan Gullett <45299186+MaxxRK@users.noreply.github.com> Date: Mon, 30 Oct 2023 20:09:13 -0500 Subject: [PATCH 06/36] fix incorrect comment --- firstradeAPI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firstradeAPI.py b/firstradeAPI.py index 597b8f2a..38dde503 100644 --- a/firstradeAPI.py +++ b/firstradeAPI.py @@ -13,7 +13,7 @@ def firstrade_init(FIRSTRADE_EXTERNAL=None): # Initialize .env file load_dotenv() - # Import Tradier account + # Import Firstrade account if not os.getenv("FIRSTRADE") and FIRSTRADE_EXTERNAL is None: print("Firstrade not found, skipping...") return None From 169b1151d612c0b8befcf5a1cd750f8c91b2760e Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Mon, 30 Oct 2023 23:40:25 -0400 Subject: [PATCH 07/36] correct object def --- firstradeAPI.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/firstradeAPI.py b/firstradeAPI.py index 38dde503..01c3382d 100644 --- a/firstradeAPI.py +++ b/firstradeAPI.py @@ -55,7 +55,7 @@ def firstrade_holdings(firstrade_o: Brokerage, loop=None): # Get holdings on each account for key in firstrade_o.get_account_numbers(): for account in firstrade_o.get_account_numbers(key): - obj: Firstrade = firstrade_o.get_logged_in_objects(key) + obj: ft_account.FTSession = firstrade_o.get_logged_in_objects(key) try: data = ft_account.FTAccountData(obj).get_positions(account=account) for item in data: @@ -85,8 +85,7 @@ def firstrade_transaction(firstrade_o: Brokerage, orderObj: stockOrder, loop=Non loop, ) for account in firstrade_o.get_account_numbers(key): - obj: Firstrade = firstrade_o.get_logged_in_objects(key) - print(f"{key} Account: {account}") + obj: ft_account.FTSession = firstrade_o.get_logged_in_objects(key) # If DRY is True, don't actually make the transaction if orderObj.get_dry(): printAndDiscord( @@ -118,11 +117,11 @@ def firstrade_transaction(firstrade_o: Brokerage, orderObj: stockOrder, loop=Non printAndDiscord( f"{key} account {account}: The order verification was " + "successful" - if ft_order.order_confirmation["success"] == 'Yes' + if ft_order.order_confirmation["success"] == "Yes" else "unsuccessful", loop, ) - if not ft_order.order_confirmation["success"] == 'Yes': + if not ft_order.order_confirmation["success"] == "Yes": printAndDiscord( f"{key} account {account}: The order verification produced the following messages: {ft_order.order_confirmation['actiondata']}", loop, From 98fbece0400720c5c5fde694dd8a83312cee15b4 Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Mon, 30 Oct 2023 23:40:35 -0400 Subject: [PATCH 08/36] add vars to readme --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e5260a0..e8c0b31b 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,11 @@ Required `.env` variables: ### Firstrade Made by [MaxxRK](https://github.com/MaxxRK/) using the [firstrade-api](https://github.com/MaxxRK/firstrade-api). Go give them a ⭐ +Required `.env` variables: +- `FIRSTRADE_USERNAME` +- `FIRSTRADE_PASSWORD` +- `FIRSTRADE_PIN` + `.env` file format: - `FIRSTRADE=FIRSTRADE_USERNAME:FIRSTRADE_PASSWORD:FIRSTRADE_PIN` @@ -171,7 +176,7 @@ Required `.env` variables: `.env` file format: - `TASTYTRADE=TASTYTRADE_USERNAME:TASTYTRADE_PASSWORD` - + ### 🤷‍♂️ Maybe future brokerages 🤷‍♀️ #### Ally Ally disabled their official API, so all Ally packages don't work. I am attempting to reverse engineer their API, which you can track [here](https://github.com/NelsonDane/ally-api). Once I get it working, I will add it to this project. From b581dea8fcdb16b52c5cf37dca7d12570a785f39 Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Mon, 30 Oct 2023 23:45:11 -0400 Subject: [PATCH 09/36] ignore pickle files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4bf65770..fc3d23c7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ __pycache__/ did.bin test.py test[0-9].py +*.pkl *.png .vscode/ *venv/ From 67b1449db8a50d87e030d8a17a43699294ff9d35 Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Mon, 30 Oct 2023 23:47:55 -0400 Subject: [PATCH 10/36] small formatting fix --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 83f48747..cf73073f 100644 --- a/.env.example +++ b/.env.example @@ -44,4 +44,4 @@ TRADIER= # Tastytrade # TASTYTRADE=TASTYTRADE_USERNAME:TASTYTRADE_PASSWORD -TASTYTRADE= \ No newline at end of file +TASTYTRADE= From 80cdc298735a14ab86dea96fe440f6f0666aed91 Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Tue, 31 Oct 2023 18:25:53 +0000 Subject: [PATCH 11/36] add traceback --- firstradeAPI.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/firstradeAPI.py b/firstradeAPI.py index 01c3382d..f20f1fb8 100644 --- a/firstradeAPI.py +++ b/firstradeAPI.py @@ -4,6 +4,7 @@ from firstrade import account as ft_account, symbols, order import os import pprint +import traceback from time import sleep from dotenv import load_dotenv @@ -47,6 +48,7 @@ def firstrade_init(FIRSTRADE_EXTERNAL=None): ) except Exception as e: print(f"Error logging in to Firstrade: {e}") + print(traceback.format_exc()) return None return firstrade_obj @@ -67,6 +69,7 @@ def firstrade_holdings(firstrade_o: Brokerage, loop=None): firstrade_o.set_holdings(key, account, sym, qty, current_price) except Exception as e: printAndDiscord(f"{key} {account}: Error getting holdings: {e}", loop) + print(traceback.format_exc()) continue printHoldings(firstrade_o, loop) @@ -130,6 +133,7 @@ def firstrade_transaction(firstrade_o: Brokerage, orderObj: stockOrder, loop=Non printAndDiscord( f"{key} {account}: Error submitting order: {e}", loop ) + print(traceback.format_exc()) continue sleep(1) print() \ No newline at end of file From ff35dcc5e08fc4cb9c117a4e43c23846426c5eed Mon Sep 17 00:00:00 2001 From: Donald Ryan Gullett <45299186+MaxxRK@users.noreply.github.com> Date: Mon, 6 Nov 2023 06:19:56 -0600 Subject: [PATCH 12/36] change ft api version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 82594a6b..b8da26f6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ asyncio==3.4.3 discord.py==2.3.2 -firstrade==0.0.5 +firstrade==0.0.7 GitPython==3.1.34 pandas==2.1.0 pyotp==2.9.0 From cb004996253add2508bd6b3cafb1cd14d12da8cf Mon Sep 17 00:00:00 2001 From: Donald Ryan Gullett <45299186+MaxxRK@users.noreply.github.com> Date: Mon, 6 Nov 2023 09:08:02 -0600 Subject: [PATCH 13/36] fix firstrade buy only --- firstradeAPI.py | 7 ++++++- requirements.txt | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/firstradeAPI.py b/firstradeAPI.py index f20f1fb8..73419696 100644 --- a/firstradeAPI.py +++ b/firstradeAPI.py @@ -105,11 +105,16 @@ def firstrade_transaction(firstrade_o: Brokerage, orderObj: stockOrder, loop=Non else: price_type = order.PriceType.MARKET price = 0.00 + if orderObj.get_action().capitalize() == "Buy": + order_type = order.OrderType.BUY + else: + order_type = order.OrderType.SELL ft_order = order.Order(obj) ft_order.place_order( account=account, symbol=s, - order_type=price_type, + price_type=price_type, + order_type=order_type, quantity=int(orderObj.get_amount()), duration=order.Duration.DAY, price=price, diff --git a/requirements.txt b/requirements.txt index b8da26f6..93fc569f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ asyncio==3.4.3 discord.py==2.3.2 -firstrade==0.0.7 +firstrade==0.0.8 GitPython==3.1.34 pandas==2.1.0 pyotp==2.9.0 From 1337e21d63b2853785464d0fb6444217f7c5bb19 Mon Sep 17 00:00:00 2001 From: Donald Ryan Gullett <45299186+MaxxRK@users.noreply.github.com> Date: Mon, 6 Nov 2023 09:55:20 -0600 Subject: [PATCH 14/36] change ft_api version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 93fc569f..344513c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ asyncio==3.4.3 discord.py==2.3.2 -firstrade==0.0.8 +firstrade==0.0.9 GitPython==3.1.34 pandas==2.1.0 pyotp==2.9.0 From 66e23ab38fdc38c12ffc1c104579b8916ced2ae3 Mon Sep 17 00:00:00 2001 From: Donald Ryan Gullett <45299186+MaxxRK@users.noreply.github.com> Date: Mon, 6 Nov 2023 10:51:37 -0600 Subject: [PATCH 15/36] fix firstrade api to use acount dict --- firstradeAPI.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/firstradeAPI.py b/firstradeAPI.py index 73419696..db5254f3 100644 --- a/firstradeAPI.py +++ b/firstradeAPI.py @@ -41,10 +41,11 @@ def firstrade_init(FIRSTRADE_EXTERNAL=None): print(f"The following Firstrade accounts were found: {account_list}") print("Logged in to Firstrade!") firstrade_obj.set_logged_in_object(name, firstrade) - for i, account in enumerate(account_list): - firstrade_obj.set_account_number(name, account) + for i, entry in enumerate(account_info.all_accounts): + account = list(entry.keys()) + firstrade_obj.set_account_number(name, account[0]) firstrade_obj.set_account_totals( - name, account, str(account_info.account_balances[i]) + name, account[0], str(account_info.all_accounts[i][account[0]]['Balance']) ) except Exception as e: print(f"Error logging in to Firstrade: {e}") From f19f2a369fa84159deab1f138acb5ca71a32417d Mon Sep 17 00:00:00 2001 From: Donald Ryan Gullett <45299186+MaxxRK@users.noreply.github.com> Date: Mon, 6 Nov 2023 11:05:53 -0600 Subject: [PATCH 16/36] update ft_api version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 344513c6..825f2188 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ asyncio==3.4.3 discord.py==2.3.2 -firstrade==0.0.9 +firstrade==0.0.11 GitPython==3.1.34 pandas==2.1.0 pyotp==2.9.0 From ee0004317f1d755504698341a258cd9e748880ce Mon Sep 17 00:00:00 2001 From: Donald Ryan Gullett <45299186+MaxxRK@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:58:38 -0600 Subject: [PATCH 17/36] change ft_api version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 825f2188..d980c360 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ asyncio==3.4.3 discord.py==2.3.2 -firstrade==0.0.11 +firstrade==0.0.12 GitPython==3.1.34 pandas==2.1.0 pyotp==2.9.0 From 50f6177e144e345ab257ac3326110a8308271cec Mon Sep 17 00:00:00 2001 From: Donald Ryan Gullett <45299186+MaxxRK@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:47:06 -0600 Subject: [PATCH 18/36] remove redundant variable --- firstradeAPI.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/firstradeAPI.py b/firstradeAPI.py index db5254f3..467094dd 100644 --- a/firstradeAPI.py +++ b/firstradeAPI.py @@ -37,8 +37,7 @@ def firstrade_init(FIRSTRADE_EXTERNAL=None): pin=account[2] ) account_info = ft_account.FTAccountData(firstrade) - account_list = account_info.account_numbers - print(f"The following Firstrade accounts were found: {account_list}") + print(f"The following Firstrade accounts were found: {account_info.account_numbers}") print("Logged in to Firstrade!") firstrade_obj.set_logged_in_object(name, firstrade) for i, entry in enumerate(account_info.all_accounts): From 9f743ee83c95f7123ee1245abd6af3a43f5488ff Mon Sep 17 00:00:00 2001 From: Donald Ryan Gullett <45299186+MaxxRK@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:59:48 -0600 Subject: [PATCH 19/36] code cleanup --- firstradeAPI.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/firstradeAPI.py b/firstradeAPI.py index 467094dd..33fd80aa 100644 --- a/firstradeAPI.py +++ b/firstradeAPI.py @@ -40,11 +40,11 @@ def firstrade_init(FIRSTRADE_EXTERNAL=None): print(f"The following Firstrade accounts were found: {account_info.account_numbers}") print("Logged in to Firstrade!") firstrade_obj.set_logged_in_object(name, firstrade) - for i, entry in enumerate(account_info.all_accounts): - account = list(entry.keys()) - firstrade_obj.set_account_number(name, account[0]) + for entry in account_info.all_accounts: + account = list(entry.keys())[0] + firstrade_obj.set_account_number(name, account) firstrade_obj.set_account_totals( - name, account[0], str(account_info.all_accounts[i][account[0]]['Balance']) + name, account, str(entry[account]['Balance']) ) except Exception as e: print(f"Error logging in to Firstrade: {e}") From c3b17bc2e5320dc5fd2e4d466d587045212b5011 Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Mon, 6 Nov 2023 20:08:15 -0800 Subject: [PATCH 20/36] Deepsource style fix --- firstradeAPI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firstradeAPI.py b/firstradeAPI.py index 33fd80aa..101e4e6b 100644 --- a/firstradeAPI.py +++ b/firstradeAPI.py @@ -141,4 +141,4 @@ def firstrade_transaction(firstrade_o: Brokerage, orderObj: stockOrder, loop=Non print(traceback.format_exc()) continue sleep(1) - print() \ No newline at end of file + print() From ab99d25ef25d2cdca269b256a0ea5bfd28cab165 Mon Sep 17 00:00:00 2001 From: Donald Ryan Gullett <45299186+MaxxRK@users.noreply.github.com> Date: Mon, 18 Dec 2023 06:22:46 -0600 Subject: [PATCH 21/36] add new print to ft --- firstradeAPI.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/firstradeAPI.py b/firstradeAPI.py index 101e4e6b..14dbf3e7 100644 --- a/firstradeAPI.py +++ b/firstradeAPI.py @@ -1,12 +1,14 @@ # Donald Ryan Gullett(MaxxRK) # Firstrade API -from firstrade import account as ft_account, symbols, order import os import pprint import traceback from time import sleep + from dotenv import load_dotenv +from firstrade import account as ft_account +from firstrade import order, symbols from helperAPI import Brokerage, printAndDiscord, printHoldings, stockOrder @@ -37,7 +39,6 @@ def firstrade_init(FIRSTRADE_EXTERNAL=None): pin=account[2] ) account_info = ft_account.FTAccountData(firstrade) - print(f"The following Firstrade accounts were found: {account_info.account_numbers}") print("Logged in to Firstrade!") firstrade_obj.set_logged_in_object(name, firstrade) for entry in account_info.all_accounts: @@ -46,6 +47,8 @@ def firstrade_init(FIRSTRADE_EXTERNAL=None): firstrade_obj.set_account_totals( name, account, str(entry[account]['Balance']) ) + print_accounts = [firstrade_obj.print_account_number(a) for a in account_info.account_numbers] + print(f"The following Firstrade accounts were found: {print_accounts}") except Exception as e: print(f"Error logging in to Firstrade: {e}") print(traceback.format_exc()) @@ -89,6 +92,7 @@ def firstrade_transaction(firstrade_o: Brokerage, orderObj: stockOrder, loop=Non ) for account in firstrade_o.get_account_numbers(key): obj: ft_account.FTSession = firstrade_o.get_logged_in_objects(key) + print_account = firstrade_o.print_account_number(account) # If DRY is True, don't actually make the transaction if orderObj.get_dry(): printAndDiscord( @@ -123,7 +127,7 @@ def firstrade_transaction(firstrade_o: Brokerage, orderObj: stockOrder, loop=Non print("The order verification produced the following messages: ") pprint.pprint(ft_order.order_confirmation) printAndDiscord( - f"{key} account {account}: The order verification was " + f"{key} account {print_account}: The order verification was " + "successful" if ft_order.order_confirmation["success"] == "Yes" else "unsuccessful", @@ -131,12 +135,12 @@ def firstrade_transaction(firstrade_o: Brokerage, orderObj: stockOrder, loop=Non ) if not ft_order.order_confirmation["success"] == "Yes": printAndDiscord( - f"{key} account {account}: The order verification produced the following messages: {ft_order.order_confirmation['actiondata']}", + f"{key} account {print_account}: The order verification produced the following messages: {ft_order.order_confirmation['actiondata']}", loop, ) except Exception as e: printAndDiscord( - f"{key} {account}: Error submitting order: {e}", loop + f"{key} {print_account}: Error submitting order: {e}", loop ) print(traceback.format_exc()) continue From c271e29aee6723711cafadb3b9c75501394b1215 Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Thu, 4 Jan 2024 11:43:32 -0600 Subject: [PATCH 22/36] update readme --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index cc603cc5..7c608162 100644 --- a/README.md +++ b/README.md @@ -188,8 +188,6 @@ Required `.env` variables: Ally disabled their official API, so all Ally packages don't work. I am attempting to reverse engineer their API, which you can track [here](https://github.com/NelsonDane/ally-api). Once I get it working, I will add it to this project. #### Chase Chase doesn't have an official API, so it would have to be added using Selenium. -#### Firstrade -In progress on the `develop-firstrade` branch. Stay tuned. #### Vanguard Will be added using Selenium just like Fidelity. I found this [vanguard-api](https://github.com/rikonor/vanguard-api), but it failed when I ran it. #### SoFi From 3a739150d6c093f659fabde0a662c6a64b742b8c Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 00:48:53 +0000 Subject: [PATCH 23/36] style: format code with Black and isort This commit fixes the style issues introduced in c57833a according to the output from Black and isort. Details: None --- autoRSA.py | 9 ++++++++- firstradeAPI.py | 11 ++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/autoRSA.py b/autoRSA.py index 8bbcd1c2..d0995478 100644 --- a/autoRSA.py +++ b/autoRSA.py @@ -30,7 +30,14 @@ # Global variables -SUPPORTED_BROKERS = ["fidelity", "firstrade", "robinhood", "schwab", "tastytrade", "tradier"] +SUPPORTED_BROKERS = [ + "fidelity", + "firstrade", + "robinhood", + "schwab", + "tastytrade", + "tradier", +] DAY1_BROKERS = ["robinhood", "firstrade", "schwab", "tastytrade", "tradier"] DISCORD_BOT = False DOCKER_MODE = False diff --git a/firstradeAPI.py b/firstradeAPI.py index 14dbf3e7..54295c38 100644 --- a/firstradeAPI.py +++ b/firstradeAPI.py @@ -34,9 +34,7 @@ def firstrade_init(FIRSTRADE_EXTERNAL=None): try: account = account.split(":") firstrade = ft_account.FTSession( - username=account[0], - password=account[1], - pin=account[2] + username=account[0], password=account[1], pin=account[2] ) account_info = ft_account.FTAccountData(firstrade) print("Logged in to Firstrade!") @@ -45,9 +43,12 @@ def firstrade_init(FIRSTRADE_EXTERNAL=None): account = list(entry.keys())[0] firstrade_obj.set_account_number(name, account) firstrade_obj.set_account_totals( - name, account, str(entry[account]['Balance']) + name, account, str(entry[account]["Balance"]) ) - print_accounts = [firstrade_obj.print_account_number(a) for a in account_info.account_numbers] + print_accounts = [ + firstrade_obj.print_account_number(a) + for a in account_info.account_numbers + ] print(f"The following Firstrade accounts were found: {print_accounts}") except Exception as e: print(f"Error logging in to Firstrade: {e}") From 7b0fb19a45a8a0fb30fb9e3973cba92457191d71 Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Mon, 8 Jan 2024 19:52:19 -0500 Subject: [PATCH 24/36] update mask string --- firstradeAPI.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/firstradeAPI.py b/firstradeAPI.py index 54295c38..5eb33b49 100644 --- a/firstradeAPI.py +++ b/firstradeAPI.py @@ -10,7 +10,7 @@ from firstrade import account as ft_account from firstrade import order, symbols -from helperAPI import Brokerage, printAndDiscord, printHoldings, stockOrder +from helperAPI import Brokerage, maskString, printAndDiscord, printHoldings, stockOrder def firstrade_init(FIRSTRADE_EXTERNAL=None): @@ -46,7 +46,7 @@ def firstrade_init(FIRSTRADE_EXTERNAL=None): name, account, str(entry[account]["Balance"]) ) print_accounts = [ - firstrade_obj.print_account_number(a) + maskString(a) for a in account_info.account_numbers ] print(f"The following Firstrade accounts were found: {print_accounts}") @@ -93,7 +93,7 @@ def firstrade_transaction(firstrade_o: Brokerage, orderObj: stockOrder, loop=Non ) for account in firstrade_o.get_account_numbers(key): obj: ft_account.FTSession = firstrade_o.get_logged_in_objects(key) - print_account = firstrade_o.print_account_number(account) + print_account = maskString(account) # If DRY is True, don't actually make the transaction if orderObj.get_dry(): printAndDiscord( From f087714c7615aae05c8eebcc071de45e3324a9c0 Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 00:52:40 +0000 Subject: [PATCH 25/36] style: format code with Black and isort This commit fixes the style issues introduced in 7b0fb19 according to the output from Black and isort. Details: None --- firstradeAPI.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/firstradeAPI.py b/firstradeAPI.py index 5eb33b49..e7d71444 100644 --- a/firstradeAPI.py +++ b/firstradeAPI.py @@ -45,10 +45,7 @@ def firstrade_init(FIRSTRADE_EXTERNAL=None): firstrade_obj.set_account_totals( name, account, str(entry[account]["Balance"]) ) - print_accounts = [ - maskString(a) - for a in account_info.account_numbers - ] + print_accounts = [maskString(a) for a in account_info.account_numbers] print(f"The following Firstrade accounts were found: {print_accounts}") except Exception as e: print(f"Error logging in to Firstrade: {e}") From 1db00ba62422ffccda6ad4696b8ca330880c91e9 Mon Sep 17 00:00:00 2001 From: maxxrk Date: Tue, 23 Jan 2024 19:30:57 -0600 Subject: [PATCH 26/36] fidelity fix --- fidelityAPI.py | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/fidelityAPI.py b/fidelityAPI.py index 09d9514c..ec18a027 100644 --- a/fidelityAPI.py +++ b/fidelityAPI.py @@ -9,6 +9,9 @@ from time import sleep from dotenv import load_dotenv +from helperAPI import (Brokerage, check_if_page_loaded, getDriver, + killSeleniumDriver, maskString, printAndDiscord, + printHoldings, stockOrder, type_slowly) from selenium import webdriver from selenium.common.exceptions import NoSuchElementException, TimeoutException from selenium.webdriver import Keys @@ -16,18 +19,6 @@ from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait -from helperAPI import ( - Brokerage, - check_if_page_loaded, - getDriver, - killSeleniumDriver, - maskString, - printAndDiscord, - printHoldings, - stockOrder, - type_slowly, -) - def fidelity_error(driver: webdriver, error: str): print(f"Fidelity Error: {error}") @@ -109,6 +100,23 @@ def fidelity_init(FIDELITY_EXTERNAL=None, DOCKER=False): driver.find_element(by=By.CSS_SELECTOR, value=login_btn_selector).click() WebDriverWait(driver, 10).until(check_if_page_loaded) sleep(3) + # Retry the login if we get an error page + try: + go_back_selector = "#dom-sys-err-go-to-login-button > span > s-slot > s-assigned-wrapper" + WebDriverWait(driver,10).until( + expected_conditions.element_to_be_clickable((By.CSS_SELECTOR, go_back_selector)), + ).click() + username_field = driver.find_element( + by=By.CSS_SELECTOR, value=username_selector + ) + type_slowly(username_field, account[0]) + password_field = driver.find_element( + by=By.CSS_SELECTOR, value=password_selector + ) + type_slowly(password_field, account[1]) + driver.find_element(by=By.CSS_SELECTOR, value=login_btn_selector).click() + except TimeoutException: + pass # Wait for page to load to summary page if "summary" not in driver.current_url: if "errorpage" in driver.current_url.lower(): @@ -346,6 +354,12 @@ def fidelity_transaction(fidelity_o: Brokerage, orderObj: stockOrder, loop=None) value="#quote-panel > div > div.eq-ticket__quote--blocks-container > div:nth-child(2) > div > span > span", ) ).text + # Last price gets us a more accurate number + last_price = driver.find_element( + by=By.CSS_SELECTOR, + value="#eq-ticket__last-price > span.last-price" + ).text + last_price = last_price.replace("$", "") bid_price = ( driver.find_element( by=By.CSS_SELECTOR, @@ -438,11 +452,11 @@ def fidelity_transaction(fidelity_o: Brokerage, orderObj: stockOrder, loop=None) ) limit_button.click() # Set price - difference_price = 0.01 if float(ask_price) > 0.1 else 0.001 + difference_price = 0.01 if float(last_price) > 0.1 else 0.0001 if orderObj.get_action() == "buy": - wanted_price = round(float(ask_price) + difference_price, 3) + wanted_price = round(float(last_price) + difference_price, 3) else: - wanted_price = round(float(bid_price) - difference_price, 3) + wanted_price = round(float(last_price) - difference_price, 3) if new_style: price_box = driver.find_element( by=By.CSS_SELECTOR, value="#eqt-mts-limit-price" From faf8c06f29303444ac11a98e2e09ca3d36c4a391 Mon Sep 17 00:00:00 2001 From: maxxrk Date: Tue, 23 Jan 2024 19:39:41 -0600 Subject: [PATCH 27/36] fix import styling --- fidelityAPI.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/fidelityAPI.py b/fidelityAPI.py index ec18a027..3c0a6c21 100644 --- a/fidelityAPI.py +++ b/fidelityAPI.py @@ -9,9 +9,18 @@ from time import sleep from dotenv import load_dotenv -from helperAPI import (Brokerage, check_if_page_loaded, getDriver, - killSeleniumDriver, maskString, printAndDiscord, - printHoldings, stockOrder, type_slowly) +from helperAPI import ( + Brokerage, + check_if_page_loaded, + getDriver, + killSeleniumDriver, + maskString, + printAndDiscord, + printHoldings, + stockOrder, + type_slowly +) + from selenium import webdriver from selenium.common.exceptions import NoSuchElementException, TimeoutException from selenium.webdriver import Keys From d02fd5ecfab38e1e930461d7365827ef9f1938fe Mon Sep 17 00:00:00 2001 From: maxxrk Date: Tue, 23 Jan 2024 19:45:25 -0600 Subject: [PATCH 28/36] revert import order --- fidelityAPI.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fidelityAPI.py b/fidelityAPI.py index 3c0a6c21..508cbdae 100644 --- a/fidelityAPI.py +++ b/fidelityAPI.py @@ -8,6 +8,13 @@ import traceback from time import sleep +from selenium import webdriver +from selenium.common.exceptions import NoSuchElementException, TimeoutException +from selenium.webdriver import Keys +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions +from selenium.webdriver.support.wait import WebDriverWait + from dotenv import load_dotenv from helperAPI import ( Brokerage, @@ -18,16 +25,9 @@ printAndDiscord, printHoldings, stockOrder, - type_slowly + type_slowly, ) -from selenium import webdriver -from selenium.common.exceptions import NoSuchElementException, TimeoutException -from selenium.webdriver import Keys -from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions -from selenium.webdriver.support.wait import WebDriverWait - def fidelity_error(driver: webdriver, error: str): print(f"Fidelity Error: {error}") From 9016ff6eb0cbb534a7305ee0669d6a64a0a38c25 Mon Sep 17 00:00:00 2001 From: maxxrk Date: Tue, 23 Jan 2024 19:46:24 -0600 Subject: [PATCH 29/36] last import order fix --- fidelityAPI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fidelityAPI.py b/fidelityAPI.py index 508cbdae..705e075c 100644 --- a/fidelityAPI.py +++ b/fidelityAPI.py @@ -8,6 +8,7 @@ import traceback from time import sleep +from dotenv import load_dotenv from selenium import webdriver from selenium.common.exceptions import NoSuchElementException, TimeoutException from selenium.webdriver import Keys @@ -15,7 +16,6 @@ from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait -from dotenv import load_dotenv from helperAPI import ( Brokerage, check_if_page_loaded, From 629a0c20a3ce8e9be0c0cace7d4c470d3cc647b5 Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Tue, 23 Jan 2024 23:37:15 -0500 Subject: [PATCH 30/36] remove unused prices --- fidelityAPI.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/fidelityAPI.py b/fidelityAPI.py index 705e075c..5ff2433e 100644 --- a/fidelityAPI.py +++ b/fidelityAPI.py @@ -356,27 +356,14 @@ def fidelity_transaction(fidelity_o: Brokerage, orderObj: stockOrder, loop=None) return except Exception: pass - # Get ask/bid price - ask_price = ( - driver.find_element( - by=By.CSS_SELECTOR, - value="#quote-panel > div > div.eq-ticket__quote--blocks-container > div:nth-child(2) > div > span > span", - ) - ).text - # Last price gets us a more accurate number + # Get last price last_price = driver.find_element( by=By.CSS_SELECTOR, value="#eq-ticket__last-price > span.last-price" ).text last_price = last_price.replace("$", "") - bid_price = ( - driver.find_element( - by=By.CSS_SELECTOR, - value="#quote-panel > div > div.eq-ticket__quote--blocks-container > div:nth-child(1) > div > span > span", - ) - ).text # If price is under $1, then we have to use a limit order - LIMIT = bool(float(ask_price) < 1 or float(bid_price) < 1) + LIMIT = bool(float(last_price) < 1) # Figure out whether page is in old or new style try: action_dropdown = driver.find_element( From 57b3cde19bb1f1f35a894a5e22a20f9cab9b57f9 Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Tue, 23 Jan 2024 23:45:56 -0500 Subject: [PATCH 31/36] bad indent --- firstradeAPI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firstradeAPI.py b/firstradeAPI.py index e7d71444..f7b5bc6c 100644 --- a/firstradeAPI.py +++ b/firstradeAPI.py @@ -72,7 +72,7 @@ def firstrade_holdings(firstrade_o: Brokerage, loop=None): printAndDiscord(f"{key} {account}: Error getting holdings: {e}", loop) print(traceback.format_exc()) continue - printHoldings(firstrade_o, loop) + printHoldings(firstrade_o, loop) def firstrade_transaction(firstrade_o: Brokerage, orderObj: stockOrder, loop=None): From e7e7083e3622a61cb595f3d395badcc2f4991e28 Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 04:47:06 +0000 Subject: [PATCH 32/36] style: format code with Black and isort This commit fixes the style issues introduced in 57b3cde according to the output from Black and isort. Details: None --- fidelityAPI.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/fidelityAPI.py b/fidelityAPI.py index 5ff2433e..79939590 100644 --- a/fidelityAPI.py +++ b/fidelityAPI.py @@ -112,8 +112,10 @@ def fidelity_init(FIDELITY_EXTERNAL=None, DOCKER=False): # Retry the login if we get an error page try: go_back_selector = "#dom-sys-err-go-to-login-button > span > s-slot > s-assigned-wrapper" - WebDriverWait(driver,10).until( - expected_conditions.element_to_be_clickable((By.CSS_SELECTOR, go_back_selector)), + WebDriverWait(driver, 10).until( + expected_conditions.element_to_be_clickable( + (By.CSS_SELECTOR, go_back_selector) + ), ).click() username_field = driver.find_element( by=By.CSS_SELECTOR, value=username_selector @@ -123,7 +125,9 @@ def fidelity_init(FIDELITY_EXTERNAL=None, DOCKER=False): by=By.CSS_SELECTOR, value=password_selector ) type_slowly(password_field, account[1]) - driver.find_element(by=By.CSS_SELECTOR, value=login_btn_selector).click() + driver.find_element( + by=By.CSS_SELECTOR, value=login_btn_selector + ).click() except TimeoutException: pass # Wait for page to load to summary page @@ -359,7 +363,7 @@ def fidelity_transaction(fidelity_o: Brokerage, orderObj: stockOrder, loop=None) # Get last price last_price = driver.find_element( by=By.CSS_SELECTOR, - value="#eq-ticket__last-price > span.last-price" + value="#eq-ticket__last-price > span.last-price", ).text last_price = last_price.replace("$", "") # If price is under $1, then we have to use a limit order @@ -450,9 +454,13 @@ def fidelity_transaction(fidelity_o: Brokerage, orderObj: stockOrder, loop=None) # Set price difference_price = 0.01 if float(last_price) > 0.1 else 0.0001 if orderObj.get_action() == "buy": - wanted_price = round(float(last_price) + difference_price, 3) + wanted_price = round( + float(last_price) + difference_price, 3 + ) else: - wanted_price = round(float(last_price) - difference_price, 3) + wanted_price = round( + float(last_price) - difference_price, 3 + ) if new_style: price_box = driver.find_element( by=By.CSS_SELECTOR, value="#eqt-mts-limit-price" From ca8d41556accb57febccb490a6b3f1dd8e5dce17 Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Thu, 8 Feb 2024 13:49:22 -0500 Subject: [PATCH 33/36] package version checker --- autoRSA.py | 6 ++-- helperAPI.py | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 +- 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/autoRSA.py b/autoRSA.py index 24477d59..ed6bb8c5 100644 --- a/autoRSA.py +++ b/autoRSA.py @@ -15,15 +15,15 @@ # Custom API libraries from fidelityAPI import * from firstradeAPI import * - from helperAPI import stockOrder, updater + from helperAPI import stockOrder, updater, check_package_versions from robinhoodAPI import * from schwabAPI import * from tastyAPI import * from tradierAPI import * except Exception as e: print(f"Error importing libraries: {e}") - print("Please run 'pip install -r requirements.txt'") print(traceback.format_exc()) + print("Please run 'pip install -r requirements.txt'") sys.exit(1) # Initialize .env file @@ -164,10 +164,12 @@ def argParser(args: list) -> stockOrder: # If discord argument, run discord bot, no docker, no prompt elif sys.argv[1].lower() == "discord": updater() + check_package_versions() print("Running Discord bot from command line") DISCORD_BOT = True else: # If any other argument, run bot, no docker or discord bot updater() + check_package_versions() print("Running bot from command line") cliOrderObj = argParser(sys.argv[1:]) if not cliOrderObj.get_holdings(): diff --git a/helperAPI.py b/helperAPI.py index bde00204..643a8367 100644 --- a/helperAPI.py +++ b/helperAPI.py @@ -4,12 +4,15 @@ import asyncio import os +import sys +import subprocess import textwrap from pathlib import Path from queue import Queue from time import sleep import requests +import pkg_resources from dotenv import load_dotenv from selenium import webdriver from selenium.webdriver.chrome.service import Service as ChromiumService @@ -359,6 +362,82 @@ def updater(): return +def check_package_versions(): + print("Checking package versions...") + # Check if pip packages are up to date + installed_packages = {pkg.key for pkg in pkg_resources.working_set} + required_packages = [] + required_repos = [] + f = open("requirements.txt", "r") + for line in f: + # Not commented pip packages + if not line.startswith("#") and "==" in line: + required_packages.append(line.strip()) + # Not commented git repos + elif not line.startswith("#") and "git+" in line: + required_repos.append(line.strip()) + SHOULD_CONTINUE = True + for package in required_packages: + if "==" not in package: + continue + package_name = package.split("==")[0].lower() + required_version = package.split("==")[1] + if package_name not in installed_packages: + print(f'Required package {package_name} is not installed.') + SHOULD_CONTINUE = False + installed_version = pkg_resources.get_distribution(package_name).version + if installed_version < required_version: + print( + f'Required package {package_name} is out of date (Want {required_version} but have {installed_version}).' + ) + SHOULD_CONTINUE = False + elif installed_version > required_version: + print( + f'WARNING: Required package {package_name} is newer than required (Want {required_version} but have {installed_version}).' + ) + for repo in required_repos: + repo_name = repo.split("/")[-1].split(".")[0].lower() + package_name = repo.split("egg=")[-1].lower() + required_version = repo.split("@")[-1].split("#")[0] + if len(required_version) != 40: + # Invalid hash + print(f"Required repo {repo_name} has invalid hash {required_version}.") + continue + if package_name not in installed_packages: + print(f'Required repo {package_name} is not installed.') + SHOULD_CONTINUE = False + continue + package_data = subprocess.run( + ["pip", "show", package_name], capture_output=True, text=True + ).stdout + if "Editable project location:" in package_data: + epl = package_data.split("Editable project location:")[1].split("\n")[0].strip() + installed_hash = subprocess.run( + ["git", "rev-parse", "HEAD"], capture_output=True, cwd=epl, text=True + ) + installed_hash = installed_hash.stdout.strip() + if installed_hash != required_version: + print( + f"Required repo {repo_name} is out of date (Want {required_version} but have {installed_hash})." + ) + SHOULD_CONTINUE = False + else: + print( + f"Required repo {repo_name} is installed as a package, not a git repo." + ) + SHOULD_CONTINUE = False + continue + if not SHOULD_CONTINUE: + print( + 'Please run "pip install -r requirements.txt" to install/update required packages.' + ) + sys.exit(1) + else: + print("All required packages are installed and up to date.") + print() + return + + def type_slowly(element, string, delay=0.3): # Type slower for character in string: diff --git a/requirements.txt b/requirements.txt index 6b7f8652..1051c0de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ GitPython==3.1.41 pyotp==2.9.0 python-dotenv==1.0.1 requests==2.31.0 --e git+https://github.com/NelsonDane/robin_stocks.git@f490a2eb0d5fc53afc93e077b3ea2a555124e105#egg=robin_stocks +-e git+https://github.com/NelsonDane/robin_stocks.git@f490a2eb0d5fc53afc93e077b3ea2a555124e105#egg=robin-stocks schwab-api==0.3.9 selenium==4.17.2 tastytrade==6.6 From 787aca1d6a90876256dc0c7c420afd67f0c1d389 Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 19:15:09 +0000 Subject: [PATCH 34/36] style: format code with Black and isort This commit fixes the style issues introduced in ca8d415 according to the output from Black and isort. Details: None --- autoRSA.py | 2 +- helperAPI.py | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/autoRSA.py b/autoRSA.py index ed6bb8c5..fbfa806d 100644 --- a/autoRSA.py +++ b/autoRSA.py @@ -15,7 +15,7 @@ # Custom API libraries from fidelityAPI import * from firstradeAPI import * - from helperAPI import stockOrder, updater, check_package_versions + from helperAPI import check_package_versions, stockOrder, updater from robinhoodAPI import * from schwabAPI import * from tastyAPI import * diff --git a/helperAPI.py b/helperAPI.py index 643a8367..f3dd0ea3 100644 --- a/helperAPI.py +++ b/helperAPI.py @@ -4,15 +4,15 @@ import asyncio import os -import sys import subprocess +import sys import textwrap from pathlib import Path from queue import Queue from time import sleep -import requests import pkg_resources +import requests from dotenv import load_dotenv from selenium import webdriver from selenium.webdriver.chrome.service import Service as ChromiumService @@ -383,17 +383,17 @@ def check_package_versions(): package_name = package.split("==")[0].lower() required_version = package.split("==")[1] if package_name not in installed_packages: - print(f'Required package {package_name} is not installed.') + print(f"Required package {package_name} is not installed.") SHOULD_CONTINUE = False installed_version = pkg_resources.get_distribution(package_name).version if installed_version < required_version: print( - f'Required package {package_name} is out of date (Want {required_version} but have {installed_version}).' + f"Required package {package_name} is out of date (Want {required_version} but have {installed_version})." ) SHOULD_CONTINUE = False elif installed_version > required_version: print( - f'WARNING: Required package {package_name} is newer than required (Want {required_version} but have {installed_version}).' + f"WARNING: Required package {package_name} is newer than required (Want {required_version} but have {installed_version})." ) for repo in required_repos: repo_name = repo.split("/")[-1].split(".")[0].lower() @@ -404,14 +404,18 @@ def check_package_versions(): print(f"Required repo {repo_name} has invalid hash {required_version}.") continue if package_name not in installed_packages: - print(f'Required repo {package_name} is not installed.') + print(f"Required repo {package_name} is not installed.") SHOULD_CONTINUE = False continue package_data = subprocess.run( ["pip", "show", package_name], capture_output=True, text=True ).stdout if "Editable project location:" in package_data: - epl = package_data.split("Editable project location:")[1].split("\n")[0].strip() + epl = ( + package_data.split("Editable project location:")[1] + .split("\n")[0] + .strip() + ) installed_hash = subprocess.run( ["git", "rev-parse", "HEAD"], capture_output=True, cwd=epl, text=True ) From 5ea9d343c95c949e62cdf0070e3f7c9251a0fbf4 Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Thu, 8 Feb 2024 14:28:22 -0500 Subject: [PATCH 35/36] remove installed check --- helperAPI.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/helperAPI.py b/helperAPI.py index f3dd0ea3..ccf8dfe8 100644 --- a/helperAPI.py +++ b/helperAPI.py @@ -365,7 +365,6 @@ def updater(): def check_package_versions(): print("Checking package versions...") # Check if pip packages are up to date - installed_packages = {pkg.key for pkg in pkg_resources.working_set} required_packages = [] required_repos = [] f = open("requirements.txt", "r") @@ -382,9 +381,6 @@ def check_package_versions(): continue package_name = package.split("==")[0].lower() required_version = package.split("==")[1] - if package_name not in installed_packages: - print(f"Required package {package_name} is not installed.") - SHOULD_CONTINUE = False installed_version = pkg_resources.get_distribution(package_name).version if installed_version < required_version: print( @@ -403,12 +399,8 @@ def check_package_versions(): # Invalid hash print(f"Required repo {repo_name} has invalid hash {required_version}.") continue - if package_name not in installed_packages: - print(f"Required repo {package_name} is not installed.") - SHOULD_CONTINUE = False - continue package_data = subprocess.run( - ["pip", "show", package_name], capture_output=True, text=True + ["pip", "show", package_name], capture_output=True, text=True, check=True ).stdout if "Editable project location:" in package_data: epl = ( @@ -417,7 +409,7 @@ def check_package_versions(): .strip() ) installed_hash = subprocess.run( - ["git", "rev-parse", "HEAD"], capture_output=True, cwd=epl, text=True + ["git", "rev-parse", "HEAD"], capture_output=True, cwd=epl, text=True, check=True ) installed_hash = installed_hash.stdout.strip() if installed_hash != required_version: From 8cd40f79ea2d4314c74f6cb2d160eaa96ea55021 Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 19:28:40 +0000 Subject: [PATCH 36/36] style: format code with Black and isort This commit fixes the style issues introduced in 5ea9d34 according to the output from Black and isort. Details: https://github.com/NelsonDane/auto-rsa/pull/165 --- helperAPI.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/helperAPI.py b/helperAPI.py index ccf8dfe8..c041aa4e 100644 --- a/helperAPI.py +++ b/helperAPI.py @@ -409,7 +409,11 @@ def check_package_versions(): .strip() ) installed_hash = subprocess.run( - ["git", "rev-parse", "HEAD"], capture_output=True, cwd=epl, text=True, check=True + ["git", "rev-parse", "HEAD"], + capture_output=True, + cwd=epl, + text=True, + check=True, ) installed_hash = installed_hash.stdout.strip() if installed_hash != required_version: