def login(cls, auth_cookie_value): if cls.is_logged_in(): raise InvestopediaAuthException( "Already logged in. This exception is to prevent inadvertantly overwriting the auth cookie. Use Session.logout() first" ) if cls.__session is not None and auth_cookie_value == cls.__auth_cookie: warnings.warn( "authenticated session already exists, returning session") return cls.__session url = UrlHelper.route('portfolio') auth_cookie = { "version": 0, "name": 'UI4', "value": auth_cookie_value, "port": None, # "port_specified":False, "domain": '.investopedia.com', # "domain_specified":False, # "domain_initial_dot":False, "path": '/', # "path_specified":True, "secure": False, "expires": None, "discard": True, "comment": None, "comment_url": None, "rest": {}, "rfc2109": False } cls.__session = requests.Session() cls.__session.cookies.set(**auth_cookie) url = UrlHelper.route('home') resp = cls.__session.get(url) if not resp.ok: cls.__session = None raise InvestopediaAuthException( "Got status code %s when fetching %s" % (resp.status_code, url)) tree = html.fromstring(resp.text) sign_out_link = tree.xpath( '//div[@class="left-nav"]//ul/li/a[text()="Sign Out"]') if len(sign_out_link) < 1: warnings.warn( "Could not locate sign out link on home page. Session may not have authenticated." ) return cls.__session
def __init__(self, symbol, quantity, trade_type, order_type=OrderType.MARKET(), duration=Duration.GOOD_TILL_CANCELLED(), send_email=True): self.security_type = 'stock' self.base_url = UrlHelper.route('tradestock') self.submit_url = UrlHelper.route('tradestock_submit') super().__init__(symbol, quantity, trade_type, order_type, duration, send_email)
def __init__( self, contract, quantity, trade_type, order_type=OrderType.MARKET(), duration=Duration.GOOD_TILL_CANCELLED(), send_email=True): self.security_type = 'option' self.base_url = UrlHelper.route('tradeoption') self.submit_url = UrlHelper.route('tradeoption_submit') super().__init__(contract.base_symbol, quantity, trade_type, order_type, duration, send_email) self.contract = contract self.prepared_trade = None
def refresh_form_token(self, tree=None): self.form_token = None if tree is None: self.form_token = None uri = UrlHelper.set_query(self.base_url, self.query_params) resp = Session().get(uri, data=self.form_data) tree = html.fromstring(resp.text) fon = lambda x: x[0] if len(x) > 0 else None token = fon(tree.xpath('//input[@name="formToken"]/@value')) self.form_token = token print("form token: %s" % self.form_token) print("form data: %s" % self.form_data)
def stock_quote(symbol): url = UrlHelper.route('lookup') session = Session() resp = session.post(url, data={'symbol': symbol}) resp.raise_for_status() try: tree = html.fromstring(resp.text) except Exception: warn("unable to get quote for %s" % symbol) return xpath_map = { 'name': '//h3[@class="companyname"]/text()', 'symbol': '//table[contains(@class,"table3")]/tbody/tr[1]/td[1]/h3[contains(@class,"pill")]/text()', 'exchange': '//table[contains(@class,"table3")]//div[@class="marketname"]/text()', 'last': '//table[@id="Table2"]/tbody/tr[1]/th[contains(text(),"Last")]/following-sibling::td/text()', 'change': '//table[@id="Table2"]/tbody/tr[2]/th[contains(text(),"Change")]/following-sibling::td/text()', 'change_percent': '//table[@id="Table2"]/tbody/tr[3]/th[contains(text(),"% Change")]/following-sibling::td/text()', 'volume': '//table[@id="Table2"]/tbody/tr[4]/th[contains(text(),"Volume")]/following-sibling::td/text()', 'days_high': '//table[@id="Table2"]/tbody/tr[5]/th[contains(text(),"Day\'s High")]/following-sibling::td/text()', 'days_low': '//table[@id="Table2"]/tbody/tr[6]/th[contains(text(),"Day\'s Low")]/following-sibling::td/text()' } stock_quote_data = {} try: stock_quote_data = { k: str(tree.xpath(v)[0]).strip() for k, v in xpath_map.items() } except IndexError: warn("Unable to parse quote ") return exchange_matches = re.search(r'^\(([^\)]+)\)$', stock_quote_data['exchange']) if exchange_matches: stock_quote_data['exchange'] = exchange_matches.group(1) quote = StockQuote(**stock_quote_data) return quote
def get_open_trades(portfolio_tree): session = Session() open_trades_resp = session.get(UrlHelper.route('opentrades')) open_tree = html.fromstring(open_trades_resp.text) open_trade_rows = open_tree.xpath( '//table[@class="table1"]/tbody/tr[@class="table_data"]/td[2]/a/parent::td/parent::tr' ) ot_xpath_map = { 'order_id': 'td[1]/text()', 'symbol': 'td[5]/a/text()', 'cancel_fn': 'td[2]/a/@href', 'order_date': 'td[3]/text()', 'quantity': 'td[6]/text()', 'order_price': 'td[7]/text()', 'trade_type': 'td[4]/text()' } open_orders = [] for tr in open_trade_rows: fon = lambda x: x[0] if len(x) > 0 else None open_order_dict = { k: fon(tr.xpath(v)) for k, v in ot_xpath_map.items() } symbol_match = re.search(r'^([^\.\d]+)', open_order_dict['symbol']) if symbol_match: open_order_dict['symbol'] = symbol_match.group(1) if open_order_dict['order_price'] == 'n/a': oid = open_order_dict['order_id'] quantity = int(open_order_dict['quantity']) pxpath = '//table[@id="stock-portfolio-table"]//tr[contains(@style,"italic")]//span[contains(@id,"%s")]/ancestor::tr/td[5]/span/text()' % oid cancel_link = open_order_dict['cancel_fn'] wrapper = CancelOrderWrapper(cancel_link) open_order_dict['cancel_fn'] = wrapper.wrap_cancel try: current_price = coerce_value( fon(portfolio_tree.xpath(pxpath)), Decimal) open_order_dict['order_price'] = current_price * quantity except Exception as e: warn("Unable to parse open trade value for %s" % open_order_dict['symbol']) open_order_dict['order_price'] = 0 open_orders.append(OpenOrder(**open_order_dict)) return open_orders
def login(cls, credentials): if cls.is_logged_in(): warnings.warn( "You are already logged in. If you want to logout call Session.logout(). Returning session" ) return cls.__session url = 'https://www.investopedia.com/auth/realms/investopedia/shopify-auth/inv-simulator/login?&redirectUrl=https%3A%2F%2Fwww.investopedia.com%2Fauth%2Frealms%2Finvestopedia%2Fprotocol%2Fopenid-connect%2Fauth%3Fresponse_type%3Dcode%26approval_prompt%3Dauto%26redirect_uri%3Dhttps%253A%252F%252Fwww.investopedia.com%252Fsimulator%252Fhome.aspx%26client_id%3Dinv-simulator-conf' cls.__session = requests.Session() resp = cls.__session.get(url) tree = html.fromstring(resp.text) script_with_url = tree.xpath('//script/text()')[0] redirect_url = re.search(r'REDIRECT_URL\s=\s"([^"]+)"', script_with_url).group(1) resp = cls.__session.get( redirect_url.encode('utf-8').decode('unicode_escape')) tree = html.fromstring(resp.text) post_url = tree.xpath('//form/@action')[0] payload = credentials resp = cls.__session.post(post_url, data=payload) url = UrlHelper.route('home') resp = cls.__session.get(url) if not resp.ok: cls.__session = None raise InvestopediaAuthException( "Got status code %s when fetching %s" % (resp.status_code, url)) tree = html.fromstring(resp.text) sign_out_link = tree.xpath( '//div[@class="left-nav"]//ul/li/a[text()="Sign Out"]') if len(sign_out_link) < 1: warnings.warn( "Could not locate sign out link on home page. Session may not have authenticated." ) return cls.__session
def get_portfolio(): session = Session() portfolio_response = session.get(UrlHelper.route('portfolio')) portfolio_tree = html.fromstring(portfolio_response.text) stock_portfolio = StockPortfolio() short_portfolio = ShortPortfolio() option_portfolio = OptionPortfolio() Parsers.parse_and_sort_positions(portfolio_tree, stock_portfolio, short_portfolio, option_portfolio) xpath_prefix = '//div[@id="infobar-container"]/div[@class="infobar-title"]/p' xpath_map = { 'account_value': '/strong[contains(text(),"Account Value")]/following-sibling::span/text()', 'buying_power': '/strong[contains(text(),"Buying Power")]/following-sibling::span/text()', 'cash': '/strong[contains(text(),"Cash")]/following-sibling::span/text()', 'annual_return_pct': '/strong[contains(text(),"Annual Return")]/following-sibling::span/text()', } xpath_get = lambda xpth: portfolio_tree.xpath("%s%s" % (xpath_prefix, xpth))[0] portfolio_args = {k: xpath_get(v) for k, v in xpath_map.items()} portfolio_args['stock_portfolio'] = stock_portfolio portfolio_args['short_portfolio'] = short_portfolio portfolio_args['option_portfolio'] = option_portfolio portfolio_args['open_orders'] = Parsers.get_open_trades(portfolio_tree) for order in portfolio_args['open_orders']: print(order.__dict__) return Portfolio(**portfolio_args)
def _get_max_shares(self): if self.form_token is None: self.refresh_form_token() uri = UrlHelper.set_query(self.base_url, self.query_params) self.form_data['isShowMax'] = 1 resp = Session().post(uri, data=self.form_data) tree = html.fromstring(resp.text) self.form_data['isShowMax'] = 0 self.refresh_form_token(tree) fon = lambda x: x[0] if len(x)> 0 else None try: xpath1 = '//div[@id="limitDiv"]/span[@id="limitationLabel"]/text()' xpath2 = '//div[@id="limitDiv"]/span/text()' text = fon(tree.xpath(xpath1)) or fon(tree.xpath(xpath2)) shares_match = re.search( r'maximum\s*of\s*(\d+)\s*(?:shares|option)', text) return int(shares_match.group(1)) except Exception as e: error = e warnings.warn("Unable to determine max shares: %s" % e) return return 0
def option_lookup(symbol, strike_price_proximity=3): logging.debug("OPTION LOOKUP FOR %s" % symbol) def filter_contracts(olist, stock_price, spp): if olist is None: return [] middle_index = 0 for i in range(len(olist)): if stock_price < olist[i]['StrikePrice']: middle_index += 1 break start = middle_index - spp end = middle_index + spp if start < 0: start = 0 if end > len(olist) - 1: end = len(olist) - 1 return olist[start:end] session = Session() resp = session.get(UrlHelper.route('optionlookup')) tree = html.fromstring(resp.text) option_token = None option_user_id = None token = None user_id = None param_script = tree.xpath( '//script[contains(text(),"quoteOptions")]/text()')[0] param_search = re.search( r'\#get\-quote\-options\'\)\,\s*\'(.+)\'\s*\,\s*(\d+)\s*\)\;', param_script) try: option_token = param_search.group(1) option_user_id = param_search.group(2) except Exception: raise Exception("Unable to get option lookup token") option_quote_qp = { 'IdentifierType': 'Symbol', 'Identifier': symbol, 'SymbologyType': 'DTNSymbol', 'OptionExchange': None, '_token': option_token, '_token_userid': option_user_id } url = UrlHelper.set_query(OPTIONS_QUOTE_URL, option_quote_qp) resp = requests.get(url) option_data = json.loads(resp.text) quote = option_data['Quote'] if quote is None: logging.debug(option_data) raise Exception("No option quote data for %s" % symbol) try: last_price = option_data['Quote']['Last'] except Exception as e: logging.debug(option_data) logging.debug(e) option_chains = [] for e in option_data['Expirations']: expiration = e['ExpirationDate'] filtered_calls = filter_contracts(e['Calls'], last_price, strike_price_proximity) filtered_puts = filter_contracts(e['Puts'], last_price, strike_price_proximity) calls = [OptionContract(o) for o in filtered_calls] puts = [OptionContract(o) for o in filtered_puts] option_chains.append(OptionChain(expiration, calls=calls, puts=puts)) option_chain_lookup = OptionChainLookup(symbol, *option_chains) return option_chain_lookup
def wrap_cancel(self): url = "%s%s" % (UrlHelper.route('opentrades'), self.link) print(url) session = Session() session.get(url)
def validate(self): if self.form_token is None: print("refreshing form token") self.refresh_form_token() if self.validated: warnings.warn( "Warning: trade has already been validated. Revalidating...") self.form_data.pop('btnReview', None) self.refresh_form_token() self.validated = False assert type(self._trade_type).__name__ == 'TradeType' assert type(self._order_type).__name__ == 'OrderType' assert type(self._duration).__name__ == 'Duration' assert type(self.quantity) == int try: assert self.security_type == 'stock' or self.security_type == 'option' except AssertionError: raise InvalidTradeException( "security type is not specified. Must be either 'stock' or 'option'" ) if self.security_type == 'stock': try: assert self.trade_type in ('BUY', 'SELL', 'SELL_SHORT', 'BUY_TO_COVER') except AssertionError: raise InvalidTradeException( "A stock's trade type must be one of the following: BUY,SELL,SELL_SHORT,BUY_TO_COVER. Got %s " % self.trade_type) if self.security_type == 'option': try: assert self.trade_type in ('BUY_TO_OPEN', 'SELL_TO_CLOSE') except AssertionError: raise InvalidTradeException( "An option's trade type must be one of the following: BUY_TO_OPEN,SELL_TO_CLOSE" ) try: max_shares = self._get_max_shares() assert self.quantity <= max_shares except AssertionError: raise TradeExceedsMaxSharesException( "Quantity %s exceeds max of %s" % (self.quantity, max_shares), max_shares) try: resp = self.go_to_preview() redirect_url = resp.history[0].headers['Location'] redirect_qp = UrlHelper.get_query_params(redirect_url) tree = html.fromstring(resp.text) self.refresh_form_token(tree) trade_info = self._get_trade_info(tree) submit_query_params = redirect_qp submit_form_data = { 'submitOrder': tree.xpath('//input[@name="submitOrder"]/@value')[0], 'formToken': self.form_token } submit_url = UrlHelper.set_query(self.submit_url, submit_query_params) prepared_trade = PreparedTrade(submit_url, submit_form_data, **trade_info) self.execute = prepared_trade.execute self.validated = True return prepared_trade except Exception as e: print("trade failed for %s %s %s" % (self.symbol, self.quantity, self.trade_type)) print(e) raise e return False
def go_to_preview(self): session = Session() uri = UrlHelper.set_query(self.base_url, self.query_params) self.form_data.update({'isShowMax': 0}) return session.post(uri, data=self.form_data)
def go_to_preview(self): session = Session() self.form_data.update({'btnReview': 'Preview+Order', 'isShowMax': 0}) uri = UrlHelper.set_query(UrlHelper.route( 'tradeoption'), self.query_params) return session.post(uri, data=self.form_data)