firstrade 0.0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
firstrade/account.py ADDED
@@ -0,0 +1,220 @@
1
+ import requests
2
+ import pickle
3
+ import re
4
+ import os
5
+ from time import sleep
6
+ from bs4 import BeautifulSoup
7
+ from firstrade import urls
8
+
9
+
10
+ class FTSession:
11
+ """
12
+ Class creating a session for Firstrade.
13
+ """
14
+ def __init__(self, username, password, pin, persistent_session=True):
15
+ """
16
+ Initializes a new instance of the FTSession class.
17
+
18
+ Args:
19
+ username (str): Firstrade login username.
20
+ password (str): Firstrade login password.
21
+ pin (str): Firstrade login pin.
22
+ persistent_session (bool, optional): Whether the user wants to save the session cookies. Defaults to False.
23
+ """
24
+ self.username = username
25
+ self.password = password
26
+ self.pin = pin
27
+ self.persistent_session = persistent_session
28
+ self.session = requests.Session()
29
+ self.cookies = {}
30
+ self.login()
31
+
32
+ def login(self):
33
+ """
34
+ Method to validate and login to the Firstrade platform.
35
+ """
36
+ headers = urls.session_headers()
37
+ cookies = self.load_cookies()
38
+ cookies = requests.utils.cookiejar_from_dict(cookies)
39
+ self.session.cookies.update(cookies)
40
+ response = self.session.get(
41
+ url=urls.get_xml(), headers=urls.session_headers(), cookies=cookies
42
+ )
43
+ if response.status_code != 200:
44
+ raise Exception('Login failed. Check your credentials or internet connection.')
45
+ if "/cgi-bin/sessionfailed?reason=6" in response.text:
46
+ self.session.get(url=urls.login(), headers=headers)
47
+ data = {
48
+ 'redirect': '',
49
+ 'ft_locale': 'en-us',
50
+ 'login.x': 'Log In',
51
+ 'username': r'' + self.username,
52
+ 'password': r'' + self.password,
53
+ 'destination_page': 'home'
54
+ }
55
+
56
+ self.session.post(
57
+ url=urls.login(), headers=headers,
58
+ cookies=self.session.cookies, data=data
59
+ )
60
+ data = {
61
+ 'destination_page': 'home',
62
+ 'pin': self.pin,
63
+ 'pin.x': '++OK++',
64
+ 'sring': '0',
65
+ 'pin': self.pin
66
+ }
67
+
68
+ self.session.post(
69
+ url=urls.pin(), headers=headers,
70
+ cookies=self.session.cookies, data=data
71
+ )
72
+ if self.persistent_session:
73
+ self.save_cookies()
74
+ self.cookies = self.session.cookies
75
+ if "/cgi-bin/sessionfailed?reason=6" in self.session.get(
76
+ url=urls.get_xml(), headers=urls.session_headers(), cookies=cookies
77
+ ).text:
78
+ raise Exception('Login failed. Check your credentials.')
79
+
80
+ def load_cookies(self):
81
+ """
82
+ Checks if session cookies were saved.
83
+
84
+ Returns:
85
+ Dict: Dictionary of cookies. Nom Nom
86
+ """
87
+ cookies = {}
88
+ for filename in os.listdir('.'):
89
+ if filename.endswith(f'{self.username}.pkl'):
90
+ with open(filename, 'rb') as f:
91
+ cookies = pickle.load(f)
92
+ return cookies
93
+
94
+ def save_cookies(self):
95
+ """
96
+ Saves session cookies to a file.
97
+ """
98
+ with open(f'ft_cookies{self.username}.pkl', 'wb') as f:
99
+ pickle.dump(self.session.cookies.get_dict(), f)
100
+
101
+ def __getattr__(self, name):
102
+ """
103
+ Forwards unknown attribute access to session object.
104
+
105
+ Args:
106
+ name (str): The name of the attribute to be accessed.
107
+
108
+ Returns:
109
+ The value of the requested attribute from the session object.
110
+ """
111
+ return getattr(self.session, name)
112
+
113
+
114
+ class FTAccountData:
115
+ """
116
+ Dataclass for storing account information.
117
+ """
118
+ def __init__(self, session):
119
+ """
120
+ Initializes a new instance of the FTAccountData class.
121
+
122
+ Args:
123
+ session (requests.Session): The session object used for making HTTP requests.
124
+ """
125
+ self.session = session
126
+ self.cookies = self.session.cookies
127
+ self.html_string = ''
128
+ self.all_accounts = []
129
+ self.account_numbers = []
130
+ self.account_statuses = []
131
+ self.account_balances = []
132
+ self.securities_held = {}
133
+ all_account_info = []
134
+ self.html_string = self.session.get(
135
+ url=urls.account_list(),
136
+ headers=urls.session_headers(),
137
+ cookies=self.cookies
138
+ ).text
139
+ regex_accounts = re.findall(
140
+ r"([0-9]+)-", self.html_string
141
+ )
142
+
143
+ for match in regex_accounts:
144
+ self.account_numbers.append(match)
145
+
146
+ for account in self.account_numbers:
147
+ data = {'accountId': account}
148
+ self.session.post(
149
+ url=urls.account_status(),
150
+ headers=urls.session_headers(),
151
+ cookies=self.session.cookies,
152
+ data=data
153
+ )
154
+ sleep(1)
155
+ data = {
156
+ 'page': 'bal',
157
+ 'account_id': account
158
+ }
159
+ account_soup = BeautifulSoup(self.session.post(
160
+ url=urls.get_xml(),
161
+ headers=urls.session_headers(),
162
+ cookies=self.session.cookies,
163
+ data=data,
164
+ ).text, 'xml')
165
+ balance = account_soup.find('total_account_value').text
166
+ self.account_balances.append(balance)
167
+ data = { 'req': 'get_status'}
168
+ account_status = self.session.post(
169
+ url=urls.status(),
170
+ headers=urls.session_headers(),
171
+ cookies=self.session.cookies,
172
+ data=data
173
+ ).json()
174
+ self.account_statuses.append(account_status['data'])
175
+
176
+ all_account_info.append({account:
177
+ {'Balance': balance, 'Status':
178
+ {'primary':account_status['data']['primary'], 'domestic':account_status['data']['domestic'],
179
+ 'joint':account_status['data']['joint'], 'ira':account_status['data']['ira'],
180
+ 'hasMargin':account_status['data']['hasMargin'], 'opLevel':account_status['data']['opLevel'], 'p_country':account_status['data']['p_country'],
181
+ 'mrgnStatus':account_status['data']['mrgnStatus'], 'opStatus':account_status['data']['opStatus'], 'margin_id':account_status['data']['margin_id']
182
+ }
183
+ }})
184
+
185
+ self.all_accounts = all_account_info
186
+
187
+ def get_positions(self, account):
188
+ """Gets currently held positions for a given account.
189
+
190
+ Args:
191
+ account (str): Account number of the account you want to get positions for.
192
+
193
+ Returns:
194
+ self.securities_held {dict}: Dict of held positions with the pos. ticker as the key.
195
+ """
196
+ data = {
197
+ 'page': 'pos',
198
+ 'accountId': str(account),
199
+ }
200
+ position_soup = BeautifulSoup(self.session.post(
201
+ url=urls.get_xml(),
202
+ headers=urls.session_headers(),
203
+ data=data,
204
+ cookies=self.cookies
205
+ ).text, 'xml')
206
+
207
+ tickers = position_soup.find_all('symbol')
208
+ quantity = position_soup.find_all('quantity')
209
+ price = position_soup.find_all('price')
210
+ change = position_soup.find_all('change')
211
+ change_percent = position_soup.find_all('changepercent')
212
+ vol = position_soup.find_all('vol')
213
+ for i, ticker in enumerate(tickers):
214
+ ticker = ticker.text
215
+ self.securities_held[ticker] = {
216
+ 'quantity': quantity[i].text,
217
+ 'price': price[i].text, 'change': change[i].text,
218
+ 'change_percent': change_percent[i].text, 'vol': vol[i].text
219
+ }
220
+ return self.securities_held
firstrade/order.py ADDED
@@ -0,0 +1,141 @@
1
+ from firstrade.account import FTSession
2
+ from firstrade import urls
3
+ from enum import Enum
4
+ from bs4 import BeautifulSoup
5
+
6
+
7
+ class PriceType(str, Enum):
8
+ """
9
+ This is an :class: 'enum.Enum' that contains the valid price types for an order.
10
+ """
11
+ LIMIT = '2'
12
+ MARKET = '1'
13
+ STOP = '3'
14
+ STOP_LIMIT = '4'
15
+ TRAILING_STOP_DOLLAR = '5'
16
+ TRAILING_STOP_PERCENT = '6'
17
+
18
+
19
+ class Duration(str, Enum):
20
+ """
21
+ This is an :class:'~enum.Enum' that contains the valid durations for an order.
22
+ """
23
+ DAY = '0'
24
+ GT90 = '1'
25
+ PRE_MARKET = 'A'
26
+ AFTER_MARKET = 'P'
27
+ DAY_EXT = 'D'
28
+
29
+
30
+ class OrderType(str, Enum):
31
+ """
32
+ This is an :class:'~enum.Enum' that contains the valid order types for an order.
33
+ """
34
+ BUY = 'B'
35
+ SELL = 'S'
36
+ SELL_SHORT = 'SS'
37
+ BUY_TO_COVER = 'BC'
38
+
39
+
40
+ class Order:
41
+ """
42
+ This class contains information about an order. It also contains a method to place an order.
43
+ """
44
+ def __init__(self, ft_session: FTSession):
45
+ self.ft_session = ft_session
46
+ self.order_confirmation = {}
47
+
48
+ def place_order(
49
+ self, account, symbol, price_type: PriceType, order_type: OrderType,
50
+ quantity, duration: Duration, price=0.00, dry_run=True
51
+ ):
52
+
53
+ """
54
+ Builds and places an order.
55
+ :attr: 'order_confirmation` contains the order confirmation data after order placement.
56
+
57
+ Args:
58
+ account (str): Account number of the account to place the order in.
59
+ symbol (str): Ticker to place the order for.
60
+ order_type (PriceType): Price Type i.e. LIMIT, MARKET, STOP, etc.
61
+ quantity (float): The number of shares to buy.
62
+ duration (Duration): Duration of the order i.e. DAY, GT90, etc.
63
+ price (float, optional): The price to buy the shares at. Defaults to 0.00.
64
+ dry_run (bool, optional): Whether you want the order to be placed or not.
65
+ Defaults to True.
66
+
67
+ Returns:
68
+ Order:order_confirmation: Dictionary containing the order confirmation data.
69
+ """
70
+
71
+ if dry_run:
72
+ previewOrders = '1'
73
+ else:
74
+ previewOrders = ''
75
+
76
+ if price_type == PriceType.MARKET:
77
+ price = ''
78
+
79
+ data = {
80
+ 'submiturl': '/cgi-bin/orderbar',
81
+ 'orderbar_clordid': '',
82
+ 'orderbar_accountid': '',
83
+ 'stockorderpage': 'yes',
84
+ 'submitOrders': '1',
85
+ 'previewOrders': previewOrders,
86
+ 'lotMethod': '1',
87
+ 'accountType': '1',
88
+ 'quoteprice': '',
89
+ 'viewederror': '',
90
+ 'stocksubmittedcompanyname1': '',
91
+ 'accountId': account,
92
+ 'transactionType': order_type,
93
+ 'quantity': quantity,
94
+ 'symbol': symbol,
95
+ 'priceType': price_type,
96
+ 'limitPrice': price,
97
+ 'duration': duration,
98
+ 'qualifier': '0',
99
+ 'cond_symbol0_0': '',
100
+ 'cond_type0_0': '2',
101
+ 'cond_compare_type0_0': '2',
102
+ 'cond_compare_value0_0': '',
103
+ 'cond_and_or0': '1',
104
+ 'cond_symbol0_1': '',
105
+ 'cond_type0_1': '2',
106
+ 'cond_compare_type0_1': '2',
107
+ 'cond_compare_value0_1': ''
108
+ }
109
+
110
+ order_data = BeautifulSoup(self.ft_session.post(
111
+ url=urls.orderbar(),
112
+ headers=urls.session_headers(),
113
+ data=data
114
+ ).text, 'xml')
115
+ order_confirmation = {}
116
+ order_success = order_data.find('success').text.strip()
117
+ order_confirmation['success'] = order_success
118
+ action_data = order_data.find('actiondata').text.strip()
119
+ if order_success != "No":
120
+ # Extract the table data
121
+ table_start = action_data.find('<table')
122
+ table_end = action_data.find('</table>') + len('</table>')
123
+ table_data = action_data[table_start:table_end]
124
+ table_data = BeautifulSoup(table_data, 'xml')
125
+ titles = table_data.find_all('th')
126
+ data = table_data.find_all('td')
127
+ for i, title in enumerate(titles):
128
+ order_confirmation[f'{title.get_text()}'] = data[i].get_text()
129
+ if not dry_run:
130
+ start_index = action_data.find('Your order reference number is: ') + len('Your order reference number is: ')
131
+ end_index = action_data.find('</div>', start_index)
132
+ order_number = action_data[start_index:end_index]
133
+ else:
134
+ start_index = action_data.find('id="') + len('id="')
135
+ end_index = action_data.find('" style=', start_index)
136
+ order_number = action_data[start_index:end_index]
137
+ order_confirmation['orderid'] = order_number
138
+ else:
139
+ order_confirmation['actiondata'] = action_data
140
+ order_confirmation['errcode'] = order_data.find('errcode').text.strip()
141
+ self.order_confirmation = order_confirmation
firstrade/symbols.py ADDED
@@ -0,0 +1,54 @@
1
+ from bs4 import BeautifulSoup
2
+ from firstrade.account import FTSession
3
+ from firstrade import urls
4
+
5
+
6
+ class SymbolQuote:
7
+ """
8
+ Dataclass containing quote information for a symbol.
9
+
10
+ Attributes:
11
+ ft_session (FTSession): The session object used for making HTTP requests to Firstrade.
12
+ symbol (str): The symbol for which the quote information is retrieved.
13
+ exchange (str): The exchange where the symbol is traded.
14
+ bid (float): The bid price for the symbol.
15
+ ask (float): The ask price for the symbol.
16
+ last (float): The last traded price for the symbol.
17
+ change (float): The change in price for the symbol.
18
+ high (float): The highest price for the symbol during the trading day.
19
+ low (float): The lowest price for the symbol during the trading day.
20
+ volume (str): The volume of shares traded for the symbol.
21
+ company_name (str): The name of the company associated with the symbol.
22
+ """
23
+ def __init__(self, ft_session: FTSession, symbol: str):
24
+ """
25
+ Initializes a new instance of the SymbolQuote class.
26
+
27
+ Args:
28
+ ft_session (FTSession): The session object used for making HTTP requests to Firstrade.
29
+ symbol (str): The symbol for which the quote information is retrieved.
30
+ """
31
+ self.ft_session = ft_session
32
+ self.symbol = symbol
33
+ symbol_data = self.ft_session.get(
34
+ url=urls.quote(self.symbol),
35
+ headers=urls.session_headers()
36
+ )
37
+ soup = BeautifulSoup(symbol_data.text, 'xml')
38
+ quote = soup.find('quote')
39
+ self.symbol = quote.find('symbol').text
40
+ self.exchange = quote.find('exchange').text
41
+ self.bid = float(quote.find('bid').text)
42
+ self.ask = float(quote.find('ask').text)
43
+ self.last = float(quote.find('last').text)
44
+ self.change = float(quote.find('change').text)
45
+ if quote.find('high').text == 'N/A':
46
+ self.high = None
47
+ else:
48
+ self.high = float(quote.find('high').text)
49
+ if quote.find('low').text == 'N/A':
50
+ self.low = 'None'
51
+ else:
52
+ self.low = float(quote.find('low').text)
53
+ self.volume = quote.find('vol').text
54
+ self.company_name = quote.find('companyname').text
firstrade/urls.py ADDED
@@ -0,0 +1,45 @@
1
+
2
+
3
+ def get_xml():
4
+ return 'https://invest.firstrade.com/cgi-bin/getxml'
5
+
6
+
7
+ def login():
8
+ return 'https://invest.firstrade.com/cgi-bin/login'
9
+
10
+
11
+ def pin():
12
+ return 'https://invest.firstrade.com/cgi-bin/enter_pin?destination_page=home'
13
+
14
+
15
+ def account_list():
16
+ return 'https://invest.firstrade.com/cgi-bin/getaccountlist'
17
+
18
+
19
+ def quote(symbol):
20
+ return f'https://invest.firstrade.com/cgi-bin/getxml?page=quo&quoteSymbol={symbol}'
21
+
22
+
23
+ def orderbar():
24
+ return 'https://invest.firstrade.com/cgi-bin/orderbar'
25
+
26
+
27
+ def account_status():
28
+ return 'https://invest.firstrade.com/cgi-bin/account_status'
29
+
30
+
31
+ def status():
32
+ return "https://invest.firstrade.com/scripts/profile/margin_v2.php"
33
+
34
+
35
+ def session_headers():
36
+ headers = {
37
+ 'Accept': '*/*',
38
+ 'Accept-Encoding': 'gzip, deflate, br',
39
+ 'Accept-Language': 'en-US,en;q=0.9',
40
+ 'Host': 'invest.firstrade.com',
41
+ 'Referer': 'https://invest.firstrade.com/cgi-bin/main',
42
+ 'Connection': 'keep-alive',
43
+ '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'
44
+ }
45
+ return headers
@@ -0,0 +1,18 @@
1
+ Copyright <2023> <MAXXRK>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the “Software”), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16
+ NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
+ OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
18
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.1
2
+ Name: firstrade
3
+ Version: 0.0.1.1
4
+ Summary: An unofficial API for Firstrade
5
+ Home-page: https://github.com/MaxxRK/firstrade-api
6
+ Download-URL: https://github.com/MaxxRK/firstrade-api/archive/refs/tags/0.0.1.1.tar.gz
7
+ Author: MaxxRK
8
+ Author-email: maxxrk@pm.me
9
+ License: MIT
10
+ Keywords: FIRSTRADE,API
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Internet :: WWW/HTTP :: Session
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: requests
23
+ Requires-Dist: beautifulsoup4
24
+ Requires-Dist: lxml
25
+
26
+ # firstrade-api
27
+ A reverse-engineered python API to interact with the Firstrade Trading platform.
28
+
29
+ This is not an official api! This api's functionality may change at any time.
30
+
31
+ This api provides a means of buying and selling stocks through Firstrade. It uses the Session class from requests to get authorization cookies. The rest is done with reverse engineered requests to Firstrade's API.
32
+
33
+ ---
34
+
35
+ ## Contribution
36
+ I am new to coding and new to open-source. I would love any help and suggestions!
37
+
38
+ ## Setup
39
+ Install using pypi:
40
+ ```
41
+ pip install firstrade
42
+ ```
43
+
44
+ ## Quikstart
45
+ The code below will:
46
+ - Login and print account info.
47
+ - Get a quote for 'INTC' and print out the information
48
+ - Place a market order for 'INTC' on the first account in the `account_numbers` list
49
+ - Print out the order confirmation
50
+
51
+ ```
52
+ from firstrade import account
53
+ from firstrade import symbols
54
+ from firstrade import order
55
+
56
+ # Create a session
57
+ ft_ss = account.FTSession(username='', password='', pin='')
58
+
59
+ # Get account data
60
+ ft_accounts = account.FTAccountData(ft_ss)
61
+ if len(ft_accounts.account_numbers) < 1:
62
+ raise Exception('No accounts found or an error occured exiting...')
63
+
64
+ # Print ALL account data
65
+ print(ft_accounts.all_accounts)
66
+
67
+ # Print 1st account number.
68
+ print(ft_accounts.account_numbers[0])
69
+
70
+ # Print ALL accounts market values.
71
+ print(ft_accounts.account_balances)
72
+
73
+ # Get quote for INTC
74
+ quote = symbols.SymbolQuote(ft_ss, 'INTC')
75
+ print(f"Symbol: {quote.symbol}")
76
+ print(f"Exchange: {quote.exchange}")
77
+ print(f"Bid: {quote.bid}")
78
+ print(f"Ask: {quote.ask}")
79
+ print(f"Last: {quote.last}")
80
+ print(f"Change: {quote.change}")
81
+ print(f"High: {quote.high}")
82
+ print(f"Low: {quote.low}")
83
+ print(f"Volume: {quote.volume}")
84
+ print(f"Company Name: {quote.company_name}")
85
+
86
+ # Get positions and print them out for an account.
87
+ positions = ft_accounts.get_positions(account=ft_accounts.account_numbers[1])
88
+ for key in ft_accounts.securities_held:
89
+ print(f"Quantity {ft_accounts.securities_held[key]['quantity']} of security {key} held in account {ft_accounts.account_numbers[1]}")
90
+
91
+ # Create an order object.
92
+ ft_order = order.Order(ft_ss)
93
+
94
+ # Place order and print out order confirmation data.
95
+ ft_order.place_order(
96
+ ft_accounts.account_numbers[0],
97
+ symbol='INTC',
98
+ price_type=order.PriceType.MARKET,
99
+ order_type=order.OrderType.BUY,
100
+ quantity=1,
101
+ duration=order.Duration.DAY,
102
+ dry_run=True
103
+ )
104
+
105
+ # Print Order data Dict
106
+ print(ft_order.order_confirmation)
107
+
108
+ # Check if order was successful
109
+ if ft_order.order_confirmation['success'] == 'Yes':
110
+ print('Order placed successfully.')
111
+ # Print Order ID
112
+ print(f"Order ID: {ft_order.order_confirmation['orderid']}.")
113
+ else:
114
+ print('Failed to place order.')
115
+ # Print errormessage
116
+ print(ft_order.order_confirmation['actiondata'])
117
+ ```
118
+ This code is also in test.py
119
+
120
+ ---
121
+
122
+ ## Implemented Features
123
+ - [x] Login
124
+ - [x] Get Quotes
125
+ - [x] Get Account Data
126
+ - [x] Place Orders and Receive order confirmation
127
+ - [x] Get Currently Held Positions
128
+
129
+ ## TO DO
130
+ - [ ] Check on placed order status.
131
+ - [ ] Cancel placed orders
132
+ - [ ] Options
133
+ - [ ] Give me some Ideas!
134
+
135
+ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/O5O6PTOYG)
@@ -0,0 +1,9 @@
1
+ firstrade/account.py,sha256=Vu7kQ4JxMo_Br-5EVVoj20pyWy51RBtyJETx5FGPYLc,7836
2
+ firstrade/order.py,sha256=BmwPSb-kxBGBkZZ8ArbJmZOTx1z-Fq0y8-GbF2pDPAA,4992
3
+ firstrade/symbols.py,sha256=4Uh8DjKhTSmQuR7KKp0OrdPVTBgr2e7k5EsmdNoVDPU,2297
4
+ firstrade/urls.py,sha256=W6iNNypdjUXTjS9pNq74FxkjNJUnCeiXZ-C-mRpMyXY,1168
5
+ firstrade-0.0.1.1.dist-info/LICENSE,sha256=wPEQjDqm5zMBmEcZp219Labmq_YIjhudpZiUzyVKaFA,1057
6
+ firstrade-0.0.1.1.dist-info/METADATA,sha256=LtS9ifSKYZOMwyWItSnFhB7k1wY4bNOFX8nxMRDWKV8,4066
7
+ firstrade-0.0.1.1.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
8
+ firstrade-0.0.1.1.dist-info/top_level.txt,sha256=tdA8v-KDxU1u4VV6soiNWGBlni4ojv_t_j2wFn5nZcs,10
9
+ firstrade-0.0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.41.3)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ firstrade