コード例 #1
0
    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
コード例 #2
0
    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)
コード例 #3
0
    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
コード例 #4
0
    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)
コード例 #5
0
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
コード例 #6
0
    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
コード例 #7
0
    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
コード例 #8
0
    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)
コード例 #9
0
    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
コード例 #10
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
コード例 #11
0
 def wrap_cancel(self):
     url = "%s%s" % (UrlHelper.route('opentrades'), self.link)
     print(url)
     session = Session()
     session.get(url)
コード例 #12
0
    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
コード例 #13
0
 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)
コード例 #14
0
 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)