-
-
Notifications
You must be signed in to change notification settings - Fork 44
/
chaseAPI.py
352 lines (326 loc) · 14.9 KB
/
chaseAPI.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# Donald Ryan Gullett(MaxxRK)
# Chase API
import asyncio
import os
import pprint
import traceback
from chase import account as ch_account
from chase import order, session, symbols
from dotenv import load_dotenv
from helperAPI import (
Brokerage,
getOTPCodeDiscord,
printAndDiscord,
printHoldings,
stockOrder
)
def chase_run(
orderObj: stockOrder, command=None, botObj=None, loop=None, CHASE_EXTERNAL=None
):
# Initialize .env file
load_dotenv()
# Import Chase account
if not os.getenv("CHASE") and CHASE_EXTERNAL is None:
print("Chase not found, skipping...")
return None
accounts = (
os.environ["CHASE"].strip().split(",")
if CHASE_EXTERNAL is None
else CHASE_EXTERNAL.strip().split(",")
)
# Get headless flag
headless = os.getenv("HEADLESS", "true").lower() == "true"
# Set the functions to be run
_, second_command = command
# For each set of login info, i.e. seperate chase accounts
for account in accounts:
# Start at index 1 and go to how many logins we have
index = accounts.index(account) + 1
# Receive the chase broker class object and the AllAccount object related to it
chase_details = chase_init(
account=account,
index=index,
headless=headless,
botObj=botObj,
loop=loop,
)
if chase_details is not None:
orderObj.set_logged_in(chase_details[0], "chase")
if second_command == "_holdings":
chase_holdings(chase_details[0], chase_details[1], loop=loop)
# Only other option is _transaction
else:
chase_transaction(
chase_details[0], chase_details[1], orderObj, loop=loop
)
return None
def get_account_id(account_connectors, value):
for key, val in account_connectors.items():
if val[0] == value:
return key
return None
def chase_init(account: str, index: int, headless=True, botObj=None, loop=None):
"""
Logs into chase. Checks for 2FA and gathers details on the chase accounts
Args:
account (str): The chase username, password, last 4 of phone #, and possible debug flag, seperated by ':'.
index (int): The index of this chase account in a list of accounts.
headless (bool): Whether to run the browser in headless mode.
botObj (Bot): The discord bot object if used.
loop (AbstractEventLoop): The event loop to be used
Raises:
Exception: Error logging in to Chase
Returns:
Brokerage object which represents the chase session and data.
AllAccounts object which holds account information.
"""
# Log in to Chase account
print("Logging in to Chase...")
# Create brokerage class object and call it chase
chase_obj = Brokerage("Chase")
name = f"Chase {index}"
try:
# Split the login into into seperate items
account = account.split(":")
# If the debug flag is present, use it, else set it to false
debug = bool(account[3]) if len(account) == 4 else False
# Create a ChaseSession class object which automatically configures and opens a browser
ch_session = session.ChaseSession(
title=f"chase_{index}",
headless=headless,
profile_path="./creds",
debug=debug,
)
# Login to chase
need_second = ch_session.login(account[0], account[1], account[2])
# If 2FA is present, ask for code
if need_second:
if botObj is None and loop is None:
ch_session.login_two(input("Enter code: "))
else:
sms_code = asyncio.run_coroutine_threadsafe(
getOTPCodeDiscord(botObj, name, code_len=8, loop=loop), loop
).result()
if sms_code is None:
raise Exception(f"Chase {index} code not received in time...", loop)
ch_session.login_two(sms_code)
# Create an AllAccounts class object using the current browser session. Holds information about all accounts
all_accounts = ch_account.AllAccount(ch_session)
# Get the account IDs and store in a list. The IDs are different than account numbers.
account_ids = list(all_accounts.account_connectors.keys())
print("Logged in to Chase!")
# In the Chase Brokerage object, set the index of "Chase 1" to be its own empty array and append the chase session to the end of this array
chase_obj.set_logged_in_object(name, ch_session)
# Create empty array to store account number masks (last 4 digits of each account number)
print_accounts = []
for acct in account_ids:
# Create an AccountDetails Object which organizes the information in the AllAccounts class object
account = ch_account.AccountDetails(acct, all_accounts)
# Save account masks
chase_obj.set_account_number(name, account.mask)
chase_obj.set_account_totals(name, account.mask, account.account_value)
print_accounts.append(account.mask)
print(f"The following Chase accounts were found: {print_accounts}")
except Exception as e:
ch_session.close_browser()
print(f"Error logging in to Chase: {e}")
print(traceback.format_exc())
return None
return [chase_obj, all_accounts]
def chase_holdings(chase_o: Brokerage, all_accounts: ch_account.AllAccount, loop=None):
"""
Get the holdings of chase account
Args:
chase_o (Brokerage): Brokerage object associated with the current session.
all_accounts (AllAccount): AllAccount object that holds account information.
loop (AbstractEventLoop): The event loop to be used if present.
"""
# Get holdings on each account. This loop only ever runs once.
for key in chase_o.get_account_numbers():
try:
# Retrieve account masks and iterate through them
for _, account in enumerate(chase_o.get_account_numbers(key)):
# Retrieve the chase session
ch_session: session.ChaseSession = chase_o.get_logged_in_objects(key)
# Get the account ID accociated with mask
account_id = get_account_id(all_accounts.account_connectors, account)
data = symbols.SymbolHoldings(account_id, ch_session)
success = data.get_holdings()
if success:
for i, _ in enumerate(data.positions):
if (
data.positions[i]["instrumentLongName"]
== "Cash and Sweep Funds"
):
sym = data.positions[i]["instrumentLongName"]
current_price = data.positions[i]["marketValue"][
"baseValueAmount"
]
qty = "1"
chase_o.set_holdings(key, account, sym, qty, current_price)
elif data.positions[i]["assetCategoryName"] == "EQUITY":
try:
sym = data.positions[i]["positionComponents"][0][
"securityIdDetail"
][0]["symbolSecurityIdentifier"]
current_price = data.positions[i]["marketValue"][
"baseValueAmount"
]
qty = data.positions[i]["tradedUnitQuantity"]
except KeyError:
sym = data.positions[i]["securityIdDetail"][
"cusipIdentifier"
]
current_price = data.positions[i]["marketValue"][
"baseValueAmount"
]
qty = data.positions[i]["tradedUnitQuantity"]
chase_o.set_holdings(key, account, sym, qty, current_price)
except Exception as e:
ch_session.close_browser()
printAndDiscord(f"{key} {account}: Error getting holdings: {e}", loop)
print(traceback.format_exc())
continue
printHoldings(chase_o, loop)
ch_session.close_browser()
def chase_transaction(
chase_obj: Brokerage,
all_accounts: ch_account.AllAccount,
orderObj: stockOrder,
loop=None,
):
"""
Executes transactions on all accounts.
Args:
chase_obj (Brokerage): The brokerage class object related to the chase session.
all_accounts (AllAccount): AllAccount object that holds account information.
orderObj (stockOrder): The order(s) to be executed.
loop (AbstractEventLoop): The event loop to be used if present.
Returns:
None
"""
print()
print("==============================")
print("Chase")
print("==============================")
print()
# Buy on each account
for ticker in orderObj.get_stocks():
# This loop should only run once, but it provides easy access to the chase session by using key to get it back from
# the chase_obj via get_logged_in_objects
for key in chase_obj.get_account_numbers():
# Declare for later
price_type = order.PriceType.MARKET
limit_price = 0.0
# Load the chase session
ch_session: session.ChaseSession = chase_obj.get_logged_in_objects(key)
# Determine limit or market for buy orders
if orderObj.get_action().capitalize() == "Buy":
account_ids = list(all_accounts.account_connectors.keys())
# Get the ask price and determine whether to use MARKET or LIMIT order
symbol_quote = symbols.SymbolQuote(
account_id=account_ids[0], session=ch_session, symbol=ticker
)
# If it should be limit
if symbol_quote.ask_price < 1:
price_type = order.PriceType.LIMIT
if symbol_quote.ask_price > 0.10:
# Set limit price
limit_price = round(symbol_quote.ask_price + 0.01, 2)
else:
# Set limit price always round up
factor = 10**2
value = symbol_quote.ask_price * factor
if value % 1 != 0:
value = int(value) + 1
limit_price = value / factor
printAndDiscord(
f"{key} {orderObj.get_action()}ing {orderObj.get_amount()} {ticker} @ {price_type.value}",
loop,
)
try:
print(chase_obj.get_account_numbers())
# For each account number "mask" attached to "Chase_#" complete the order
for account in chase_obj.get_account_numbers(key):
target_account_id = get_account_id(
all_accounts.account_connectors, 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
)
if orderObj.get_action().capitalize() == "Buy":
order_type = order.OrderSide.BUY
else:
# Reset to market for selling
price_type = order.PriceType.MARKET
order_type = order.OrderSide.SELL
chase_order = order.Order(ch_session)
messages = chase_order.place_order(
account_id=target_account_id,
quantity=int(orderObj.get_amount()),
price_type=price_type,
symbol=ticker,
duration=order.Duration.DAY,
order_type=order_type,
dry_run=orderObj.get_dry(),
limit_price=limit_price,
)
print("The order verification produced the following messages: ")
if orderObj.get_dry():
pprint.pprint(messages["ORDER PREVIEW"])
printAndDiscord(
(
f"{key} account {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 {account}: The order verification produced the following messages: {messages['ORDER INVALID']}",
loop,
)
else:
pprint.pprint(messages["ORDER CONFIRMATION"])
printAndDiscord(
(
f"{key} account {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 {account}: The order verification produced the following messages: {messages['ORDER INVALID']}",
loop,
)
except Exception as e:
printAndDiscord(f"{key} {account}: Error submitting order: {e}", loop)
print(traceback.format_exc())
continue
ch_session.close_browser()
printAndDiscord(
"All Chase transactions complete",
loop,
)