Skip to content

Commit f20133d

Browse files
committed
Merge pull request #183 from gliptak/googleoptions
Implement Google options functionality
2 parents f883c65 + a8ce767 commit f20133d

File tree

5 files changed

+276
-1
lines changed

5 files changed

+276
-1
lines changed

docs/source/remote_data.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,27 @@ Google Finance
145145
f = web.DataReader("F", 'google', start, end)
146146
f.ix['2010-01-04']
147147
148+
.. _remote_data.google_options:
149+
150+
Google Finance Options
151+
----------------------
152+
***Experimental***
153+
154+
The Options class allows the download of options data from Google Finance.
155+
156+
The ``get_options_data`` method downloads options data for specified expiry date
157+
and provides a formatted ``DataFrame`` with a hierarchical index, so its easy to get
158+
to the specific option you want.
159+
160+
Available expiry dates can be accessed from the ``expiry_dates`` property.
161+
162+
.. ipython:: python
163+
164+
from pandas_datareader.data import Options
165+
goog = Options('goog', 'google')
166+
data = goog.get_options_data(expiry=goog.expiry_dates[0])
167+
data.iloc[0:5, 0:5]
168+
148169
.. _remote_data.fred:
149170

150171
FRED

docs/source/whatsnew/v0.2.3.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ New features
1919
~~~~~~~~~~~~
2020

2121
- ``DataReader`` now supports pulling data for the TSP.
22+
- ``DataReader`` now supports Google options data source, see :ref:`here<remote_data.google_options>` (:issue:`148`).
2223

2324
.. _whatsnew_023.api_breaking:
2425

pandas_datareader/data.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from pandas_datareader.yahoo.actions import YahooActionReader
1313
from pandas_datareader.yahoo.components import _get_data as get_components_yahoo # noqa
1414
from pandas_datareader.yahoo.options import Options as YahooOptions
15+
from pandas_datareader.google.options import Options as GoogleOptions
1516

1617
from pandas_datareader.eurostat import EurostatReader
1718
from pandas_datareader.fred import FredReader
@@ -157,5 +158,7 @@ def Options(symbol, data_source=None, session=None):
157158
data_source = "yahoo"
158159
if data_source == "yahoo":
159160
return YahooOptions(symbol, session=session)
161+
elif data_source == "google":
162+
return GoogleOptions(symbol, session=session)
160163
else:
161-
raise NotImplementedError("currently only yahoo supported")
164+
raise NotImplementedError("currently only yahoo and google supported")

