Skip to content

Commit

Permalink
Merge pull request #3822 from gliptak/googleoptions
Browse files Browse the repository at this point in the history
Change Finance Options signatures and deprecate year/month parameters
  • Loading branch information
jreback committed Jun 22, 2013
2 parents 4c3cf70 + 62e4168 commit 2b4fd7c
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 56 deletions.
3 changes: 3 additions & 0 deletions doc/source/release.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
102 changes: 46 additions & 56 deletions pandas/io/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(',')
Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand All @@ -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
-------
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
-------
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
-------
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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
-------
Expand All @@ -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:
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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
----------
Expand Down Expand Up @@ -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)

Expand Down
56 changes: 56 additions & 0 deletions pandas/io/tests/test_yahoo.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import unittest
import nose
from datetime import datetime
import warnings

import pandas as pd
import pandas.io.data as web
Expand Down Expand Up @@ -96,6 +97,61 @@ 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
# 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')
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)

if __name__ == '__main__':
nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
Expand Down

0 comments on commit 2b4fd7c

Please sign in to comment.