From 6b6c7842a1a96fa77087c331274db4be5088be7b Mon Sep 17 00:00:00 2001 From: lockefox Date: Tue, 1 Aug 2017 18:10:51 -0700 Subject: [PATCH] adding tests for dependent robinhood endpoints --- prosper/datareader/stocks/prices.py | 14 ++++- tests/schemas/stocks/rh_fundamentals.schema | 27 +++++---- tests/schemas/stocks/rh_instruments.schema | 13 +++-- tests/schemas/stocks/rh_quotes.schema | 16 +++--- tests/test_config.cfg | 3 + tests/test_stocks_prices.py | 62 +++++++++++++++++++++ 6 files changed, 109 insertions(+), 26 deletions(-) diff --git a/prosper/datareader/stocks/prices.py b/prosper/datareader/stocks/prices.py index c3ce16c..be88743 100644 --- a/prosper/datareader/stocks/prices.py +++ b/prosper/datareader/stocks/prices.py @@ -1,4 +1,6 @@ """datareader.news.py: tools for fetching stock news""" +from datetime import datetime +import dateutil.parser from os import path import requests @@ -160,6 +162,7 @@ def market_is_open( (bool): https://api.robinhood.com/markets/{market}/hours/{date}/['is_open'] """ + #TODO: cache me market_name = market_uri.split('/')[-1] logger.info('fetching market info for %s -- Robinhood', market_name) @@ -172,7 +175,16 @@ def market_is_open( hours_req.raise_for_status() hours_data = hours_req.json() - return hours_data['is_open'] + if not hours_data['is_open']: + return False + + close_datetime = dateutil.parser.parse(hours_data['extended_opens_at']) + now = datetime.utcnow() + + if close_datetime > now: + return False + else: + return True SUMMARY_KEYS = [ 'symbol', 'simple_name', 'pe_ratio', 'pct_change', 'current_price', 'updated_at'] diff --git a/tests/schemas/stocks/rh_fundamentals.schema b/tests/schemas/stocks/rh_fundamentals.schema index d32ad21..7f0225b 100644 --- a/tests/schemas/stocks/rh_fundamentals.schema +++ b/tests/schemas/stocks/rh_fundamentals.schema @@ -1,18 +1,23 @@ { "type": "object", "properties": { - "open": {"type":"number"}, - "high": {"type":"number"}, - "low": {"type":"number"}, - "volume": {"type":"number"}, - "average_volume": {"type":"number"}, - "high_52_weeks": {"type":"number"}, - "dividend_yield": {"type":"number"}, - "low_52_weeks": {"type":"number"}, - "market_cap": {"type":"number"}, - "pe_ratio": {"type":"number"}, + "open": {"type":"string"}, + "high": {"type":"string"}, + "low": {"type":"string"}, + "volume": {"type":"string"}, + "average_volume": {"type":"string"}, + "high_52_weeks": {"type":"string"}, + "dividend_yield": {"type":["string", "null"]}, + "low_52_weeks": {"type":"string"}, + "market_cap": {"type":"string"}, + "pe_ratio": {"type":["string", "null"]}, "description": {"type":"string"}, - "instrument": {"type":"string", "format":"uri"} + "instrument": {"type":"string", "format":"uri"}, + "ceo": {"type":"string"}, + "num_employees": {"type":"integer"}, + "year_founded": {"type":"integer"}, + "headquarters_city": {"type":"string"}, + "headquarters_state": {"type":"string"} }, "required":[ "open", "high", "low", "volume", "average_volume", "high_52_weeks", "dividend_yield", diff --git a/tests/schemas/stocks/rh_instruments.schema b/tests/schemas/stocks/rh_instruments.schema index ef00c21..ac0746c 100644 --- a/tests/schemas/stocks/rh_instruments.schema +++ b/tests/schemas/stocks/rh_instruments.schema @@ -1,28 +1,29 @@ { "type": "object", "properties": { - "min_tick_size", + "min_tick_size": {"type":"null"}, "type": {"type":"string", "enum":["stock"]}, - "margin_initial_ratio": {"type":"number"}, + "splits": {"type":"string", "format":"uri"}, + "margin_initial_ratio": {"type":"string"}, "url": {"type":"string", "format":"uri"}, "quote": {"type":"string", "format":"uri"}, "symbol": {"type":"string"}, - "bloomberg_unique": {"type":"string", "pattern":"EQ\d{16}"}, + "bloomberg_unique": {"type":"string", "pattern":"EQ\\d{16}"}, "list_date": {"type":"string", "format":"date-time"}, "name": {"type":"string"}, "fundamentals": {"type":"string", "format":"uri"}, "state": {"type":"string", "enum":["active"]}, "country": {"type":"string"}, - "day_trade_ratio": {"type":"number"}, + "day_trade_ratio": {"type":"string"}, "tradeable": {"type":"boolean"}, - "maintenance_ratio": {"type":"number"}, + "maintenance_ratio": {"type":"string"}, "id": {"type":"string"}, "market": {"type":"string", "format":"uri"}, "simple_name": {"type":"string"} }, "required": [ "min_tick_size", "type", "margin_initial_ratio", "url", "quote", "symbol", - "bloomberg_unique", "list_date", "name", "fundamentals", "state", "country", + "bloomberg_unique", "list_date", "name", "fundamentals", "country", "day_trade_ratio", "tradeable", "maintenance_ratio", "id", "market","simple_name"], "additionalProperties": false } \ No newline at end of file diff --git a/tests/schemas/stocks/rh_quotes.schema b/tests/schemas/stocks/rh_quotes.schema index 286306d..5e968b3 100644 --- a/tests/schemas/stocks/rh_quotes.schema +++ b/tests/schemas/stocks/rh_quotes.schema @@ -6,14 +6,14 @@ "items": { "type": "object", "properties": { - "ask_price": {"type":"number"}, + "ask_price": {"type":"string"}, "ask_size": {"type":"integer"}, - "bid_price": {"type":"number"}, + "bid_price": {"type":"string"}, "bid_size": {"type":"integer"}, - "last_trade_price": {"type":"number"}, - "last_extended_hours_trade_price": {"type":"number"}, - "previous_close": {"type":"number"}, - "adjusted_previous_close": {"type":"number"}, + "last_trade_price": {"type":"string"}, + "last_extended_hours_trade_price": {"type":"string"}, + "previous_close": {"type":"string"}, + "adjusted_previous_close": {"type":"string"}, "previous_close_date": {"type":"string", "format":"date-time"}, "symbol": {"type":"string", "pattern":"([A-Z])+"}, "trading_halted": {"type":"boolean"}, @@ -22,8 +22,8 @@ "updated_at": {"type":"string", "format":"date-time"}, "instrument": {"type":"string", "format":"uri"} }, - "required":[ - "ask_price", "ask_size", "bid_price", "bid_size", "last_trade_price" + "required": [ + "ask_price", "ask_size", "bid_price", "bid_size", "last_trade_price", "last_extended_hours_trade_price", "previous_close", "adjusted_previous_close", "previous_close_date", "symbol", "trading_halted", "has_traded", "last_trade_price_source", "updated_at", "instrument"], diff --git a/tests/test_config.cfg b/tests/test_config.cfg index fb0061b..e044f67 100644 --- a/tests/test_config.cfg +++ b/tests/test_config.cfg @@ -3,3 +3,6 @@ alt_ticker = BA bad_ticker = BUTTS ticker_list = MU,INTC,A,TWTR + markets_url = https://api.robinhood.com/markets/XNAS/ + instruments_url = https://api.robinhood.com/instruments/0a8a072c-e52c-4e41-a2ee-8adbd72217d3/ + instruments_ticker = MU \ No newline at end of file diff --git a/tests/test_stocks_prices.py b/tests/test_stocks_prices.py index e69de29..e2919ee 100644 --- a/tests/test_stocks_prices.py +++ b/tests/test_stocks_prices.py @@ -0,0 +1,62 @@ +"""test_stocks_news.py: validate behavior for datareader.stocks.prices""" +from datetime import datetime +from os import path + +import pytest +import requests +import helpers + +import prosper.datareader.stocks.prices as prices +import prosper.datareader.exceptions as exceptions + +class TestExpectedSchemas: + good_ticker = helpers.CONFIG.get('STOCKS', 'good_ticker') + markets_url = helpers.CONFIG.get('STOCKS', 'markets_url') + ticker_list = helpers.CONFIG.get('STOCKS', 'ticker_list').split(',') + instruments_url = helpers.CONFIG.get('STOCKS', 'instruments_url') + instruments_ticker = helpers.CONFIG.get('STOCKS', 'instruments_ticker') + today = datetime.utcnow().strftime('%Y-%m-%d') + + def test_validate_quotes_endpoint(self): + """make sure /quotes endpoint works as expected""" + for ticker in self.ticker_list: + quote = prices.fetch_price_quotes_rh(ticker) + + helpers.validate_schema(quote, 'stocks/rh_quotes.schema') + + def test_validate_fundamentals_endpoint(self): + """make sure /fundamentals endpoint works as expected""" + for ticker in self.ticker_list: + fundamental = prices.fetch_fundamentals_rh(ticker) + + helpers.validate_schema(fundamental, 'stocks/rh_fundamentals.schema') + + def test_validate_instruments_rh(self): + """make sure /instruments endpoint works as expected""" + instrument = prices.fetch_instruments_rh(self.instruments_url) + + helpers.validate_schema(instrument, 'stocks/rh_instruments.schema') + + assert instrument['symbol'] == self.instruments_ticker + + def test_validate_market_info(self): + """make sure /markets endpoint works as expected""" + market_req = requests.get(self.markets_url) + market_req.raise_for_status() + + market = market_req.json() + + helpers.validate_schema(market, 'stocks/rh_markets.schema') + + def test_validate_market_hours(self): + """make sure /markets/hours works as expected""" + url = '{markets_url}hours/{today}'.format( + markets_url=self.markets_url, + today=self.today) + + hours_req = requests.get(url) + hours_req.raise_for_status() + + hours = hours_req.json() + + helpers.validate_schema(hours, 'stocks/rh_markets_hours.schema')