From 19c58126ebef614c189a648a7442b6eb7b0395c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Lipt=C3=A1k?= Date: Thu, 20 Jun 2013 18:01:07 -0400 Subject: [PATCH 1/4] Deprecated month/year, use expiry instead --- doc/source/release.rst | 3 ++ pandas/io/data.py | 102 +++++++++++++++++++---------------------- 2 files changed, 49 insertions(+), 56 deletions(-) diff --git a/doc/source/release.rst b/doc/source/release.rst index 07489a140c018..df09d2f5a50ba 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -156,6 +156,9 @@ pandas 0.11.1 ``load`` will give deprecation warning. - the ``method`` and ``axis`` arguments of ``DataFrame.replace()`` are deprecated + - set FutureWarning to require data_source, and to replace year/month with + expiry date in pandas.io options. This is in preparation to add options + data from google (:issue:`3822`) - the ``method`` and ``axis`` arguments of ``DataFrame.replace()`` are deprecated - Implement ``__nonzero__`` for ``NDFrame`` objects (:issue:`3691`, :issue:`3696`) diff --git a/pandas/io/data.py b/pandas/io/data.py index a97c77c207a4c..2707fcad6a67b 100644 --- a/pandas/io/data.py +++ b/pandas/io/data.py @@ -10,6 +10,7 @@ import urllib import urllib2 import time +import warnings from zipfile import ZipFile from pandas.util.py3compat import StringIO, BytesIO, bytes_to_str @@ -111,12 +112,7 @@ def get_quote_yahoo(symbols): urlStr = 'http://finance.yahoo.com/d/quotes.csv?s=%s&f=%s' % ( sym_list, request) - try: - lines = urllib2.urlopen(urlStr).readlines() - except Exception, e: - s = "Failed to download:\n{0}".format(e) - print (s) - return None + lines = urllib2.urlopen(urlStr).readlines() for line in lines: fields = line.decode('utf-8').strip().split(',') @@ -539,7 +535,7 @@ def _parse_options_data(table): class Options(object): """ - This class fetches call/put data for a given stock/exipry month. + This class fetches call/put data for a given stock/expiry month. It is instantiated with a string representing the ticker symbol. @@ -553,7 +549,7 @@ class Options(object): Examples -------- # Instantiate object with ticker - >>> aapl = Options('aapl') + >>> aapl = Options('aapl', 'yahoo') # Fetch September 2012 call data >>> calls = aapl.get_call_data(9, 2012) @@ -576,24 +572,25 @@ class Options(object): """ - def __init__(self, symbol): + def __init__(self, symbol, data_source=None): """ Instantiates options_data with a ticker saved as symbol """ self.symbol = str(symbol).upper() + if (data_source is None): + warnings.warn("Options(symbol) is deprecated, use Options(symbol, data_source) instead", + FutureWarning) + data_source = "yahoo" + if (data_source != "yahoo"): + raise NotImplementedError("currently only yahoo supported") - def get_options_data(self, month=None, year=None): + def get_options_data(self, month=None, year=None, expiry=None): """ Gets call/put data for the stock with the expiration data in the given month and year Parameters ---------- - month: number, int, optional(default=None) - The month the options expire. This should be either 1 or 2 - digits. - - year: number, int, optional(default=None) - The year the options expire. This sould be a 4 digit int. - + expiry: datetime.date, optional(default=None) + The date when options expire (defaults to current month) Returns ------- @@ -609,7 +606,7 @@ def get_options_data(self, month=None, year=None): When called, this function will add instance variables named calls and puts. See the following example: - >>> aapl = Options('aapl') # Create object + >>> aapl = Options('aapl', 'yahoo') # Create object >>> aapl.calls # will give an AttributeError >>> aapl.get_options_data() # Get data and set ivars >>> aapl.calls # Doesn't throw AttributeError @@ -621,6 +618,8 @@ def get_options_data(self, month=None, year=None): representations of the month and year for the expiry of the options. """ + year, month = self._try_parse_dates(year,month,expiry) + from lxml.html import parse if month and year: # try to get specified month from yahoo finance @@ -659,19 +658,15 @@ def get_options_data(self, month=None, year=None): return [call_data, put_data] - def get_call_data(self, month=None, year=None): + def get_call_data(self, month=None, year=None, expiry=None): """ Gets call/put data for the stock with the expiration data in the given month and year Parameters ---------- - month: number, int, optional(default=None) - The month the options expire. This should be either 1 or 2 - digits. - - year: number, int, optional(default=None) - The year the options expire. This sould be a 4 digit int. + expiry: datetime.date, optional(default=None) + The date when options expire (defaults to current month) Returns ------- @@ -683,7 +678,7 @@ def get_call_data(self, month=None, year=None): When called, this function will add instance variables named calls and puts. See the following example: - >>> aapl = Options('aapl') # Create object + >>> aapl = Options('aapl', 'yahoo') # Create object >>> aapl.calls # will give an AttributeError >>> aapl.get_call_data() # Get data and set ivars >>> aapl.calls # Doesn't throw AttributeError @@ -694,6 +689,8 @@ def get_call_data(self, month=None, year=None): repsectively, two digit representations of the month and year for the expiry of the options. """ + year, month = self._try_parse_dates(year,month,expiry) + from lxml.html import parse if month and year: # try to get specified month from yahoo finance @@ -727,19 +724,15 @@ def get_call_data(self, month=None, year=None): return call_data - def get_put_data(self, month=None, year=None): + def get_put_data(self, month=None, year=None, expiry=None): """ Gets put data for the stock with the expiration data in the given month and year Parameters ---------- - month: number, int, optional(default=None) - The month the options expire. This should be either 1 or 2 - digits. - - year: number, int, optional(default=None) - The year the options expire. This sould be a 4 digit int. + expiry: datetime.date, optional(default=None) + The date when options expire (defaults to current month) Returns ------- @@ -764,6 +757,8 @@ def get_put_data(self, month=None, year=None): repsectively, two digit representations of the month and year for the expiry of the options. """ + year, month = self._try_parse_dates(year,month,expiry) + from lxml.html import parse if month and year: # try to get specified month from yahoo finance @@ -798,7 +793,7 @@ def get_put_data(self, month=None, year=None): return put_data def get_near_stock_price(self, above_below=2, call=True, put=False, - month=None, year=None): + month=None, year=None, expiry=None): """ Cuts the data frame opt_df that is passed in to only take options that are near the current stock price. @@ -810,19 +805,15 @@ def get_near_stock_price(self, above_below=2, call=True, put=False, should be taken call: bool - Tells the function weather or not it should be using + Tells the function whether or not it should be using self.calls put: bool Tells the function weather or not it should be using self.puts - month: number, int, optional(default=None) - The month the options expire. This should be either 1 or 2 - digits. - - year: number, int, optional(default=None) - The year the options expire. This sould be a 4 digit int. + expiry: datetime.date, optional(default=None) + The date when options expire (defaults to current month) Returns ------- @@ -831,6 +822,8 @@ def get_near_stock_price(self, above_below=2, call=True, put=False, desired. If there isn't data as far out as the user has asked for then """ + year, month = self._try_parse_dates(year,month,expiry) + price = float(get_quote_yahoo([self.symbol])['last']) if call: @@ -844,13 +837,6 @@ def get_near_stock_price(self, above_below=2, call=True, put=False, except AttributeError: df_c = self.get_call_data(month, year) - # NOTE: For some reason the put commas in all values >1000. We remove - # them here - df_c.Strike = df_c.Strike.astype(str).apply(lambda x: \ - x.replace(',', '')) - # Now make sure Strike column has dtype float - df_c.Strike = df_c.Strike.astype(float) - start_index = np.where(df_c['Strike'] > price)[0][0] get_range = range(start_index - above_below, @@ -872,13 +858,6 @@ def get_near_stock_price(self, above_below=2, call=True, put=False, except AttributeError: df_p = self.get_put_data(month, year) - # NOTE: For some reason the put commas in all values >1000. We remove - # them here - df_p.Strike = df_p.Strike.astype(str).apply(lambda x: \ - x.replace(',', '')) - # Now make sure Strike column has dtype float - df_p.Strike = df_p.Strike.astype(float) - start_index = np.where(df_p.Strike > price)[0][0] get_range = range(start_index - above_below, @@ -897,11 +876,21 @@ def get_near_stock_price(self, above_below=2, call=True, put=False, else: return chop_put + def _try_parse_dates(self, year, month, expiry): + if (year is not None or month is not None): + warnings.warn("month, year arguments are deprecated, use expiry instead", + FutureWarning) + + if (expiry is not None): + year=expiry.year + month=expiry.month + return year, month + def get_forward_data(self, months, call=True, put=False, near=False, above_below=2): """ Gets either call, put, or both data for months starting in the current - month and going out in the future a spcified amount of time. + month and going out in the future a specified amount of time. Parameters ---------- @@ -933,6 +922,7 @@ def get_forward_data(self, months, call=True, put=False, near=False, If asked for, a DataFrame containing put data from the current month to the current month plus months. """ + warnings.warn("get_forward_data() is deprecated", FutureWarning) in_months = range(cur_month, cur_month + months + 1) in_years = [cur_year] * (months + 1) From f5977321d873ebbdf652dbc7337df3665d64da4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Lipt=C3=A1k?= Date: Thu, 20 Jun 2013 18:05:48 -0400 Subject: [PATCH 2/4] Added tests for Options --- pandas/io/tests/test_yahoo.py | 72 +++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/pandas/io/tests/test_yahoo.py b/pandas/io/tests/test_yahoo.py index 712475f76f5ed..738ed5b78727d 100644 --- a/pandas/io/tests/test_yahoo.py +++ b/pandas/io/tests/test_yahoo.py @@ -96,6 +96,78 @@ def test_get_data(self): t= np.array(pan) assert np.issubdtype(t.dtype, np.floating) + @network + def test_options(self): + try: + import lxml + except ImportError: + raise nose.SkipTest + try: + # aapl has monthlies + aapl = web.Options('aapl', 'yahoo') + today = datetime.today() + year = today.year + month = today.month+1 + if (month>12): + year = year +1 + month = 1 + expiry=datetime(year, month, 1) + (calls, puts) = aapl.get_options_data(expiry=expiry) + assert len(calls)>1 + assert len(puts)>1 + (calls, puts) = aapl.get_near_stock_price(call=True, put=True, expiry=expiry) + assert len(calls)==5 + assert len(puts)==5 + calls = aapl.get_call_data(expiry=expiry) + assert len(calls)>1 + puts = aapl.get_put_data(expiry=expiry) + assert len(puts)>1 + except IOError: + try: + urllib2.urlopen('http://www.google.com') + except IOError: + raise nose.SkipTest + else: + raise + + @network + def test_options_warnings(self): + try: + import lxml + except ImportError: + raise nose.SkipTest + try: + import warnings + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + # aapl has monthlies + aapl = web.Options('aapl') + today = datetime.today() + year = today.year + month = today.month+1 + if (month>12): + year = year +1 + month = 1 + (calls, puts) = aapl.get_options_data(month=month, year=year) + (calls, puts) = aapl.get_near_stock_price(call=True, put=True, month=month, year=year) + calls = aapl.get_call_data(month=month, year=year) + puts = aapl.get_put_data(month=month, year=year) + print(w) + assert len(w) == 5 + assert "deprecated" in str(w[0].message) + assert "deprecated" in str(w[1].message) + assert "deprecated" in str(w[2].message) + assert "deprecated" in str(w[3].message) + assert "deprecated" in str(w[4].message) + except IOError: + try: + urllib2.urlopen('http://www.google.com') + except IOError: + raise nose.SkipTest + else: + raise if __name__ == '__main__': nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'], From ae388a25ec6542c83e3d70b0929c7d4abaf27923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Lipt=C3=A1k?= Date: Fri, 21 Jun 2013 20:09:56 -0400 Subject: [PATCH 3/4] @network now catches connectivity problems --- pandas/io/tests/test_yahoo.py | 100 ++++++++++++++-------------------- 1 file changed, 42 insertions(+), 58 deletions(-) diff --git a/pandas/io/tests/test_yahoo.py b/pandas/io/tests/test_yahoo.py index 738ed5b78727d..1edb29efd00b9 100644 --- a/pandas/io/tests/test_yahoo.py +++ b/pandas/io/tests/test_yahoo.py @@ -1,6 +1,7 @@ import unittest import nose from datetime import datetime +import warnings import pandas as pd import pandas.io.data as web @@ -102,72 +103,55 @@ def test_options(self): import lxml except ImportError: raise nose.SkipTest + # aapl has monthlies + aapl = web.Options('aapl', 'yahoo') + today = datetime.today() + year = today.year + month = today.month+1 + if (month>12): + year = year +1 + month = 1 + expiry=datetime(year, month, 1) + (calls, puts) = aapl.get_options_data(expiry=expiry) + assert len(calls)>1 + assert len(puts)>1 + (calls, puts) = aapl.get_near_stock_price(call=True, put=True, expiry=expiry) + assert len(calls)==5 + assert len(puts)==5 + calls = aapl.get_call_data(expiry=expiry) + assert len(calls)>1 + puts = aapl.get_put_data(expiry=expiry) + assert len(puts)>1 + + @network + def test_options_warnings(self): try: + import lxml + except ImportError: + raise nose.SkipTest + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + # Cause all warnings to always be triggered. + warnings.simplefilter("always") # aapl has monthlies - aapl = web.Options('aapl', 'yahoo') + aapl = web.Options('aapl') today = datetime.today() year = today.year month = today.month+1 if (month>12): year = year +1 month = 1 - expiry=datetime(year, month, 1) - (calls, puts) = aapl.get_options_data(expiry=expiry) - assert len(calls)>1 - assert len(puts)>1 - (calls, puts) = aapl.get_near_stock_price(call=True, put=True, expiry=expiry) - assert len(calls)==5 - assert len(puts)==5 - calls = aapl.get_call_data(expiry=expiry) - assert len(calls)>1 - puts = aapl.get_put_data(expiry=expiry) - assert len(puts)>1 - except IOError: - try: - urllib2.urlopen('http://www.google.com') - except IOError: - raise nose.SkipTest - else: - raise - - @network - def test_options_warnings(self): - try: - import lxml - except ImportError: - raise nose.SkipTest - try: - import warnings - with warnings.catch_warnings(record=True) as w: - warnings.resetwarnings() - # Cause all warnings to always be triggered. - warnings.simplefilter("always") - # aapl has monthlies - aapl = web.Options('aapl') - today = datetime.today() - year = today.year - month = today.month+1 - if (month>12): - year = year +1 - month = 1 - (calls, puts) = aapl.get_options_data(month=month, year=year) - (calls, puts) = aapl.get_near_stock_price(call=True, put=True, month=month, year=year) - calls = aapl.get_call_data(month=month, year=year) - puts = aapl.get_put_data(month=month, year=year) - print(w) - assert len(w) == 5 - assert "deprecated" in str(w[0].message) - assert "deprecated" in str(w[1].message) - assert "deprecated" in str(w[2].message) - assert "deprecated" in str(w[3].message) - assert "deprecated" in str(w[4].message) - except IOError: - try: - urllib2.urlopen('http://www.google.com') - except IOError: - raise nose.SkipTest - else: - raise + (calls, puts) = aapl.get_options_data(month=month, year=year) + (calls, puts) = aapl.get_near_stock_price(call=True, put=True, month=month, year=year) + calls = aapl.get_call_data(month=month, year=year) + puts = aapl.get_put_data(month=month, year=year) + print(w) + assert len(w) == 5 + assert "deprecated" in str(w[0].message) + assert "deprecated" in str(w[1].message) + assert "deprecated" in str(w[2].message) + assert "deprecated" in str(w[3].message) + assert "deprecated" in str(w[4].message) if __name__ == '__main__': nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'], From 62e416899258336a23ee1da8469eaf72cb0df0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Lipt=C3=A1k?= Date: Fri, 21 Jun 2013 21:10:34 -0400 Subject: [PATCH 4/4] Remove superfluous parentheses --- pandas/io/data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/io/data.py b/pandas/io/data.py index 2707fcad6a67b..03ccde6a2fcc1 100644 --- a/pandas/io/data.py +++ b/pandas/io/data.py @@ -877,11 +877,11 @@ def get_near_stock_price(self, above_below=2, call=True, put=False, return chop_put def _try_parse_dates(self, year, month, expiry): - if (year is not None or month is not None): + if year is not None or month is not None: warnings.warn("month, year arguments are deprecated, use expiry instead", FutureWarning) - if (expiry is not None): + if expiry is not None: year=expiry.year month=expiry.month return year, month