def test_parse_realtime_data_single_quote(self): raw_data = {"Symbol": "ABC", "LastTradePriceOnly": 123.45} test_date = dates.get_database_timestamp().date() quotes = stocks._parse_realtime_data(raw_data, TEST_DB_NAME) self._check_quotes(quotes, "ABC", test_date, 123.45) self._check_database_quotes("ABC", 1, test_date, 123.45)
def test_fetch_realtime_multiple_quotes(self): test_date = dates.get_database_timestamp().date() quotes = stocks.fetch_realtime(["SPX", "VIX", "AAPL"], TEST_DB_NAME) self.assertGreater(quotes["SPX"].close, 0) self.assertGreater(quotes["VIX"].close, 0) self.assertGreater(quotes["AAPL"].close, 0) self._check_database_quotes("SPX", 1, test_date, None) self._check_database_quotes("VIX", 1, test_date, None) self._check_database_quotes("AAPL", 1, test_date, None)
def get_db_date(): """Get date from request parameters""" date = request.args.get("date", None, type=str) current_db_date = dates.get_database_timestamp().date() if date is not None: try: date = datetime.strptime(date, "%Y-%m-%d").date() date = min(date, current_db_date) except ValueError: date = current_db_date return date
def test_parse_realtime_data(self): test_data = """<div class="component"> <div class="left"> <a name="VX"></a> <h3>VX - CBOE S&P 500 Volatility Index (VIX) Futures</h3> </div> <div class="table-wrapper"> <table class="table oddeven padvertical padhorizontal inner-cellborders"> <tbody> <tr> <td><a href="ssfquote.aspx?ticker=VIX/F6" title="VIX/F6">VIX/F6</a></td> <td style="text-align:right"><span>01/20/2016</span> <td style="text-align:right"><span>24.15</span></td> <td style="text-align:right"><span data-numericcolor="-0.575">-0.575</span></td> <td style="text-align:right"><span>25.30</span></td> <td style="text-align:right"><span>24.05</span></td> <td style="text-align:right"><span>24.72</span></td> <td style="text-align:right"><span>3344</span></td> <td style="text-align:right"><span>72413</span></td> </tr> <tr> <td><a href="ssfquote.aspx?ticker=04VX/F6" title="04VX/F6">04VX/F6</a></td> <td style="text-align:right"><span>01/27/2016</span> <td style="text-align:right"><span>23.875</span></td> <td style="text-align:right"><span data-numericcolor="0.0">0.0</span></td> <td style="text-align:right"><span>0.0</span></td> <td style="text-align:right"><span>0.0</span></td> <td style="text-align:right"><span>23.88</span></td> <td style="text-align:right"><span>0</span></td> <td style="text-align:right"><span>78</span></td> </tr> <tr> <td><a href="ssfquote.aspx?ticker=VIX/G6" title="VIX/G6">VIX/G6</a></td> <td style="text-align:right"><span>02/17/2016</span> <td style="text-align:right"><span>22.56</span></td> <td style="text-align:right"><span data-numericcolor="-0.565">-0.565</span></td> <td style="text-align:right"><span>23.47</span></td> <td style="text-align:right"><span>22.56</span></td> <td style="text-align:right"><span>23.13</span></td> <td style="text-align:right"><span>2860</span></td> <td style="text-align:right"><span>112614</span></td> </tr> </tbody> </table> </div> </div> """ test_date = dates.get_database_timestamp().date() quotes = futures._parse_realtime_data('VX', test_data, TEST_DB_NAME) self._check_quotes(quotes, 'VX', test_date) self._check_database_quotes('VX', 2, test_date, 2)
def _parse_realtime_data(symbol, data, db_name=None): """Parses realtime (delayed) futures quotes from CBOE website and saves them to database. Args: symbol (str): Symbol. data (str): Raw quotes for the symbol. db_name (str): Optional database name. Returns: list: List of FutureQuote objects. """ assert symbol == 'VX' logger = logging.getLogger(__name__) timestamp = dates.get_database_timestamp() date = timestamp.date() time = timestamp.time() base_symbol = _future_to_base_symbol(symbol) quotes = [] with database.connect_db(db_name) as db: try: soup = BeautifulSoup(data, 'html5lib') table = soup.find('a', {'name': symbol}).parent.parent.find('table') for row in table.find_all('tr'): cols = row.find_all('td') if len(cols) != 9: continue row_symbol = cols[0].text.strip().split('/')[0] if row_symbol != base_symbol: continue expiration = datetime.strptime( cols[1].text.strip(), '%m/%d/%Y').date() last = utils.to_float(cols[2].text.strip()) if last is None: continue quote = FutureQuote(symbol, expiration, date, time, last) save_quote(db, quote) quotes.append(quote) except AttributeError: logger.error('Cannot parse futures quotes from CBOE for %s ...', symbol) return quotes
def test_fetch_realtime_vx(self): test_date = dates.get_database_timestamp().date() quotes = futures.fetch_realtime('VX', TEST_DB_NAME) self._check_quotes(quotes, 'VX', test_date) self._check_database_quotes('VX', len(quotes), test_date, len(quotes))
def test_fetch_realtime_non_existing_quote(self): test_date = dates.get_database_timestamp().date() quotes = stocks.fetch_realtime([NON_EXISTING_SYMBOL], TEST_DB_NAME) self.assertIsNone(quotes[NON_EXISTING_SYMBOL].close) self._check_database_quotes(NON_EXISTING_SYMBOL, 0, test_date, None)
def test_fetch_realtime_single_quote(self): test_date = dates.get_database_timestamp().date() quotes = stocks.fetch_realtime(["SPX"], TEST_DB_NAME) self._check_quotes(quotes, "SPX", test_date, None) self._check_database_quotes("SPX", 1, test_date, None)
def test_fetch_realtime_non_existing(self): test_date = dates.get_database_timestamp().date() quotes = options.fetch_realtime(NON_EXISTING_SYMBOL, TEST_DB_NAME) self.assertEquals(len(quotes), 0) self._check_database_quotes(NON_EXISTING_SYMBOL, 0, test_date, 0, False, None)
def test_get_database_timestamp_at_open(self): now = datetime(2015, 12, 8, 9, 30) self.assertEqual( dates.get_database_timestamp(now), datetime(2015, 12, 8, 9, 30))
def test_get_database_timestamp_before_open(self): now = datetime(2015, 12, 8, 9, 29) self.assertEqual( dates.get_database_timestamp(now), datetime(2015, 12, 8, 9, 29))
def test_parse_data_not_eod(self): test_date = dates.get_database_timestamp().date() quotes = options._parse_data("VIX", self.TEST_DATA, False, TEST_DB_NAME) self._check_quotes(quotes, "VIX", test_date, False, 25.22) self._check_database_quotes("VIX", 10, test_date, 10, False, 25.22)
def test_query_historical_non_existing(self): test_date = dates.get_database_timestamp().date() quotes = options.query_historical(NON_EXISTING_SYMBOL, test_date, TEST_DB_NAME) self.assertEqual(len(quotes), 0)
def test_query_historical_existing(self): test_date = dates.get_database_timestamp().date() options.fetch_historical("VIX", TEST_DB_NAME) quotes = options.query_historical("VIX", test_date, TEST_DB_NAME) self._check_quotes(quotes, "VIX", test_date, True, None)
def test_fetch_historical_non_existing(self): test_date = dates.get_database_timestamp().date() lines = options.fetch_historical(NON_EXISTING_SYMBOL, TEST_DB_NAME) self.assertEquals(lines, 0) self._check_database_quotes(NON_EXISTING_SYMBOL, 0, test_date, 0, True, None)
def test_fetch_historical_existing(self): test_date = dates.get_database_timestamp().date() lines = options.fetch_historical("VIX", TEST_DB_NAME) self.assertGreater(lines, 0) self._check_database_quotes("VIX", lines, test_date, lines, True, None)
def _parse_data(symbol, data, is_eod, db_name=None, timestamp=None): """Parses realtime (delayed) options quotes from CBOE and saves to database. Args: symbol (str): Symbol. data (str): Raw quotes for the symbol. is_eod (bool): If True: mark received quotes as EOD (time=None), if False: store actual time. db_name (str): Optional database name. timestamp (datetime): Optional datetime for the data. Returns: list: List of OptionQuote objects. """ logger = logging.getLogger(__name__) if timestamp is None: timestamp = dates.get_database_timestamp() date = timestamp.date() time = None if is_eod else timestamp.time() quotes = [] stock_price = None expirations = dates.get_expirations(symbol) with database.connect_db(db_name) as db: for line in data.splitlines(): values = line.strip().split(',') if (len(values) == 4) and (stock_price is None): stock_price = utils.to_float(values[1]) continue if len(values) != 15: continue if values[0] == 'Calls' or values[0].find('-') >= 0: continue code_values = values[0].split(' ') if len(code_values) != 4: continue position = code_values[3].find(code_values[0]) if code_values[3][1:position] in SKIP_SYMBOLS: continue expiration_year = 2000 + int(code_values[0]) expiration_month = MONTHS[code_values[1]] expiration_day = int(code_values[3][position + 2:position + 4]) expiration = datetime(expiration_year, expiration_month, expiration_day).date() if expiration not in expirations: continue strike = utils.to_float(code_values[2]) for type_, bid, ask in [ (consts.CALL, values[3], values[4]), (consts.PUT, values[10], values[11]), ]: bid = utils.to_float(bid) ask = utils.to_float(ask) quote = OptionQuote( symbol, type_, expiration, strike, date, time, bid, ask, stock_price, None, None) iv_bid = math.calc_iv(quote, bid) * 100 iv_ask = math.calc_iv(quote, ask) * 100 quote = OptionQuote( symbol, type_, expiration, strike, date, time, bid, ask, stock_price, iv_bid, iv_ask) save_quote(db, quote) quotes.append(quote) logger.info('... quotes parsed: %d', len(quotes)) return quotes
def test_get_database_timestamp_before_close(self): now = datetime(2015, 12, 8, 15, 59) self.assertEqual( dates.get_database_timestamp(now), datetime(2015, 12, 8, 15, 59))
def test_get_database_timestamp_after_close(self): now = datetime(2015, 12, 8, 16, 1) self.assertEqual( dates.get_database_timestamp(now), datetime(2015, 12, 9, 16, 1))
def test_fetch_realtime_existing(self): test_date = dates.get_database_timestamp().date() quotes = options.fetch_realtime("VIX", TEST_DB_NAME) self._check_quotes(quotes, "VIX", test_date, False, None) self._check_database_quotes("VIX", len(quotes), test_date, len(quotes), False, None)