Skip to content

Commit

Permalink
Merge pull request #242 from NelsonDane/develop-vanguard
Browse files Browse the repository at this point in the history
Add Vanguard Support!
  • Loading branch information
NelsonDane authored Jun 11, 2024
2 parents 52821ea + 23162e7 commit ebc052e
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 2 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ TRADIER=
# TASTYTRADE=TASTYTRADE_USERNAME:TASTYTRADE_PASSWORD
TASTYTRADE=

# Vanguard
# VANGUARD=VANGUARD_USERNAME:VANGUARD_PASSWORD:PHONE_LAST_FOUR:DEBUG(Optional) TRUE/FALSE
VANGUARD=

# Webull
# WEBULL=WEBULL_USERNAME:WEBULL_PASSWORD:WEBULL_DID:WEBULL_TRADING_PIN
WEBULL=
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ src/
test*
*venv/
.vscode/
*.zip
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,21 @@ Your `WEBULL_USERNAME` can be your email or phone number. If using a phone numbe

To get your Webull DID, follow this [guide](https://github.com/tedchou12/webull/wiki/Workaround-for-Login-%E2%80%90-Method-2).

### Vanguard
Made by [MaxxRK](https://github.com/MaxxRK/) using the [vanguard-api](https://github.com/MaxxRK/vanguard-api). Go give them a ⭐

Required `.env` variables:
- `VANGUARD_USERNAME`
- `VANGUARD_PASSWORD`
- `CELL_PHONE_LAST_FOUR`

Optional `.env` variables:
- `DEBUG` (Set to `True` to enable debug mode)

`.env` file format:
- `VANGUARD=VANGUARD_USERNAME:VANGUARD_PASSWORD:PHONE_LAST_FOUR:DEBUG`


### 🤷‍♂️ Maybe future brokerages 🤷‍♀️
#### Vanguard
In progress [here](https://github.com/NelsonDane/auto-rsa/pull/242).
Expand Down
8 changes: 6 additions & 2 deletions autoRSA.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from schwabAPI import *
from tastyAPI import *
from tradierAPI import *
from vanguardAPI import *
from webullAPI import *
except Exception as e:
print(f"Error importing libraries: {e}")
Expand All @@ -51,6 +52,7 @@
"schwab",
"tastytrade",
"tradier",
"vanguard",
"webull",
]
DAY1_BROKERS = [
Expand Down Expand Up @@ -79,6 +81,8 @@ def nicknames(broker):
return "robinhood"
if broker == "tasty":
return "tastytrade"
if broker == "vg":
return "vanguard"
if broker == "wb":
return "webull"
return broker
Expand Down Expand Up @@ -106,7 +110,7 @@ def fun_run(orderObj: stockOrder, command, botObj=None, loop=None):
orderObj.set_logged_in(
globals()[fun_name](botObj=botObj, loop=loop), broker
)
elif broker.lower() == "chase":
elif broker.lower() in ["chase", "vanguard"]:
fun_name = broker + "_run"
# PLAYWRIGHT_BROKERS have to run all transactions with one function
th = ThreadHandler(
Expand All @@ -129,7 +133,7 @@ def fun_run(orderObj: stockOrder, command, botObj=None, loop=None):
orderObj.set_logged_in(globals()[fun_name](), broker)

print()
if broker.lower() != "chase":
if broker.lower() not in ["chase", "vanguard"]:
# Verify broker is logged in
orderObj.order_validate(preLogin=False)
logged_in_broker = orderObj.get_logged_in(broker)
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ schwab-api==0.4.0
selenium==4.21.0
setuptools==70.0.0
tastytrade==8.0
vanguard-api==0.1.7
-e git+https://github.com/NelsonDane/webull.git@ef14ae63f9e1436fbea77fe864df54847cf2f730#egg=webull
250 changes: 250 additions & 0 deletions vanguardAPI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
# Donald Ryan Gullett(MaxxRK)
# Vanguard API

import asyncio
import os
import pprint
import traceback

from dotenv import load_dotenv
from vanguard import account as vg_account
from vanguard import order, session

from helperAPI import (
Brokerage,
getOTPCodeDiscord,
maskString,
printAndDiscord,
printHoldings,
stockOrder,
)


def vanguard_run(orderObj: stockOrder, command=None, botObj=None, loop=None):
# Initialize .env file
load_dotenv()
# Import Vanguard account
if not os.getenv("VANGUARD"):
print("Vanguard not found, skipping...")
return None
accounts = os.environ["VANGUARD"].strip().split(",")
# Set the functions to be run
_, second_command = command

for account in accounts:
index = accounts.index(account) + 1
success = vanguard_init(
account=account,
index=index,
botObj=botObj,
loop=loop,
)
if success is not None:
orderObj.set_logged_in(success, "vanguard")
if second_command == "_holdings":
vanguard_holdings(success, loop=loop)
else:
vanguard_transaction(success, orderObj, loop=loop)
return None


def vanguard_init(account, index, botObj=None, loop=None):
# Log in to Vanguard account
print("Logging in to Vanguard...")
vanguard_obj = Brokerage("VANGUARD")
name = f"Vanguard {index}"
try:
account = account.split(":")
debug = bool(account[3]) if len(account) == 4 else False
vg_session = session.VanguardSession(
title=f"Vanguard_{index}",
headless=True,
profile_path="./creds",
debug=debug,
)
need_second = vg_session.login(account[0], account[1], account[2])
if need_second:
if botObj is None and loop is None:
vg_session.login_two(input("Enter code: "))
else:
sms_code = asyncio.run_coroutine_threadsafe(
getOTPCodeDiscord(botObj, name, timeout=120, loop=loop), loop
).result()
if sms_code is None:
raise Exception(
f"Vanguard {index} code not received in time...", loop
)
vg_session.login_two(sms_code)
all_accounts = vg_account.AllAccount(vg_session)
success = all_accounts.get_account_ids()
if not success:
raise Exception("Error getting account details")
print("Logged in to Vanguard!")
vanguard_obj.set_logged_in_object(name, vg_session)
print_accounts = []
for acct in all_accounts.account_totals:
vanguard_obj.set_account_number(name, acct)
vanguard_obj.set_account_totals(
name, acct, all_accounts.account_totals[acct]
)
print_accounts.append(acct)
print(f"The following Vanguard accounts were found: {print_accounts}")
except Exception as e:
vg_session.close_browser()
print(f"Error logging in to Vanguard: {e}")
print(traceback.format_exc())
return None
return vanguard_obj


def vanguard_holdings(vanguard_o: Brokerage, loop=None):
# Get holdings on each account
for key in vanguard_o.get_account_numbers():
try:
obj: session.VanguardSession = vanguard_o.get_logged_in_objects(key)
all_accounts = vg_account.AllAccount(obj)
if all_accounts is None:
raise Exception("Error getting account details")
success = all_accounts.get_holdings()
if success:
for account in all_accounts.accounts_positions:
for account_type in all_accounts.accounts_positions[account].keys():
for stock in all_accounts.accounts_positions[account][
account_type
]:
if float(stock["quantity"]) != 0:
vanguard_o.set_holdings(
key,
account,
stock["symbol"],
stock["quantity"],
stock["price"],
)
else:
raise Exception("Vanguard-api failed to retrieve holdings.")
except Exception as e:
obj.close_browser()
printAndDiscord(f"{key} {account}: Error getting holdings: {e}", loop)
print(traceback.format_exc())
continue
printHoldings(vanguard_o, loop)
obj.close_browser()


def vanguard_transaction(vanguard_o: Brokerage, orderObj: stockOrder, loop=None):
print()
print("==============================")
print("Vanguard")
print("==============================")
print()
# Buy on each account
for s in orderObj.get_stocks():
for key in vanguard_o.get_account_numbers():
printAndDiscord(
f"{key} {orderObj.get_action()}ing {orderObj.get_amount()} {s} @ {orderObj.get_price()}",
loop,
)
try:
for account in vanguard_o.get_account_numbers(key):
print_account = maskString(account)
obj: session.VanguardSession = vanguard_o.get_logged_in_objects(key)
# 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
)
vg_order = order.Order(obj)
price_type = order.PriceType.MARKET
if orderObj.get_action().capitalize() == "Buy":
order_type = order.OrderSide.BUY
else:
order_type = order.OrderSide.SELL
messages = vg_order.place_order(
account_id=account,
quantity=int(orderObj.get_amount()),
price_type=price_type,
symbol=s,
duration=order.Duration.DAY,
order_type=order_type,
dry_run=orderObj.get_dry(),
)
print("The order verification produced the following messages: ")
if (
messages["ORDER CONFIRMATION"]
== "No order confirmation page found. Order Failed."
):
printAndDiscord(
"Market order failed placing limit order.", loop
)
price_type = order.PriceType.LIMIT
price = vg_order.get_quote(s) + 0.01
messages = vg_order.place_order(
account_id=account,
quantity=int(orderObj.get_amount()),
price_type=price_type,
symbol=s,
duration=order.Duration.DAY,
order_type=order_type,
limit_price=price,
dry_run=orderObj.get_dry(),
)
if orderObj.get_dry():
if messages["ORDER PREVIEW"] != "":
pprint.pprint(messages["ORDER PREVIEW"])
printAndDiscord(
(
f"{key} account {print_account}: The order verification was "
+ (
"successful"
if messages["ORDER PREVIEW"]
not in ["", "No order preview page found."]
else "unsuccessful"
)
),
loop,
)
if (
messages["ORDER INVALID"]
!= "No invalid order message found."
):
printAndDiscord(
f"{key} account {print_account}: The order verification produced the following messages: {messages['ORDER INVALID']}",
loop,
)
else:
if messages["ORDER CONFIRMATION"] != "":
pprint.pprint(messages["ORDER CONFIRMATION"])
printAndDiscord(
(
f"{key} account {print_account}: The order verification was "
+ (
"successful"
if messages["ORDER CONFIRMATION"]
not in [
"",
"No order confirmation page found. Order Failed.",
]
else "unsuccessful"
)
),
loop,
)
if (
messages["ORDER INVALID"]
!= "No invalid order message found."
):
printAndDiscord(
f"{key} account {print_account}: The order verification produced the following messages: {messages['ORDER INVALID']}",
loop,
)
except Exception as e:
printAndDiscord(
f"{key} {print_account}: Error submitting order: {e}", loop
)
print(traceback.format_exc())
continue
obj.close_browser()
printAndDiscord(
"All Vanguard transactions complete",
loop,
)

0 comments on commit ebc052e

Please sign in to comment.