From f3a8127c9dafd6e600439428d227f630be6435dd Mon Sep 17 00:00:00 2001 From: ImNotOssy Date: Mon, 2 Sep 2024 01:31:32 -0500 Subject: [PATCH 01/24] dspac development start --- autoRSA.py | 6 +- dspacAPI.py | 274 +++++++++++++++++++++++++++++++++++++++++++++++++++ helperAPI.py | 41 ++++++++ 3 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 dspacAPI.py diff --git a/autoRSA.py b/autoRSA.py index 57d89cdf..4d4f4f67 100644 --- a/autoRSA.py +++ b/autoRSA.py @@ -52,6 +52,7 @@ # Global variables SUPPORTED_BROKERS = [ "chase", + "dspac", "fennel", "fidelity", "firstrade", @@ -66,6 +67,7 @@ ] DAY1_BROKERS = [ "chase", + "dspac", "fennel", "firstrade", "public", @@ -81,6 +83,8 @@ # Account nicknames def nicknames(broker): + if broker == "ds": + return "dspac" if broker in ["fid", "fido"]: return "fidelity" if broker == "ft": @@ -122,7 +126,7 @@ def fun_run(orderObj: stockOrder, command, botObj=None, loop=None): globals()[fun_name](DOCKER=DOCKER_MODE, loop=loop), broker, ) - elif broker.lower() in ["fennel", "firstrade", "public"]: + elif broker.lower() in ["fennel", "firstrade", "public" "dspac"]: # Requires bot object and loop orderObj.set_logged_in( globals()[fun_name](botObj=botObj, loop=loop), broker diff --git a/dspacAPI.py b/dspacAPI.py new file mode 100644 index 00000000..1f1d4e38 --- /dev/null +++ b/dspacAPI.py @@ -0,0 +1,274 @@ +import asyncio +import os +import traceback +from io import BytesIO + +from dotenv import load_dotenv + +from dspac_investing_API import DSPACAPI +from helperAPI import ( + Brokerage, + printAndDiscord, + printHoldings, + getOTPCodeDiscord, + maskString, + stockOrder, + send_captcha_to_discord, + getUserInputDiscord, +) + +load_dotenv() + + +def dspac_init(DSPAC_EXTERNAL=None, botObj=None, loop=None): + load_dotenv() + dspac_obj = Brokerage("DSPAC") + if not os.getenv("DSPAC") and DSPAC_EXTERNAL is None: + print("DSPAC not found, skipping...") + return None + + DSPAC = ( + os.environ["DSPAC"].strip().split(",") + if DSPAC_EXTERNAL is None + else DSPAC_EXTERNAL.strip().split(",") + ) + print("Logging in to DSPAC...") + for index, account in enumerate(DSPAC): + name = f"DSPAC {index + 1}" + try: + user, password, use_email = account.split(":") + use_email = use_email.upper() + ds = DSPACAPI(user, password, filename=f"DSPAC_{index + 1}.txt", creds_path="./creds/") + + # Initial API call to establish session and get initial cookies + print(f"{name}: Making initial request to establish session...") + ds.make_initial_request() + + # All the rest of the requests responsible for getting authenticated + print(f"{name}: Attempting to login...") + login(ds, botObj, name, loop, use_email) + + print(f"{name}: Retrieving account assets...") + account_assets = ds.get_account_assets() + + print(f"{name}: Retrieving account information...") + account_info = ds.get_account_info() + + account_number = str(account_info['Data']['accountNumber']) + + # Mask the account number before printing it + masked_account_number = maskString(account_number) + print(f"{name}: Found account {masked_account_number}") + + dspac_obj.set_account_number(name, masked_account_number) + dspac_obj.set_account_totals(name, masked_account_number, float(account_assets['Data']['totalAssets'])) + + dspac_obj.set_logged_in_object(name, ds, "ds") + print(f"{name}: Logged in with account number {masked_account_number}") + except Exception as e: + print(f"Error logging into DSPAC: {e}") + print(traceback.format_exc()) + continue + print("Logged into DSPAC!") + return dspac_obj + + +def login(ds, botObj, name, loop, use_email): + try: + # API call to generate the login ticket + if use_email == "TRUE": + print(f"{name}: Generating login ticket (Email)...") + ticket_response = ds.generate_login_ticket_email() + else: + print(f"{name}: Generating login ticket (SMS)...") + ticket_response = ds.generate_login_ticket_sms() + + # Log the raw response details + print(f"{name}: Initial ticket response: {ticket_response}") + + # Ensure 'Data' key exists and proceed with verification if necessary + if 'Data' not in ticket_response: + raise Exception("Invalid response from generating login ticket") + + # Check if SMS or CAPTCHA verification are required + data = ticket_response['Data'] + if data.get('needSmsVerifyCode', False): + # TODO 8/30/24: CAPTCHA should only be needed if SMS is needed. Is this true? + sms_and_captcha_response = handle_captcha_and_sms(ds, botObj, data, loop, name, use_email) + if not sms_and_captcha_response: + raise Exception("Error solving SMS or Captcha") + + print(f"{name}: Waiting for OTP code from user...") + otp_code = asyncio.run_coroutine_threadsafe( + getOTPCodeDiscord(botObj, name, timeout=300, loop=loop), + loop, + ).result() + if otp_code is None: + raise Exception("No SMS code received") + + print(f"{name}: OTP code received: {otp_code}") + ticket_response = ds.generate_login_ticket_sms(sms_code=otp_code) + + if "Message" in ticket_response and ticket_response["Message"] == "Incorrect verification code.": + raise Exception("Incorrect OTP code") + + # Handle the login ticket + if 'Data' in ticket_response and 'ticket' in ticket_response['Data']: + ticket = ticket_response['Data']['ticket'] + else: + print(f"{name}: Raw response object: {ticket_response}") + raise Exception(f"Login failed. No ticket generated. Response: {ticket_response}") + + print(f"{name}: Logging in with ticket...") + ds.login_with_ticket(ticket) + return True + except Exception as e: + print(f"Error in SMS login: {e}") + print(traceback.format_exc()) + return False + + +def handle_captcha_and_sms(ds, botObj, data, loop, name, use_email): + try: + if data.get('needCaptchaCode', False): + print(f"{name}: CAPTCHA required. Requesting CAPTCHA image...") + sms_response = solve_captcha(ds, botObj, name, loop, use_email) + if not sms_response: + raise Exception("Failure solving CAPTCHA!") + print(f"{name}: CAPTCHA solved. SMS response is: {sms_response}") + else: + print(f"{name}: Requesting SMS code...") + sms_response = send_sms_code(ds, name, use_email) + if not sms_response: + raise Exception("Unable to retrieve sms code!") + print(f"{name}: SMS response is: {sms_response}") + return True + + except Exception as e: + print(f"Error in CAPTCHA or SMS: {e}") + print(traceback.format_exc()) + return False + + +def solve_captcha(ds, botObj, name, loop, use_email): + try: + captcha_image = ds.request_captcha() + if not captcha_image: + raise Exception("Unable to request CAPTCHA image, aborting...") + + print("Sending CAPTCHA to Discord for user input...") + file = BytesIO() + captcha_image.save(file, format="PNG") + file.seek(0) + + asyncio.run_coroutine_threadsafe( + send_captcha_to_discord(file), + loop, + ).result() + + captcha_input = asyncio.run_coroutine_threadsafe( + getUserInputDiscord(botObj, f"{name} requires CAPTCHA input", timeout=300, loop=loop), + loop, + ).result() + + if captcha_input: + if use_email == "TRUE": + sms_request_response = ds.request_email_code(captcha_input=captcha_input) + else: + sms_request_response = ds.request_sms_code(captcha_input=captcha_input) + + print(f"{name}: SMS code request response: {sms_request_response}") + + if sms_request_response.get("Message") == "Incorrect verification code.": + raise Exception("Incorrect CAPTCHA code!") + + return sms_request_response # Return the response if successful + return None # Ensure the function always returns an expression + + except Exception as e: + print(f"{name}: Error during CAPTCHA code step: {e}") + print(traceback.format_exc()) + return None + + +def send_sms_code(ds, name, use_email, captcha_input=None): + if use_email == "TRUE": + sms_code_response = ds.request_email_code(captcha_input=captcha_input) + else: + sms_code_response = ds.request_sms_code(captcha_input=captcha_input) + print(f"{name}: SMS code request response: {sms_code_response}") + + if sms_code_response.get("Message") == "Incorrect verification code.": + print(f"{name}: Incorrect CAPTCHA code, retrying...") + return False + + return sms_code_response + + +def dspac_holdings(dso: Brokerage, loop=None): + for key in dso.get_account_numbers(): + for account in dso.get_account_numbers(key): + obj: DSPACAPI = dso.get_logged_in_objects(key, "ds") + try: + positions = obj.get_account_holdings() + print(f"Raw holdings data: {positions}") + + if 'Data' in positions: + for holding in positions['Data']: + qty = holding["CurrentAmount"] + if float(qty) == 0: + continue + sym = holding["displaySymbol"] + cp = holding["Last"] + print(f"Stock Ticker: {sym}, Amount: {qty}, Current Price: {cp}") + dso.set_holdings(key, account, sym, qty, cp) + except Exception as e: + printAndDiscord(f"Error getting DSPAC holdings: {e}") + print(traceback.format_exc()) + continue + printHoldings(dso, loop, False) + + +def dspac_transaction(dso: Brokerage, orderObj: stockOrder, loop=None): + print() + print("==============================") + print("DSPAC") + print("==============================") + print() + for s in orderObj.get_stocks(): + for key in dso.get_account_numbers(): + printAndDiscord( + f"{key}: {orderObj.get_action()}ing {orderObj.get_amount()} of {s}", + loop, + ) + for account in dso.get_account_numbers(key): + obj: DSPACAPI = dso.get_logged_in_objects(key, "ds") + try: + quantity = orderObj.get_amount() + is_dry_run = orderObj.get_dry() + + # Execute the buy/sell transaction + response = obj.execute_buy( + symbol=s, + amount=quantity, + account_number=account, + dry_run=is_dry_run + ) + + # Handle the result + if is_dry_run: + message = "Dry Run Success" + if not response.get("Outcome") == "Success": + message = f"Dry Run Failed: {response.get('Message')}" + else: + message = response.get('Message', "Success") + + printAndDiscord( + f"{key}: {orderObj.get_action().capitalize()} {quantity} of {s} in {account}: {message}", + loop, + ) + + except Exception as e: + printAndDiscord(f"{key} {account}: Error placing order: {e}", loop) + print(traceback.format_exc()) + continue diff --git a/helperAPI.py b/helperAPI.py index 9ac46e2a..87558db4 100644 --- a/helperAPI.py +++ b/helperAPI.py @@ -658,6 +658,47 @@ async def getOTPCodeDiscord( return code.content +async def getUserInputDiscord(botObj, prompt, timeout=60, loop=None): + printAndDiscord(prompt, loop) + printAndDiscord(f"Please enter the input or type cancel within {timeout} seconds", loop) + try: + code = await botObj.wait_for( + "message", + check=lambda m: m.author != botObj.user and m.channel.id == int(DISCORD_CHANNEL), + timeout=timeout, + ) + except asyncio.TimeoutError: + printAndDiscord("Timed out waiting for input", loop) + return None + + if code.content.lower() == "cancel": + printAndDiscord("Input canceled by user", loop) + return None + + return code.content + + +async def send_captcha_to_discord(file): + BASE_URL = f"https://discord.com/api/v10/channels/{DISCORD_CHANNEL}/messages" + HEADERS = { + "Authorization": f"Bot {DISCORD_TOKEN}", + } + files = { + 'file': ('captcha.png', file, 'image/png') + } + success = False + while not success: + response = requests.post(BASE_URL, headers=HEADERS, files=files) + if response.status_code == 200: + success = True + elif response.status_code == 429: + rate_limit = response.json()["retry_after"] * 2 + await asyncio.sleep(rate_limit) + else: + print(f"Error sending CAPTCHA image: {response.status_code}: {response.text}") + break + + def maskString(string): # Mask string (12345678 -> xxxx5678) string = str(string) From b43b79c2ada50d19ba4d6367062e14829f78dbe9 Mon Sep 17 00:00:00 2001 From: ImNotOssy Date: Mon, 2 Sep 2024 02:35:55 -0500 Subject: [PATCH 02/24] Update dspacAPI.py --- dspacAPI.py | 73 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/dspacAPI.py b/dspacAPI.py index 1f1d4e38..09fff607 100644 --- a/dspacAPI.py +++ b/dspacAPI.py @@ -237,8 +237,9 @@ def dspac_transaction(dso: Brokerage, orderObj: stockOrder, loop=None): print() for s in orderObj.get_stocks(): for key in dso.get_account_numbers(): + action = orderObj.get_action().lower() printAndDiscord( - f"{key}: {orderObj.get_action()}ing {orderObj.get_amount()} of {s}", + f"{key}: {action}ing {orderObj.get_amount()} of {s}", loop, ) for account in dso.get_account_numbers(key): @@ -247,21 +248,63 @@ def dspac_transaction(dso: Brokerage, orderObj: stockOrder, loop=None): quantity = orderObj.get_amount() is_dry_run = orderObj.get_dry() - # Execute the buy/sell transaction - response = obj.execute_buy( - symbol=s, - amount=quantity, - account_number=account, - dry_run=is_dry_run - ) + if action == "buy": + # Validate the buy transaction + validation_response = obj.validate_buy(symbol=s, amount=quantity, order_side=1, account_number=account) + print(f"Validate Buy Response: {validation_response}") + if validation_response['Outcome'] != 'Success': + printAndDiscord(f"{key} {account}: Validation failed for buying {quantity} of {s}: {validation_response['Message']}", loop) + continue + + # Proceed to execute the buy if not in dry run mode + if not is_dry_run: + buy_response = obj.execute_buy( + symbol=s, + amount=quantity, + account_number=account, + dry_run=is_dry_run + ) + print(f"Execute Buy Response: {buy_response}") + message = buy_response['Message'] + else: + message = "Dry Run Success" + + elif action == "sell": + # Check stock holdings before attempting to sell + holdings_response = obj.check_stock_holdings(symbol=s, account_number=account) + print(f"Check Holdings Response: {holdings_response}") + if holdings_response["Outcome"] != "Success": + printAndDiscord(f"{key} {account}: Error checking holdings: {holdings_response['Message']}", loop) + continue + + available_amount = float(holdings_response["Data"]["enableAmount"]) + + # If trying to sell more than available, skip to the next + if quantity > available_amount: + printAndDiscord(f"{key} {account}: Not enough shares to sell {quantity} of {s}. Available: {available_amount}", loop) + continue + + # Validate the sell transaction + validation_response = obj.validate_sell(symbol=s, amount=quantity, account_number=account) + print(f"Validate Sell Response: {validation_response}") + if validation_response['Outcome'] != 'Success': + printAndDiscord(f"{key} {account}: Validation failed for selling {quantity} of {s}: {validation_response['Message']}", loop) + continue - # Handle the result - if is_dry_run: - message = "Dry Run Success" - if not response.get("Outcome") == "Success": - message = f"Dry Run Failed: {response.get('Message')}" - else: - message = response.get('Message', "Success") + # Proceed to execute the sell if not in dry run mode + if not is_dry_run: + entrust_price = validation_response['Data']['entrustPrice'] + sell_response = obj.execute_sell( + symbol=s, + amount=quantity, + account_number=account, + entrust_price=entrust_price, + dry_run=is_dry_run + ) + print(f"Execute Sell Response: {sell_response}") + message = sell_response['Message'] + else: + message = "Dry Run Success" printAndDiscord( f"{key}: {orderObj.get_action().capitalize()} {quantity} of {s} in {account}: {message}", From fc39fb6d5713a1c1f916503b3bc63a5e822fa2b7 Mon Sep 17 00:00:00 2001 From: ImNotOssy Date: Mon, 2 Sep 2024 02:37:59 -0500 Subject: [PATCH 03/24] Update .env.example --- .env.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.env.example b/.env.example index 3c57ec4d..33a6856f 100644 --- a/.env.example +++ b/.env.example @@ -15,6 +15,11 @@ HEADLESS="true" # at the same brokerage with a comma, then separate account credentials with a colon # BROKER=BROKER_USERNAME:BROKER_PASSWORD,OTHER_BROKER_USERNAME:OTHER_BROKER_PASSWORD +# dSPAC Pro +# USE_EMAIL takes either TRUE or FALSE. TRUE mean email for username, FALSE means phone number +# DSPAC=DSPAC_EMAIL:DSPAC_PASSWORD +DSPAC= + # Chase # CHASE=CHASE_USERNAME:CHASE_PASSWORD:CELL_PHONE_LAST_FOUR:DEBUG(Optional) TRUE/FALSE CHASE= From 1ba3767757e3d6f4ac287c86aebf69c6f7dac986 Mon Sep 17 00:00:00 2001 From: ImNotOssy Date: Tue, 3 Sep 2024 11:46:35 -0500 Subject: [PATCH 04/24] added pip --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 75a9f145..08363fbb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,3 +16,4 @@ setuptools==74.0.0 tastytrade==8.2 vanguard-api==0.2.5 -e git+https://github.com/NelsonDane/webull.git@ef14ae63f9e1436fbea77fe864df54847cf2f730#egg=webull +-e git+https://github.com/ImNotOssy/dSPAC_investing_API#egg=dSPAC-investing-API \ No newline at end of file From 11973f4bf2099c7f56321df31abc2a9624898692 Mon Sep 17 00:00:00 2001 From: ImNotOssy Date: Tue, 3 Sep 2024 15:44:29 -0500 Subject: [PATCH 05/24] fixed pip installation --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 39f05c8b..7d19a6b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ asyncio==3.4.3 chaseinvest-api==0.2.4 discord.py==2.4.0 +dspac_invest_api==0.1.0 fennel-invest-api==1.1.0 firstrade==0.0.30 GitPython==3.1.43 @@ -16,4 +17,3 @@ setuptools==74.1.0 tastytrade==8.2 vanguard-api==0.2.5 -e git+https://github.com/NelsonDane/webull.git@ef14ae63f9e1436fbea77fe864df54847cf2f730#egg=webull --e git+https://github.com/ImNotOssy/dSPAC_investing_API#egg=dSPAC-investing-API \ No newline at end of file From 2c9124e729080763867a225e936d066ed783fea1 Mon Sep 17 00:00:00 2001 From: ImNotOssy Date: Tue, 3 Sep 2024 15:44:54 -0500 Subject: [PATCH 06/24] Update dspacAPI.py --- dspacAPI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspacAPI.py b/dspacAPI.py index 09fff607..8bd586f2 100644 --- a/dspacAPI.py +++ b/dspacAPI.py @@ -5,7 +5,7 @@ from dotenv import load_dotenv -from dspac_investing_API import DSPACAPI +from dspac_invest_api import DSPACAPI from helperAPI import ( Brokerage, printAndDiscord, From e944269b1b8d4e77191ea08d967eb66dd5cec32b Mon Sep 17 00:00:00 2001 From: matthew55 <78285385+matthew55@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:50:14 -0400 Subject: [PATCH 07/24] Fix dspac initiation check --- autoRSA.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoRSA.py b/autoRSA.py index 459566be..ab916492 100644 --- a/autoRSA.py +++ b/autoRSA.py @@ -126,7 +126,7 @@ def fun_run(orderObj: stockOrder, command, botObj=None, loop=None): globals()[fun_name](DOCKER=DOCKER_MODE, loop=loop), broker, ) - elif broker.lower() in ["fennel", "firstrade", "public" "dspac"]: + elif broker.lower() in ["fennel", "firstrade", "public", "dspac"]: # Requires bot object and loop orderObj.set_logged_in( globals()[fun_name](botObj=botObj, loop=loop), broker From 34a73201077a643e8bfe2faa75eb5863756352a0 Mon Sep 17 00:00:00 2001 From: ImNotOssy Date: Thu, 5 Sep 2024 10:28:46 -0500 Subject: [PATCH 08/24] Update .env.example --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 33a6856f..607ebc41 100644 --- a/.env.example +++ b/.env.example @@ -17,7 +17,7 @@ HEADLESS="true" # dSPAC Pro # USE_EMAIL takes either TRUE or FALSE. TRUE mean email for username, FALSE means phone number -# DSPAC=DSPAC_EMAIL:DSPAC_PASSWORD +# DSPAC=DSPAC_USERNAME:DSPAC_PASSWORD:USE_EMAIL TRUE/FALSE DSPAC= # Chase From 4c35599a983016990477445144a7ffbb5ae12668 Mon Sep 17 00:00:00 2001 From: matthew55 <78285385+matthew55@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:47:54 -0400 Subject: [PATCH 09/24] Import dspacAPI in autoRSA --- autoRSA.py | 1 + 1 file changed, 1 insertion(+) diff --git a/autoRSA.py b/autoRSA.py index ab916492..dcf6674e 100644 --- a/autoRSA.py +++ b/autoRSA.py @@ -21,6 +21,7 @@ # Custom API libraries from chaseAPI import * + from dspacAPI import * from fennelAPI import * from fidelityAPI import * from firstradeAPI import * From 222a88ebe200921343d8220e66ccd74fc7e44dd4 Mon Sep 17 00:00:00 2001 From: ImNotOssy <85385882+ImNotOssy@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:49:18 -0500 Subject: [PATCH 10/24] Update requirements.txt updated pip --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index aab11bb7..17edfd1f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ asyncio==3.4.3 chaseinvest-api==0.2.4 discord.py==2.4.0 -dspac_invest_api==0.1.0 +dspac_invest_api==0.1.1 fennel-invest-api==1.1.0 firstrade==0.0.30 GitPython==3.1.43 From f9f5fc48268591204dbf4dac7299ed6d989f0411 Mon Sep 17 00:00:00 2001 From: ImNotOssy Date: Thu, 5 Sep 2024 20:52:36 -0500 Subject: [PATCH 11/24] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 17edfd1f..7e92c39f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ asyncio==3.4.3 chaseinvest-api==0.2.4 discord.py==2.4.0 -dspac_invest_api==0.1.1 +dspac_invest_api==0.1.2 fennel-invest-api==1.1.0 firstrade==0.0.30 GitPython==3.1.43 From e843bb548fbbb7490dc89ee691d9c0dae44d36f0 Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Sat, 7 Sep 2024 13:41:56 -0400 Subject: [PATCH 12/24] small clean --- dspacAPI.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dspacAPI.py b/dspacAPI.py index 8bd586f2..47ccba50 100644 --- a/dspacAPI.py +++ b/dspacAPI.py @@ -17,8 +17,6 @@ getUserInputDiscord, ) -load_dotenv() - def dspac_init(DSPAC_EXTERNAL=None, botObj=None, loop=None): load_dotenv() @@ -73,7 +71,7 @@ def dspac_init(DSPAC_EXTERNAL=None, botObj=None, loop=None): return dspac_obj -def login(ds, botObj, name, loop, use_email): +def login(ds: DSPACAPI, botObj, name, loop, use_email): try: # API call to generate the login ticket if use_email == "TRUE": From b4980e543a3f5d0f968e8d1a1be51d92401eb210 Mon Sep 17 00:00:00 2001 From: maxxrk Date: Sat, 7 Sep 2024 21:24:55 -0500 Subject: [PATCH 13/24] fix email login and cli otp --- dspacAPI.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/dspacAPI.py b/dspacAPI.py index 47ccba50..b92d68a4 100644 --- a/dspacAPI.py +++ b/dspacAPI.py @@ -97,15 +97,21 @@ def login(ds: DSPACAPI, botObj, name, loop, use_email): raise Exception("Error solving SMS or Captcha") print(f"{name}: Waiting for OTP code from user...") - otp_code = asyncio.run_coroutine_threadsafe( - getOTPCodeDiscord(botObj, name, timeout=300, loop=loop), - loop, - ).result() + if botObj is None: + otp_code = input("Enter OTP code: ") + else: + otp_code = asyncio.run_coroutine_threadsafe( + getOTPCodeDiscord(botObj, name, timeout=300, loop=loop), + loop, + ).result() if otp_code is None: raise Exception("No SMS code received") print(f"{name}: OTP code received: {otp_code}") - ticket_response = ds.generate_login_ticket_sms(sms_code=otp_code) + if use_email == "TRUE": + ticket_response = ds.generate_login_ticket_email(sms_code=otp_code) + else: + ticket_response = ds.generate_login_ticket_sms(sms_code=otp_code) if "Message" in ticket_response and ticket_response["Message"] == "Incorrect verification code.": raise Exception("Incorrect OTP code") From 12cca623b2d792fb22e59dbbc441624c2e44cb9b Mon Sep 17 00:00:00 2001 From: maxxrk Date: Sat, 7 Sep 2024 21:32:17 -0500 Subject: [PATCH 14/24] change code entry format --- dspacAPI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspacAPI.py b/dspacAPI.py index b92d68a4..4ff858e2 100644 --- a/dspacAPI.py +++ b/dspacAPI.py @@ -98,7 +98,7 @@ def login(ds: DSPACAPI, botObj, name, loop, use_email): print(f"{name}: Waiting for OTP code from user...") if botObj is None: - otp_code = input("Enter OTP code: ") + otp_code = input("Enter code: ") else: otp_code = asyncio.run_coroutine_threadsafe( getOTPCodeDiscord(botObj, name, timeout=300, loop=loop), From 1a2b651f9d2cc628d985edf34f9baa5f6b02d10e Mon Sep 17 00:00:00 2001 From: ImNotOssy <85385882+ImNotOssy@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:46:45 -0500 Subject: [PATCH 15/24] Update dspacAPI.py --- dspacAPI.py | 73 +++++++++++++++++++++-------------------------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/dspacAPI.py b/dspacAPI.py index 4ff858e2..f593628e 100644 --- a/dspacAPI.py +++ b/dspacAPI.py @@ -3,18 +3,18 @@ import traceback from io import BytesIO +from dspac_invest_api import DSPACAPI from dotenv import load_dotenv -from dspac_invest_api import DSPACAPI from helperAPI import ( Brokerage, - printAndDiscord, - printHoldings, getOTPCodeDiscord, + getUserInputDiscord, maskString, - stockOrder, + printAndDiscord, + printHoldings, send_captcha_to_discord, - getUserInputDiscord, + stockOrder, ) @@ -24,7 +24,6 @@ def dspac_init(DSPAC_EXTERNAL=None, botObj=None, loop=None): if not os.getenv("DSPAC") and DSPAC_EXTERNAL is None: print("DSPAC not found, skipping...") return None - DSPAC = ( os.environ["DSPAC"].strip().split(",") if DSPAC_EXTERNAL is None @@ -34,35 +33,27 @@ def dspac_init(DSPAC_EXTERNAL=None, botObj=None, loop=None): for index, account in enumerate(DSPAC): name = f"DSPAC {index + 1}" try: - user, password, use_email = account.split(":") - use_email = use_email.upper() - ds = DSPACAPI(user, password, filename=f"DSPAC_{index + 1}.txt", creds_path="./creds/") - - # Initial API call to establish session and get initial cookies - print(f"{name}: Making initial request to establish session...") - ds.make_initial_request() - + user, password = account.split(":")[:2] + use_email = "@" in user + # Initialize the DSPAC API object + bb = DSPACAPI( + user, password, filename=f"DSPAC_{index + 1}.pkl", creds_path="./creds/" + ) + bb.make_initial_request() # All the rest of the requests responsible for getting authenticated - print(f"{name}: Attempting to login...") - login(ds, botObj, name, loop, use_email) - - print(f"{name}: Retrieving account assets...") - account_assets = ds.get_account_assets() - - print(f"{name}: Retrieving account information...") - account_info = ds.get_account_info() - - account_number = str(account_info['Data']['accountNumber']) - - # Mask the account number before printing it + login(bb, botObj, name, loop, use_email) + account_assets = bb.get_account_assets() + account_info = bb.get_account_info() + account_number = str(account_info["Data"]["accountNumber"]) + # Set account values masked_account_number = maskString(account_number) - print(f"{name}: Found account {masked_account_number}") - dspac_obj.set_account_number(name, masked_account_number) - dspac_obj.set_account_totals(name, masked_account_number, float(account_assets['Data']['totalAssets'])) - - dspac_obj.set_logged_in_object(name, ds, "ds") - print(f"{name}: Logged in with account number {masked_account_number}") + dspac_obj.set_account_totals( + name, + masked_account_number, + float(account_assets["Data"]["totalAssets"]), + ) + dspac_obj.set_logged_in_object(name, bb, "bb") except Exception as e: print(f"Error logging into DSPAC: {e}") print(traceback.format_exc()) @@ -71,7 +62,7 @@ def dspac_init(DSPAC_EXTERNAL=None, botObj=None, loop=None): return dspac_obj -def login(ds: DSPACAPI, botObj, name, loop, use_email): +def login(ds, botObj, name, loop, use_email): try: # API call to generate the login ticket if use_email == "TRUE": @@ -97,21 +88,15 @@ def login(ds: DSPACAPI, botObj, name, loop, use_email): raise Exception("Error solving SMS or Captcha") print(f"{name}: Waiting for OTP code from user...") - if botObj is None: - otp_code = input("Enter code: ") - else: - otp_code = asyncio.run_coroutine_threadsafe( - getOTPCodeDiscord(botObj, name, timeout=300, loop=loop), - loop, - ).result() + otp_code = asyncio.run_coroutine_threadsafe( + getOTPCodeDiscord(botObj, name, timeout=300, loop=loop), + loop, + ).result() if otp_code is None: raise Exception("No SMS code received") print(f"{name}: OTP code received: {otp_code}") - if use_email == "TRUE": - ticket_response = ds.generate_login_ticket_email(sms_code=otp_code) - else: - ticket_response = ds.generate_login_ticket_sms(sms_code=otp_code) + ticket_response = ds.generate_login_ticket_sms(sms_code=otp_code) if "Message" in ticket_response and ticket_response["Message"] == "Incorrect verification code.": raise Exception("Incorrect OTP code") From 8a4ea01aacca5b31df24babe1959813c793e41cc Mon Sep 17 00:00:00 2001 From: ImNotOssy <85385882+ImNotOssy@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:48:12 -0500 Subject: [PATCH 16/24] Update .env.example --- .env.example | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 607ebc41..bb70cb19 100644 --- a/.env.example +++ b/.env.example @@ -15,15 +15,14 @@ HEADLESS="true" # at the same brokerage with a comma, then separate account credentials with a colon # BROKER=BROKER_USERNAME:BROKER_PASSWORD,OTHER_BROKER_USERNAME:OTHER_BROKER_PASSWORD -# dSPAC Pro -# USE_EMAIL takes either TRUE or FALSE. TRUE mean email for username, FALSE means phone number -# DSPAC=DSPAC_USERNAME:DSPAC_PASSWORD:USE_EMAIL TRUE/FALSE -DSPAC= - # Chase # CHASE=CHASE_USERNAME:CHASE_PASSWORD:CELL_PHONE_LAST_FOUR:DEBUG(Optional) TRUE/FALSE CHASE= +# dSPAC Pro +# DSPAC=DSPAC_USERNAME:DSPAC_PASSWORD +DSPAC= + # Fennel # FENNEL=FENNEL_EMAIL FENNEL= @@ -72,4 +71,4 @@ VANGUARD= # Webull # WEBULL=WEBULL_USERNAME:WEBULL_PASSWORD:WEBULL_DID:WEBULL_TRADING_PIN -WEBULL= \ No newline at end of file +WEBULL= From ed0d6c30e4bf3fffa2e723a8666fbf3ac1254b3a Mon Sep 17 00:00:00 2001 From: ImNotOssy <85385882+ImNotOssy@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:52:39 -0500 Subject: [PATCH 17/24] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fb3d0677..523c6c8c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ asyncio==3.4.3 bbae-invest-api==0.1.3 chaseinvest-api==0.2.4 discord.py==2.4.0 -dspac_invest_api==0.1.2 +dspac_invest_api==0.1.3 fennel-invest-api==1.1.0 firstrade==0.0.30 GitPython==3.1.43 From 0b81c0bd4386746dbafd7dbe92179eacf6e50fce Mon Sep 17 00:00:00 2001 From: ImNotOssy <85385882+ImNotOssy@users.noreply.github.com> Date: Tue, 10 Sep 2024 13:14:28 -0500 Subject: [PATCH 18/24] Update dspacAPI.py --- dspacAPI.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/dspacAPI.py b/dspacAPI.py index f593628e..f3665abf 100644 --- a/dspacAPI.py +++ b/dspacAPI.py @@ -36,14 +36,14 @@ def dspac_init(DSPAC_EXTERNAL=None, botObj=None, loop=None): user, password = account.split(":")[:2] use_email = "@" in user # Initialize the DSPAC API object - bb = DSPACAPI( + ds = DSPACAPI( user, password, filename=f"DSPAC_{index + 1}.pkl", creds_path="./creds/" ) - bb.make_initial_request() + ds.make_initial_request() # All the rest of the requests responsible for getting authenticated - login(bb, botObj, name, loop, use_email) - account_assets = bb.get_account_assets() - account_info = bb.get_account_info() + login(ds, botObj, name, loop, use_email) + account_assets = ds.get_account_assets() + account_info = ds.get_account_info() account_number = str(account_info["Data"]["accountNumber"]) # Set account values masked_account_number = maskString(account_number) @@ -53,7 +53,7 @@ def dspac_init(DSPAC_EXTERNAL=None, botObj=None, loop=None): masked_account_number, float(account_assets["Data"]["totalAssets"]), ) - dspac_obj.set_logged_in_object(name, bb, "bb") + dspac_obj.set_logged_in_object(name, ds, "ds") except Exception as e: print(f"Error logging into DSPAC: {e}") print(traceback.format_exc()) @@ -194,10 +194,10 @@ def send_sms_code(ds, name, use_email, captcha_input=None): return sms_code_response -def dspac_holdings(dso: Brokerage, loop=None): - for key in dso.get_account_numbers(): - for account in dso.get_account_numbers(key): - obj: DSPACAPI = dso.get_logged_in_objects(key, "ds") +def dspac_holdings(ds: Brokerage, loop=None): + for key in ds.get_account_numbers(): + for account in ds.get_account_numbers(key): + obj: DSPACAPI = ds.get_logged_in_objects(key, "ds") try: positions = obj.get_account_holdings() print(f"Raw holdings data: {positions}") @@ -210,29 +210,29 @@ def dspac_holdings(dso: Brokerage, loop=None): sym = holding["displaySymbol"] cp = holding["Last"] print(f"Stock Ticker: {sym}, Amount: {qty}, Current Price: {cp}") - dso.set_holdings(key, account, sym, qty, cp) + ds.set_holdings(key, account, sym, qty, cp) except Exception as e: printAndDiscord(f"Error getting DSPAC holdings: {e}") print(traceback.format_exc()) continue - printHoldings(dso, loop, False) + printHoldings(ds, loop, False) -def dspac_transaction(dso: Brokerage, orderObj: stockOrder, loop=None): +def dspac_transaction(ds: Brokerage, orderObj: stockOrder, loop=None): print() print("==============================") print("DSPAC") print("==============================") print() for s in orderObj.get_stocks(): - for key in dso.get_account_numbers(): + for key in ds.get_account_numbers(): action = orderObj.get_action().lower() printAndDiscord( f"{key}: {action}ing {orderObj.get_amount()} of {s}", loop, ) - for account in dso.get_account_numbers(key): - obj: DSPACAPI = dso.get_logged_in_objects(key, "ds") + for account in ds.get_account_numbers(key): + obj: DSPACAPI = ds.get_logged_in_objects(key, "ds") try: quantity = orderObj.get_amount() is_dry_run = orderObj.get_dry() From 81487d99898fedac693fbeb3f0372f409605ca7a Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Tue, 10 Sep 2024 19:01:15 -0400 Subject: [PATCH 19/24] match bbae --- README.md | 10 ++++ dspacAPI.py | 142 +++++++++++++++++++++++----------------------------- 2 files changed, 72 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 41e47f73..259bcf7a 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,16 @@ Optional .env variables: `.env` file format: - `CHASE=CHASE_USERNAME:CHASE_PASSWORD:CELL_PHONE_LAST_FOUR:DEBUG` +#### DSPAC +Made by [ImNotOssy](https://github.com/ImNotOssy) using the [dSPAC_investing_api](https://github.com/ImNotOssy/dSPAC_investing_API). Go give them a ⭐ +- `DSPAC_USERNAME` +- `DSPAC_PASSWORD` + +`.env` file format: +- `DSPAC=DSPAC_USERNAME:DSPAC_PASSWORD` + +Note: `DSPAC_USERNAME` can either be email or phone number. + ### Fennel Made by yours truly using the [fennel-invest-api](https://github.com/NelsonDane/fennel-invest-api). Consider giving me a ⭐ diff --git a/dspacAPI.py b/dspacAPI.py index f3665abf..47172e6e 100644 --- a/dspacAPI.py +++ b/dspacAPI.py @@ -62,54 +62,54 @@ def dspac_init(DSPAC_EXTERNAL=None, botObj=None, loop=None): return dspac_obj -def login(ds, botObj, name, loop, use_email): +def login(ds: DSPACAPI, botObj, name, loop, use_email): try: # API call to generate the login ticket - if use_email == "TRUE": - print(f"{name}: Generating login ticket (Email)...") + if use_email: ticket_response = ds.generate_login_ticket_email() else: - print(f"{name}: Generating login ticket (SMS)...") ticket_response = ds.generate_login_ticket_sms() - - # Log the raw response details - print(f"{name}: Initial ticket response: {ticket_response}") - - # Ensure 'Data' key exists and proceed with verification if necessary - if 'Data' not in ticket_response: + # Ensure "Data" key exists and proceed with verification if necessary + if ticket_response.get("Data") is None: raise Exception("Invalid response from generating login ticket") - # Check if SMS or CAPTCHA verification are required data = ticket_response['Data'] if data.get('needSmsVerifyCode', False): # TODO 8/30/24: CAPTCHA should only be needed if SMS is needed. Is this true? - sms_and_captcha_response = handle_captcha_and_sms(ds, botObj, data, loop, name, use_email) + sms_and_captcha_response = handle_captcha_and_sms( + ds, botObj, data, loop, name, use_email + ) if not sms_and_captcha_response: raise Exception("Error solving SMS or Captcha") - - print(f"{name}: Waiting for OTP code from user...") - otp_code = asyncio.run_coroutine_threadsafe( - getOTPCodeDiscord(botObj, name, timeout=300, loop=loop), - loop, - ).result() + # Get the OTP code from the user + if botObj is not None and loop is not None: + otp_code = asyncio.run_coroutine_threadsafe( + getOTPCodeDiscord(botObj, name, timeout=300, loop=loop), + loop, + ).result() + else: + otp_code = input("Enter security code: ") if otp_code is None: raise Exception("No SMS code received") - - print(f"{name}: OTP code received: {otp_code}") - ticket_response = ds.generate_login_ticket_sms(sms_code=otp_code) - - if "Message" in ticket_response and ticket_response["Message"] == "Incorrect verification code.": + # Login with the OTP code + if use_email: + ticket_response = ds.generate_login_ticket_email(sms_code=otp_code) + else: + ticket_response = ds.generate_login_ticket_sms(sms_code=otp_code) + if ticket_response.get("Message") == "Incorrect verification code.": raise Exception("Incorrect OTP code") - # Handle the login ticket - if 'Data' in ticket_response and 'ticket' in ticket_response['Data']: + if ( + ticket_response.get("Data") is not None + and ticket_response["Data"].get("ticket") is not None + ): ticket = ticket_response['Data']['ticket'] else: - print(f"{name}: Raw response object: {ticket_response}") raise Exception(f"Login failed. No ticket generated. Response: {ticket_response}") - - print(f"{name}: Logging in with ticket...") - ds.login_with_ticket(ticket) + # Login with the ticket + login_response = ds.login_with_ticket(ticket) + if login_response.get("Outcome") != "Success": + raise Exception(f"Login failed. Response: {login_response}") return True except Exception as e: print(f"Error in SMS login: {e}") @@ -117,7 +117,7 @@ def login(ds, botObj, name, loop, use_email): return False -def handle_captcha_and_sms(ds, botObj, data, loop, name, use_email): +def handle_captcha_and_sms(ds: DSPACAPI, botObj, data, loop, name, use_email): try: if data.get('needCaptchaCode', False): print(f"{name}: CAPTCHA required. Requesting CAPTCHA image...") @@ -132,65 +132,61 @@ def handle_captcha_and_sms(ds, botObj, data, loop, name, use_email): raise Exception("Unable to retrieve sms code!") print(f"{name}: SMS response is: {sms_response}") return True - except Exception as e: print(f"Error in CAPTCHA or SMS: {e}") print(traceback.format_exc()) return False -def solve_captcha(ds, botObj, name, loop, use_email): +def solve_captcha(ds: DSPACAPI, botObj, name, loop, use_email): try: captcha_image = ds.request_captcha() if not captcha_image: raise Exception("Unable to request CAPTCHA image, aborting...") - + # Send the CAPTCHA image to Discord for manual input print("Sending CAPTCHA to Discord for user input...") file = BytesIO() captcha_image.save(file, format="PNG") file.seek(0) - - asyncio.run_coroutine_threadsafe( - send_captcha_to_discord(file), - loop, - ).result() - - captcha_input = asyncio.run_coroutine_threadsafe( - getUserInputDiscord(botObj, f"{name} requires CAPTCHA input", timeout=300, loop=loop), - loop, - ).result() - - if captcha_input: - if use_email == "TRUE": - sms_request_response = ds.request_email_code(captcha_input=captcha_input) - else: - sms_request_response = ds.request_sms_code(captcha_input=captcha_input) - - print(f"{name}: SMS code request response: {sms_request_response}") - - if sms_request_response.get("Message") == "Incorrect verification code.": - raise Exception("Incorrect CAPTCHA code!") - - return sms_request_response # Return the response if successful - return None # Ensure the function always returns an expression - + # Retrieve input + if botObj is not None and loop is not None: + asyncio.run_coroutine_threadsafe( + send_captcha_to_discord(file), + loop, + ).result() + captcha_input = asyncio.run_coroutine_threadsafe( + getUserInputDiscord(botObj, f"{name} requires CAPTCHA input", timeout=300, loop=loop), + loop, + ).result() + else: + captcha_image.save("./captcha.png", format="PNG") + captcha_input = input( + "CAPTCHA image saved to ./captcha.png. Please open it and type in the code: " + ) + if captcha_input is None: + raise Exception("No CAPTCHA code found") + # Send the CAPTCHA to the appropriate API based on login type + if use_email: + sms_request_response = ds.request_email_code(captcha_input=captcha_input) + else: + sms_request_response = ds.request_sms_code(captcha_input=captcha_input) + if sms_request_response.get("Message") == "Incorrect verification code.": + raise Exception("Incorrect CAPTCHA code!") + return sms_request_response except Exception as e: print(f"{name}: Error during CAPTCHA code step: {e}") print(traceback.format_exc()) return None -def send_sms_code(ds, name, use_email, captcha_input=None): - if use_email == "TRUE": +def send_sms_code(ds: DSPACAPI, name, use_email, captcha_input=None): + if use_email: sms_code_response = ds.request_email_code(captcha_input=captcha_input) else: sms_code_response = ds.request_sms_code(captcha_input=captcha_input) - print(f"{name}: SMS code request response: {sms_code_response}") - if sms_code_response.get("Message") == "Incorrect verification code.": print(f"{name}: Incorrect CAPTCHA code, retrying...") return False - return sms_code_response @@ -200,9 +196,7 @@ def dspac_holdings(ds: Brokerage, loop=None): obj: DSPACAPI = ds.get_logged_in_objects(key, "ds") try: positions = obj.get_account_holdings() - print(f"Raw holdings data: {positions}") - - if 'Data' in positions: + if positions.get("Data") is not None: for holding in positions['Data']: qty = holding["CurrentAmount"] if float(qty) == 0: @@ -236,15 +230,13 @@ def dspac_transaction(ds: Brokerage, orderObj: stockOrder, loop=None): try: quantity = orderObj.get_amount() is_dry_run = orderObj.get_dry() - + # Buy if action == "buy": # Validate the buy transaction validation_response = obj.validate_buy(symbol=s, amount=quantity, order_side=1, account_number=account) - print(f"Validate Buy Response: {validation_response}") if validation_response['Outcome'] != 'Success': printAndDiscord(f"{key} {account}: Validation failed for buying {quantity} of {s}: {validation_response['Message']}", loop) continue - # Proceed to execute the buy if not in dry run mode if not is_dry_run: buy_response = obj.execute_buy( @@ -253,33 +245,26 @@ def dspac_transaction(ds: Brokerage, orderObj: stockOrder, loop=None): account_number=account, dry_run=is_dry_run ) - print(f"Execute Buy Response: {buy_response}") message = buy_response['Message'] else: message = "Dry Run Success" - + # Sell elif action == "sell": # Check stock holdings before attempting to sell holdings_response = obj.check_stock_holdings(symbol=s, account_number=account) - print(f"Check Holdings Response: {holdings_response}") if holdings_response["Outcome"] != "Success": printAndDiscord(f"{key} {account}: Error checking holdings: {holdings_response['Message']}", loop) continue - available_amount = float(holdings_response["Data"]["enableAmount"]) - # If trying to sell more than available, skip to the next if quantity > available_amount: printAndDiscord(f"{key} {account}: Not enough shares to sell {quantity} of {s}. Available: {available_amount}", loop) continue - # Validate the sell transaction validation_response = obj.validate_sell(symbol=s, amount=quantity, account_number=account) - print(f"Validate Sell Response: {validation_response}") if validation_response['Outcome'] != 'Success': printAndDiscord(f"{key} {account}: Validation failed for selling {quantity} of {s}: {validation_response['Message']}", loop) continue - # Proceed to execute the sell if not in dry run mode if not is_dry_run: entrust_price = validation_response['Data']['entrustPrice'] @@ -290,16 +275,13 @@ def dspac_transaction(ds: Brokerage, orderObj: stockOrder, loop=None): entrust_price=entrust_price, dry_run=is_dry_run ) - print(f"Execute Sell Response: {sell_response}") message = sell_response['Message'] else: message = "Dry Run Success" - printAndDiscord( f"{key}: {orderObj.get_action().capitalize()} {quantity} of {s} in {account}: {message}", loop, ) - except Exception as e: printAndDiscord(f"{key} {account}: Error placing order: {e}", loop) print(traceback.format_exc()) From f2fd46e7a15a2051b093d9a64851cb08558513a5 Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Tue, 10 Sep 2024 19:02:58 -0400 Subject: [PATCH 20/24] fix alpha --- autoRSA.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/autoRSA.py b/autoRSA.py index 661bfd16..aa0b4d6c 100644 --- a/autoRSA.py +++ b/autoRSA.py @@ -87,10 +87,10 @@ # Account nicknames def nicknames(broker): - if broker == "ds": - return "dspac" if broker == "bb": return "bbae" + if broker == "ds": + return "dspac" if broker in ["fid", "fido"]: return "fidelity" if broker == "ft": @@ -133,7 +133,7 @@ def fun_run(orderObj: stockOrder, command, botObj=None, loop=None): broker, ) - elif broker.lower() in ["bbae", "fennel", "firstrade", "public", "dspac"]: + elif broker.lower() in ["bbae", "dspac", "fennel", "firstrade", "public"]: # Requires bot object and loop orderObj.set_logged_in( globals()[fun_name](botObj=botObj, loop=loop), broker From da4e7037f40609f82d50b51ace713c97e5469469 Mon Sep 17 00:00:00 2001 From: matthew55 <78285385+matthew55@users.noreply.github.com> Date: Tue, 10 Sep 2024 19:27:29 -0400 Subject: [PATCH 21/24] Add dspac to day1 in README --- .env.example | 2 +- README.md | 1 + dspacAPI.py | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 35814174..c9419c5a 100644 --- a/.env.example +++ b/.env.example @@ -23,7 +23,7 @@ BBAE= # CHASE=CHASE_USERNAME:CHASE_PASSWORD:CELL_PHONE_LAST_FOUR:DEBUG(Optional) TRUE/FALSE CHASE= -# dSPAC Pro +# dSPAC # DSPAC=DSPAC_USERNAME:DSPAC_PASSWORD DSPAC= diff --git a/README.md b/README.md index ffa5cfd7..98bc87d9 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ For help: Note: There are two special keywords you can use when specifying accounts: `all` and `day1`. `all` will use every account that you have set up. `day1` will use "day 1" brokers, which are: - BBAE - Chase +- DSPAC - Fennel - Firstrade - Public diff --git a/dspacAPI.py b/dspacAPI.py index 47172e6e..3f32e3f0 100644 --- a/dspacAPI.py +++ b/dspacAPI.py @@ -75,7 +75,6 @@ def login(ds: DSPACAPI, botObj, name, loop, use_email): # Check if SMS or CAPTCHA verification are required data = ticket_response['Data'] if data.get('needSmsVerifyCode', False): - # TODO 8/30/24: CAPTCHA should only be needed if SMS is needed. Is this true? sms_and_captcha_response = handle_captcha_and_sms( ds, botObj, data, loop, name, use_email ) From ce5f9c97d7779d52dd59e433d91b0700bfad478d Mon Sep 17 00:00:00 2001 From: maxxrk Date: Wed, 18 Sep 2024 11:26:31 -0500 Subject: [PATCH 22/24] fix extra print --- dspacAPI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspacAPI.py b/dspacAPI.py index 3f32e3f0..5d8a0f67 100644 --- a/dspacAPI.py +++ b/dspacAPI.py @@ -202,7 +202,7 @@ def dspac_holdings(ds: Brokerage, loop=None): continue sym = holding["displaySymbol"] cp = holding["Last"] - print(f"Stock Ticker: {sym}, Amount: {qty}, Current Price: {cp}") + #print(f"Stock Ticker: {sym}, Amount: {qty}, Current Price: {cp}") ds.set_holdings(key, account, sym, qty, cp) except Exception as e: printAndDiscord(f"Error getting DSPAC holdings: {e}") From 803acdd49dedd4fd5291e2997417beddf56f00e3 Mon Sep 17 00:00:00 2001 From: maxxrk Date: Wed, 18 Sep 2024 11:46:32 -0500 Subject: [PATCH 23/24] remove comment --- dspacAPI.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dspacAPI.py b/dspacAPI.py index 5d8a0f67..16dd9349 100644 --- a/dspacAPI.py +++ b/dspacAPI.py @@ -202,7 +202,6 @@ def dspac_holdings(ds: Brokerage, loop=None): continue sym = holding["displaySymbol"] cp = holding["Last"] - #print(f"Stock Ticker: {sym}, Amount: {qty}, Current Price: {cp}") ds.set_holdings(key, account, sym, qty, cp) except Exception as e: printAndDiscord(f"Error getting DSPAC holdings: {e}") From 582f9926f237cfa82c67ebba3aa384d2287ee0af Mon Sep 17 00:00:00 2001 From: Nelson Dane <47427072+NelsonDane@users.noreply.github.com> Date: Sat, 21 Sep 2024 18:51:58 -0400 Subject: [PATCH 24/24] small wording fix --- dspacAPI.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspacAPI.py b/dspacAPI.py index 16dd9349..ee652aa2 100644 --- a/dspacAPI.py +++ b/dspacAPI.py @@ -89,7 +89,7 @@ def login(ds: DSPACAPI, botObj, name, loop, use_email): else: otp_code = input("Enter security code: ") if otp_code is None: - raise Exception("No SMS code received") + raise Exception("No OTP code received") # Login with the OTP code if use_email: ticket_response = ds.generate_login_ticket_email(sms_code=otp_code) @@ -111,7 +111,7 @@ def login(ds: DSPACAPI, botObj, name, loop, use_email): raise Exception(f"Login failed. Response: {login_response}") return True except Exception as e: - print(f"Error in SMS login: {e}") + print(f"Error in OTP login: {e}") print(traceback.format_exc()) return False