Skip to content

Commit

Permalink
Merge pull request #323 from MaxxRK/new_firstrade
Browse files Browse the repository at this point in the history
New firstrade
  • Loading branch information
NelsonDane authored Aug 19, 2024
2 parents 8911efd + 6959782 commit 49e46a6
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 29 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ FENNEL=
FIDELITY=

# Firstrade
# FIRSTRADE=FIRSTRADE_USERNAME:FIRSTRADE_PASSWORD:FIRSTRADE_PIN
# FIRSTRADE=FIRSTRADE_USERNAME:FIRSTRADE_PASSWORD:FIRSTRADE_OTP
FIRSTRADE=

# Public
Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,18 @@ Made by [MaxxRK](https://github.com/MaxxRK/) using the [firstrade-api](https://g
Required `.env` variables:
- `FIRSTRADE_USERNAME`
- `FIRSTRADE_PASSWORD`
- `FIRSTRADE_PIN`
- `FIRSTRADE_OTP`

`.env` file format:
- `FIRSTRADE=FIRSTRADE_USERNAME:FIRSTRADE_PASSWORD:FIRSTRADE_PIN`
- `FIRSTRADE=FIRSTRADE_USERNAME:FIRSTRADE_PASSWORD:FIRSTRADE_OTP`

- Note: You may now use any firstrade supported 2fa method, including pin, SMS, Email, or an Authenticator. Place any ONE of the below in the `FIRSTRADE_OTP` field:
- Pin: Use the login pin you created when setting up the account.
- Phone: Enter your 10 digit phone number with no spaces. I.E. 1234567890
- Email: Enter your full email address. I.E. [email protected]
- Authenticator: Use the code generated by Firstrade when adding an authenticator. Click on "Can't scan it?" to get the code.

If you get errors after upgrading, try clearing your cookies in the `creds` folder and then trying again.

### Public
Made by yours truly using using [public-invest-api](https://github.com/NelsonDane/public-invest-api). Consider giving me a ⭐
Expand Down
2 changes: 1 addition & 1 deletion autoRSA.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def fun_run(orderObj: stockOrder, command, botObj=None, loop=None):
),
broker,
)
elif broker.lower() in ["fennel", "public"]:
elif broker.lower() in ["fennel", "firstrade", "public"]:
# Requires bot object and loop
orderObj.set_logged_in(
globals()[fun_name](botObj=botObj, loop=loop), broker
Expand Down
63 changes: 39 additions & 24 deletions firstradeAPI.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Donald Ryan Gullett(MaxxRK)
# Firstrade API

import asyncio
import os
import pprint
import traceback
Expand All @@ -10,20 +11,17 @@
from firstrade import account as ft_account
from firstrade import order, symbols

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


def firstrade_init(FIRSTRADE_EXTERNAL=None):
def firstrade_init(botObj=None, loop=None):
# Initialize .env file
load_dotenv()
# Import Firstrade account
if not os.getenv("FIRSTRADE") and FIRSTRADE_EXTERNAL is None:
if not os.getenv("FIRSTRADE"):
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...")
Expand All @@ -36,17 +34,32 @@ def firstrade_init(FIRSTRADE_EXTERNAL=None):
firstrade = ft_account.FTSession(
username=account[0],
password=account[1],
pin=account[2],
pin=account[2] if len(account[2]) == 4 and account[2].isdigit() else None,
phone=account[2][-4:] if len(account[2]) == 10 and account[2].isdigit() else None,
email=account[2] if "@" in account[2] else None,
mfa_secret=account[2] if len(account[2]) > 14 and "@" not in account[2] else None,
profile_path="./creds/",
)
account_info = ft_account.FTAccountData(firstrade)
need_code = firstrade.login()
if need_code:
if botObj is None and loop is None:
firstrade.login_two(input("Enter code: "))
else:
sms_code = asyncio.run_coroutine_threadsafe(
getOTPCodeDiscord(botObj, name, timeout=300, loop=loop), loop
).result()
if sms_code is None:
raise Exception(
f"Firstrade {index} code not received in time...", loop
)
firstrade.login_two(sms_code)
print("Logged in to Firstrade!")
account_info = ft_account.FTAccountData(firstrade)
firstrade_obj.set_logged_in_object(name, firstrade)
for entry in account_info.all_accounts:
account = list(entry.keys())[0]
for account in account_info.account_numbers:
firstrade_obj.set_account_number(name, account)
firstrade_obj.set_account_totals(
name, account, str(entry[account]["Balance"])
name, account, account_info.account_balances[account]
)
print_accounts = [maskString(a) for a in account_info.account_numbers]
print(f"The following Firstrade accounts were found: {print_accounts}")
Expand All @@ -64,13 +77,14 @@ def firstrade_holdings(firstrade_o: Brokerage, loop=None):
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)
for item in data["items"]:
firstrade_o.set_holdings(
key,
account,
item.get("symbol") or "Unknown",
item["quantity"],
item["market_value"],
)
except Exception as e:
printAndDiscord(f"{key} {account}: Error getting holdings: {e}", loop)
print(traceback.format_exc())
Expand Down Expand Up @@ -100,7 +114,7 @@ def firstrade_transaction(firstrade_o: Brokerage, orderObj: stockOrder, loop=Non
"Running in DRY mode. No transactions will be made.", loop
)
try:
symbol_data = symbols.SymbolQuote(obj, s)
symbol_data = symbols.SymbolQuote(obj, account, s)
if symbol_data.last < 1.00:
price_type = order.PriceType.LIMIT
if orderObj.get_action().capitalize() == "Buy":
Expand All @@ -115,7 +129,7 @@ def firstrade_transaction(firstrade_o: Brokerage, orderObj: stockOrder, loop=Non
else:
order_type = order.OrderType.SELL
ft_order = order.Order(obj)
ft_order.place_order(
order_conf = ft_order.place_order(
account=account,
symbol=s,
price_type=price_type,
Expand All @@ -125,20 +139,21 @@ def firstrade_transaction(firstrade_o: Brokerage, orderObj: stockOrder, loop=Non
price=price,
dry_run=orderObj.get_dry(),
)

print("The order verification produced the following messages: ")
pprint.pprint(ft_order.order_confirmation)
pprint.pprint(order_conf)
printAndDiscord(
(
f"{key} account {print_account}: The order verification was "
+ "successful"
if ft_order.order_confirmation["success"] == "Yes"
if order_conf["error"] == ""
else "unsuccessful"
),
loop,
)
if not ft_order.order_confirmation["success"] == "Yes":
if not order_conf["error"] == "":
printAndDiscord(
f"{key} account {print_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: {order_conf}",
loop,
)
except Exception as e:
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ asyncio==3.4.3
chaseinvest-api==0.2.3
discord.py==2.4.0
fennel-invest-api==1.1.0
firstrade==0.0.21
firstrade==0.0.30
GitPython==3.1.43
public-invest-api==1.0.4
pyotp==2.9.0
Expand Down

0 comments on commit 49e46a6

Please sign in to comment.