diff --git a/README.md b/README.md index 8ca9606..1f40127 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,9 @@ The code below will: - Print out the order confirmation ``` -import account -import symbols -import order +from firstrade import account +from firstrade import symbols +from firstrade import order # Create a session ft_ss = account.FTSession(username='', password='', pin='') diff --git a/build/lib/firstrade/__init__.py b/build/lib/firstrade/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/build/lib/firstrade/account.py b/build/lib/firstrade/account.py deleted file mode 100644 index 6d05d54..0000000 --- a/build/lib/firstrade/account.py +++ /dev/null @@ -1,143 +0,0 @@ -import requests -import pickle -import re -import urls - - -class FTSession: - """ - Class creating a session for Firstrade. - """ - def __init__(self, username, password, pin, persistent_session=False): - """ - Initializes a new instance of the FTSession class. - - Args: - username (str): Firstrade login username. - password (str): Firstrade login password. - pin (str): Firstrade login pin. - persistent_session (bool, optional): Whether the user wants to save the session cookies. Defaults to False. - """ - self.username = username - self.password = password - self.pin = pin - self.persistent_session = persistent_session - self.session = requests.Session() - self.cookies = {} - self.login() - - def login(self): - """ - Method to validate and login to the Firstrade platform. - """ - headers = urls.session_headers() - cookies = self.load_cookies() - cookies = requests.utils.cookiejar_from_dict(cookies) - self.session.cookies.update(cookies) - if "/cgi-bin/sessionfailed?reason=6" in self.session.get( - url=urls.get_xml(), headers=urls.session_headers(), cookies=cookies - ).text: - self.session.get(url=urls.login(), headers=headers) - data = { - 'redirect': '', - 'ft_locale': 'en-us', - 'login.x': 'Log In', - 'username': self.username, - 'password': self.password, - 'destination_page': 'home' - } - - self.session.post( - url=urls.login(), headers=headers, - cookies=self.session.cookies, data=data - ) - data = { - 'destination_page': 'home', - 'pin': self.pin, - 'pin.x': '++OK++', - 'sring': '0', - 'pin': self.pin - } - - self.session.post( - url=urls.pin(), headers=headers, - cookies=self.session.cookies, data=data - ) - if self.persistent_session: - self.save_cookies() - self.cookies = self.session.cookies - - def load_cookies(self): - """ - Checks if session cookies were saved. - - Returns: - Dict: Dictionary of cookies. Nom Nom - """ - try: - with open('cookies.pkl', 'rb') as f: - cookies = pickle.load(f) - except FileNotFoundError: - cookies = {} - return cookies - - def save_cookies(self): - """ - Saves session cookies to a file. - """ - with open('cookies.pkl', 'wb') as f: - pickle.dump(self.session.cookies.get_dict(), f) - - def __getattr__(self, name): - """ - Forwards unknown attribute access to session object. - - Args: - name (str): The name of the attribute to be accessed. - - Returns: - The value of the requested attribute from the session object. - """ - return getattr(self.session, name) - - -class FTAccountData: - """ - Dataclass for storing account information. - """ - def __init__(self, session): - """ - Initializes a new instance of the FTAccountData class. - - Args: - session (requests.Session): The session object used for making HTTP requests. - """ - self.session = session - self.cookies = self.session.cookies - self.all_accounts = [] - self.account_numbers = [] - self.account_types = [] - self.account_owners = [] - self.account_balances = [] - all_account_info = [] - html_string = self.session.get( - url=urls.account_list(), - headers=urls.session_headers(), - cookies=self.cookies - ).text - regex_accounts = re.findall( - r'(.*?)(.*?)', html_string - ) - for match in regex_accounts: - start = match[0].split('-')[1] - type = start.split(' ')[0] - owner = start.split(' ')[1] + start.split(' ')[2] - account = match[0].split('-')[0] - balance = float(match[1].replace(',', '')) - self.account_types.append(type) - self.account_owners.append(owner) - self.account_numbers.append(account) - self.account_balances.append(balance) - all_account_info.append({account: {'Type': type, 'Owner': owner, 'Balance': balance}}) - self.all_accounts = all_account_info - diff --git a/build/lib/firstrade/order.py b/build/lib/firstrade/order.py deleted file mode 100644 index bac6f5a..0000000 --- a/build/lib/firstrade/order.py +++ /dev/null @@ -1,131 +0,0 @@ -from account import FTSession -import urls -from enum import Enum -from bs4 import BeautifulSoup - - -class PriceType(str, Enum): - """ - This is an :class: 'enum.Enum' that contains the valid price types for an order. - """ - LIMIT = '2' - MARKET = '1' - STOP = '3' - STOP_LIMIT = '4' - TRAILING_STOP_DOLLAR = '5' - TRAILING_STOP_PERCENT = '6' - - -class Duration(str, Enum): - """ - This is an :class:'~enum.Enum' that contains the valid durations for an order. - """ - DAY = '0' - GT90 = '1' - PRE_MARKET = 'A' - AFTER_MARKET = 'P' - DAY_EXT = 'D' - - -class Order: - """ - This class contains information about an order. It also contains a method to place an order. - """ - def __init__(self, ft_session: FTSession): - self.ft_session = ft_session - self.order_confirmation = {} - - def place_order( - self, account, symbol, order_type: PriceType, - quantity, duration: Duration, price=0.00, dry_run=True - ): - - """ - Builds and places an order. - :attr: 'order_confirmation` contains the order confirmation data after order placement. - - Args: - account (str): Account number of the account to place the order in. - symbol (str): Ticker to place the order for. - order_type (PriceType): Price Type i.e. LIMIT, MARKET, STOP, etc. - quantity (float): The number of shares to buy. - duration (Duration): Duration of the order i.e. DAY, GT90, etc. - price (float, optional): The price to buy the shares at. Defaults to 0.00. - dry_run (bool, optional): Whether you want the order to be placed or not. - Defaults to True. - - Returns: - Order:order_confirmation: Dictionary containing the order confirmation data. - """ - - if dry_run: - previewOrders = '1' - else: - previewOrders = '' - - if order_type == PriceType.MARKET: - price = '' - - data = { - 'submiturl': '/cgi-bin/orderbar', - 'orderbar_clordid': '', - 'orderbar_accountid': '', - 'stockorderpage': 'yes', - 'submitOrders': '1', - 'previewOrders': previewOrders, - 'lotMethod': '1', - 'accountType': '1', - 'quoteprice': '', - 'viewederror': '', - 'stocksubmittedcompanyname1': '', - 'accountId': account, - 'transactionType': 'B', - 'quantity': quantity, - 'symbol': symbol, - 'priceType': order_type, - 'limitPrice': price, - 'duration': duration, - 'qualifier': '0', - 'cond_symbol0_0': '', - 'cond_type0_0': '2', - 'cond_compare_type0_0': '2', - 'cond_compare_value0_0': '', - 'cond_and_or0': '1', - 'cond_symbol0_1': '', - 'cond_type0_1': '2', - 'cond_compare_type0_1': '2', - 'cond_compare_value0_1': '' - } - - order_data = BeautifulSoup(self.ft_session.post( - url=urls.orderbar(), - headers=urls.session_headers(), - data=data - ).text, 'xml') - order_confirmation = {} - order_success = order_data.find('success').text.strip() - order_confirmation['success'] = order_success - action_data = order_data.find('actiondata').text.strip() - if order_success != "No": - # Extract the table data - table_start = action_data.find('') + len('') - table_data = action_data[table_start:table_end] - table_data = BeautifulSoup(table_data, 'xml') - titles = table_data.find_all('th') - data = table_data.find_all('td') - for i, title in enumerate(titles): - order_confirmation[f'{title.get_text()}'] = data[i].get_text() - if not dry_run: - start_index = action_data.find('Your order reference number is: ') + len('Your order reference number is: ') - end_index = action_data.find('', start_index) - order_number = action_data[start_index:end_index] - else: - start_index = action_data.find('id="') + len('id="') - end_index = action_data.find('" style=', start_index) - order_number = action_data[start_index:end_index] - order_confirmation['orderid'] = order_number - else: - order_confirmation['actiondata'] = action_data - order_confirmation['errcode'] = order_data.find('errcode').text.strip() - self.order_confirmation = order_confirmation diff --git a/build/lib/firstrade/symbols.py b/build/lib/firstrade/symbols.py deleted file mode 100644 index 79416f4..0000000 --- a/build/lib/firstrade/symbols.py +++ /dev/null @@ -1,48 +0,0 @@ -from bs4 import BeautifulSoup -from account import FTSession -import urls - - -class SymbolQuote: - """ - Dataclass containing quote information for a symbol. - - Attributes: - ft_session (FTSession): The session object used for making HTTP requests to Firstrade. - symbol (str): The symbol for which the quote information is retrieved. - exchange (str): The exchange where the symbol is traded. - bid (float): The bid price for the symbol. - ask (float): The ask price for the symbol. - last (float): The last traded price for the symbol. - change (float): The change in price for the symbol. - high (float): The highest price for the symbol during the trading day. - low (float): The lowest price for the symbol during the trading day. - volume (str): The volume of shares traded for the symbol. - company_name (str): The name of the company associated with the symbol. - """ - def __init__(self, ft_session: FTSession, symbol: str): - """ - Initializes a new instance of the SymbolQuote class. - - Args: - ft_session (FTSession): The session object used for making HTTP requests to Firstrade. - symbol (str): The symbol for which the quote information is retrieved. - """ - self.ft_session = ft_session - self.symbol = symbol - symbol_data = self.ft_session.get( - url=urls.quote(self.symbol), - headers=urls.session_headers() - ) - soup = BeautifulSoup(symbol_data.text, 'xml') - quote = soup.find('quote') - self.symbol = quote.find('symbol').text - self.exchange = quote.find('exchange').text - self.bid = float(quote.find('bid').text) - self.ask = float(quote.find('ask').text) - self.last = float(quote.find('last').text) - self.change = float(quote.find('change').text) - self.high = float(quote.find('high').text) - self.low = float(quote.find('low').text) - self.volume = quote.find('vol').text - self.company_name = quote.find('companyname').text diff --git a/build/lib/firstrade/urls.py b/build/lib/firstrade/urls.py deleted file mode 100644 index 9bb9ca0..0000000 --- a/build/lib/firstrade/urls.py +++ /dev/null @@ -1,37 +0,0 @@ - - -def get_xml(): - return 'https://invest.firstrade.com/cgi-bin/getxml' - - -def login(): - return 'https://invest.firstrade.com/cgi-bin/login' - - -def pin(): - return 'https://invest.firstrade.com/cgi-bin/enter_pin?destination_page=home' - - -def account_list(): - return 'https://invest.firstrade.com/cgi-bin/getaccountlist' - - -def quote(symbol): - return f'https://invest.firstrade.com/cgi-bin/getxml?page=quo"eSymbol={symbol}' - - -def orderbar(): - return 'https://invest.firstrade.com/cgi-bin/orderbar' - - -def session_headers(): - headers = { - 'Accept': '*/*', - 'Accept-Encoding': 'gzip, deflate, br', - 'Accept-Language': 'en-US,en;q=0.9', - 'Host': 'invest.firstrade.com', - 'Referer': 'https://invest.firstrade.com/cgi-bin/main', - 'Connection': 'keep-alive', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.81' - } - return headers diff --git a/firstrade/__init__.py b/firstrade/__init__.py index e69de29..2c4d592 100644 --- a/firstrade/__init__.py +++ b/firstrade/__init__.py @@ -0,0 +1,4 @@ +from firstrade import account +from firstrade import order +from firstrade import symbols +from firstrade import urls diff --git a/firstrade_api.egg-info/PKG-INFO b/firstrade_api.egg-info/PKG-INFO deleted file mode 100644 index 48bc531..0000000 --- a/firstrade_api.egg-info/PKG-INFO +++ /dev/null @@ -1,7 +0,0 @@ -Metadata-Version: 2.1 -Name: firstrade-api -Version: 0.0.1 -Summary: An unofficial API for Firstrade -Author: MaxxRK -Author-email: maxxrk@pm.me -License-File: LICENSE diff --git a/firstrade_api.egg-info/SOURCES.txt b/firstrade_api.egg-info/SOURCES.txt deleted file mode 100644 index ef250f4..0000000 --- a/firstrade_api.egg-info/SOURCES.txt +++ /dev/null @@ -1,12 +0,0 @@ -LICENSE -README.md -setup.py -firstrade/__init__.py -firstrade/account.py -firstrade/order.py -firstrade/symbols.py -firstrade/urls.py -firstrade_api.egg-info/PKG-INFO -firstrade_api.egg-info/SOURCES.txt -firstrade_api.egg-info/dependency_links.txt -firstrade_api.egg-info/top_level.txt \ No newline at end of file diff --git a/firstrade_api.egg-info/dependency_links.txt b/firstrade_api.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/firstrade_api.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/firstrade_api.egg-info/top_level.txt b/firstrade_api.egg-info/top_level.txt deleted file mode 100644 index a47ec49..0000000 --- a/firstrade_api.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -firstrade