pandas_datareader/google/options.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import datetime as dt
2+
import re
3+
import json
4+
5+
from pandas import DataFrame, MultiIndex
6+
7+
from pandas_datareader.base import _BaseReader
8+
9+
10+
class Options(_BaseReader):
11+
"""
12+
***Experimental***
13+
This class fetches call/put data for a given stock/expiry month.
14+
15+
It is instantiated with a string representing the ticker symbol.
16+
17+
The class has the following methods:
18+
get_options_data(month, year, expiry)
19+
get_call_data(month, year, expiry)
20+
get_put_data(month, year, expiry)
21+
get_near_stock_price(opt_frame, above_below)
22+
get_all_data(call, put)
23+
get_forward_data(months, call, put) (deprecated)
24+
25+
Examples
26+
--------
27+
# Instantiate object with ticker
28+
>>> aapl = Options('aapl')
29+
30+
# Fetch next expiry call data
31+
>>> calls = aapl.get_call_data()
32+
33+
# Can now access aapl.calls instance variable
34+
>>> aapl.calls
35+
36+
# Fetch next expiry put data
37+
>>> puts = aapl.get_put_data()
38+
39+
# Can now access aapl.puts instance variable
40+
>>> aapl.puts
41+
42+
# cut down the call data to be 3 below and 3 above the stock price.
43+
>>> cut_calls = aapl.get_near_stock_price(call=True, above_below=3)
44+
45+
# Fetch call and put data with expiry from now to 8 months out
46+
>>> forward_data = aapl.get_forward_data(8, call=True, put=True)
47+
48+
# Fetch all call and put data
49+
>>> all_data = aapl.get_all_data()
50+
"""
51+
52+
_OPTIONS_BASE_URL = "http://www.google.com/finance/option_chain?q={sym}" \
53+
"&expd={day}&expm={month}&expy={year}&output=json"
54+
55+
def __init__(self, symbol, session=None):
56+
""" Instantiates options_data with a ticker saved as symbol """
57+
self.symbol = symbol.upper()
58+
super(Options, self).__init__(symbols=symbol, session=session)
59+
60+
def get_options_data(self, month=None, year=None, expiry=None):
61+
"""
62+
***Experimental***
63+
Gets call/put data for the stock with the expiration data in the
64+
given month and year
65+
66+
Parameters
67+
----------
68+
month : number, int, optional(default=None)
69+
The month the options expire. This should be either 1 or 2
70+
digits.
71+
72+
year : number, int, optional(default=None)
73+
The year the options expire. This should be a 4 digit int.
74+
75+
expiry : date-like or convertible or list-like object, optional (default=None)
76+
The date (or dates) when options expire (defaults to current month)
77+
78+
Returns
79+
-------
80+
pandas.DataFrame
81+
A DataFrame with requested options data.
82+
83+
Index:
84+
Strike: Option strike, int
85+
Expiry: Option expiry, Timestamp
86+
Type: Call or Put, string
87+
Symbol: Option symbol as reported on Google, string
88+
Columns:
89+
Last: Last option price, float
90+
Chg: Change from prior day, float
91+
Bid: Bid price, float
92+
Ask: Ask price, float
93+
Vol: Volume traded, int64
94+
Open_Int: Open interest, int64
95+
Underlying: Ticker of the underlying security, string
96+
Underlying_Price: Price of the underlying security, float64
97+
Quote_Time: Time of the quote, Timestamp
98+
99+
Notes
100+
-----
101+
Note: Format of returned data frame is dependent on Google and may change.
102+
103+
>>> goog = Options('goog', 'google') # Create object
104+
>>> goog.get_options_data(expiry=goog.expiry_dates[0]) # Get data
105+
106+
"""
107+
if month is not None or year is not None:
108+
raise NotImplementedError('month and year parameters cannot be used')
109+
if expiry is None:
110+
raise ValueError('expiry has to be set')
111+
d = self._load_data(expiry)
112+
return self._process_data(d, expiry)
113+
114+
@property
115+
def expiry_dates(self):
116+
"""
117+
Returns a list of available expiry dates
118+
"""
119+
try:
120+
expiry_dates = self._expiry_dates
121+
except AttributeError:
122+
# has to be a non-valid date, to trigger returning 'expirations'
123+
d = self._load_data(dt.datetime(2016, 1, 3))
124+
expiry_dates = [dt.date(x['y'], x['m'], x['d']) for x in d['expirations']]
125+
self._expiry_dates = expiry_dates
126+
return expiry_dates
127+
128+
def _load_data(self, expiry):
129+
url = self._OPTIONS_BASE_URL.format(sym=self.symbol, day=expiry.day,
130+
month=expiry.month, year=expiry.year)
131+
s = re.sub(r'(\w+):', '"\\1":', self._read_url_as_StringIO(url).read())
132+
return json.loads(s)
133+
134+
def _process_data(self, jd, expiry):
135+
"""
136+
Parse JSON data into the DataFrame
137+
138+
"""
139+
now = dt.datetime.now()
140+
141+
columns = ['Last', 'Bid', 'Ask', 'Chg', 'PctChg', 'Vol', 'Open_Int', 'Root', 'Underlying_Price', 'Quote_Time']
142+
indexes = ['Strike', 'Expiry', 'Type', 'Symbol']
143+
rows_list, index = self._process_rows(jd, now, expiry)
144+
df = DataFrame(rows_list, columns=columns, index=MultiIndex.from_tuples(index, names=indexes))
145+
return df.sort_index()
146+
147+
def _process_rows(self, jd, now, expiry):
148+
rows_list = []
149+
index = []
150+
for key, typ in [['calls', 'call'], ['puts', 'put']]:
151+
for row in jd[key]:
152+
d = {}
153+
for dkey, rkey, ntype in [('Last', 'p', float), ('Bid', 'b', float),
154+
('Ask', 'a', float), ('Chg', 'c', float),
155+
('PctChg', 'cp', float), ('Vol', 'vol', int),
156+
('Open_Int', 'oi', int)]:
157+
try:
158+
d[dkey] = ntype(row[rkey].replace(',', ''))
159+
except (KeyError, ValueError):
160+
pass
161+
d['Root'] = self.symbol
162+
d['Underlying_Price'] = jd['underlying_price']
163+
d['Quote_Time'] = now
164+
rows_list.append(d)
165+
index.append((float(row['strike'].replace(',', '')), expiry, typ, row['s']))
166+
return rows_list, index
167+
168+
def get_call_data(self, month=None, year=None, expiry=None):
169+
"""
170+
***Experimental***
171+
Gets call/put data for the stock with the expiration data in the
172+
given month and year
173+
"""
174+
raise NotImplementedError()
175+
176+
def get_put_data(self, month=None, year=None, expiry=None):
177+
"""
178+
***Experimental***
179+
Gets put data for the stock with the expiration data in the
180+
given month and year
181+
"""
182+
raise NotImplementedError()
183+
184+
def get_near_stock_price(self, above_below=2, call=True, put=False,
185+
month=None, year=None, expiry=None):
186+
"""
187+
***Experimental***
188+
Returns a data frame of options that are near the current stock price.
189+
"""
190+
raise NotImplementedError()
191+
192+
def get_forward_data(self, months, call=True, put=False, near=False,
193+
above_below=2): # pragma: no cover
194+
"""
195+
***Experimental***
196+
Gets either call, put, or both data for months starting in the current
197+
month and going out in the future a specified amount of time.
198+
"""
199+
raise NotImplementedError()
200+
201+
def get_all_data(self, call=True, put=True):
202+
"""
203+
***Experimental***
204+
Gets either call, put, or both data for all available months starting
205+
in the current month.
206+
"""
207+
raise NotImplementedError()
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import nose
2+
import pandas as pd
3+
import pandas.util.testing as tm
4+
5+
import pandas_datareader.data as web
6+
from pandas_datareader._utils import RemoteDataError
7+
8+
9+
class TestGoogleOptions(tm.TestCase):
10+
@classmethod
11+
def setUpClass(cls):
12+
super(TestGoogleOptions, cls).setUpClass()
13+
14+
# goog has monthlies
15+
cls.goog = web.Options('GOOG', 'google')
16+
17+
def test_get_options_data(self):
18+
try:
19+
options = self.goog.get_options_data(expiry=self.goog.expiry_dates[0])
20+
except RemoteDataError as e: # pragma: no cover
21+
raise nose.SkipTest(e)
22+
self.assertTrue(len(options) > 10)
23+
tm.assert_index_equal(options.columns,
24+
pd.Index(['Last', 'Bid', 'Ask', 'Chg', 'PctChg', 'Vol',
25+
'Open_Int', 'Root', 'Underlying_Price', 'Quote_Time']))
26+
tm.assert_equal(options.index.names, [u'Strike', u'Expiry', u'Type', u'Symbol'])
27+
28+
def test_expiry_dates(self):
29+
try:
30+
dates = self.goog.expiry_dates
31+
except RemoteDataError as e: # pragma: no cover
32+
raise nose.SkipTest(e)
33+
self.assertTrue(len(dates) > 10)
34+
35+
def test_get_all_data(self):
36+
self.assertRaises(NotImplementedError, self.goog.get_all_data)
37+
38+
def test_get_options_data_with_year(self):
39+
self.assertRaises(NotImplementedError, self.goog.get_options_data, year=2016)
40+
41+
if __name__ == '__main__':
42+
nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
43+
exit=False) # pragma: no cover

0 commit comments

Comments
 (0)