Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change Finance Options signatures and deprecate year/month parameters #3822

Merged
merged 4 commits into from
Jun 22, 2013
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't need to change this but fyi there's no need for the parens, they are implicit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done :)

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