Skip to content

Commit

Permalink
Add Firstrade Support (NelsonDane#120)
Browse files Browse the repository at this point in the history
* add firsttrade

* finish firstrade

* add firstrade to dockerfile

* remove duplicate import

* fix order

* fix incorrect comment

* correct object def

* add vars to readme

* ignore pickle files

* small formatting fix

* add traceback

* change ft api version

* fix firstrade buy only

* change ft_api version

* fix firstrade api to use acount dict

* update ft_api version

* change ft_api version

* remove redundant variable

* code cleanup

* Deepsource style fix

* add new print to ft

* update readme

* 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

* update mask string

* 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

---------

Co-authored-by: Donald Ryan Gullett <[email protected]>
Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Jan 11, 2024
1 parent 187cbdd commit c6e8f62
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 4 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ __pycache__/
did.bin
test.py
test[0-9].py
*.pkl
*.png
.vscode/
*venv/
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ RUN playwright install && \
COPY ./autoRSA.py .
COPY ./entrypoint.sh .
COPY ./fidelityAPI.py .
COPY ./firstradeAPI.py .
COPY ./helperAPI.py .
COPY ./robinhoodAPI.py .
COPY ./schwabAPI.py .
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,17 @@ 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 ⭐

Required `.env` variables:
- `FIRSTRADE_USERNAME`
- `FIRSTRADE_PASSWORD`
- `FIRSTRADE_PIN`

`.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 ⭐

Expand Down Expand Up @@ -177,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
Expand Down
14 changes: 12 additions & 2 deletions autoRSA.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

# Custom API libraries
from fidelityAPI import *
from firstradeAPI import *
from helperAPI import stockOrder, updater
from robinhoodAPI import *
from schwabAPI import *
Expand All @@ -29,15 +30,24 @@


# Global variables
SUPPORTED_BROKERS = ["fidelity", "robinhood", "schwab", "tastytrade", "tradier"]
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


# Account nicknames
def nicknames(broker):
if broker == "ft":
return "firstrade"
if broker == "rh":
return "robinhood"
if broker == "tasty":
Expand Down
146 changes: 146 additions & 0 deletions firstradeAPI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Donald Ryan Gullett(MaxxRK)
# Firstrade API

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, maskString, printAndDiscord, printHoldings, stockOrder


def firstrade_init(FIRSTRADE_EXTERNAL=None):
# Initialize .env file
load_dotenv()
# Import Firstrade 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(":")
firstrade = ft_account.FTSession(
username=account[0], password=account[1], pin=account[2]
)
account_info = ft_account.FTAccountData(firstrade)
print("Logged in to Firstrade!")
firstrade_obj.set_logged_in_object(name, firstrade)
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, str(entry[account]["Balance"])
)
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}")
print(traceback.format_exc())
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: ft_account.FTSession = 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)
print(traceback.format_exc())
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: ft_account.FTSession = firstrade_o.get_logged_in_objects(key)
print_account = maskString(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
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,
price_type=price_type,
order_type=order_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 {print_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 {print_account}: The order verification produced the following messages: {ft_order.order_confirmation['actiondata']}",
loop,
)
except Exception as e:
printAndDiscord(
f"{key} {print_account}: Error submitting order: {e}", loop
)
print(traceback.format_exc())
continue
sleep(1)
print()
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
asyncio==3.4.3
discord.py==2.3.2
firstrade==0.0.12
GitPython==3.1.41
pandas==2.1.4
pyotp==2.9.0
Expand Down

0 comments on commit c6e8f62

Please sign in to comment.