7
7
from pandas import concat , DatetimeIndex , Series
8
8
from pandas .tseries .offsets import MonthEnd
9
9
from pandas .util .testing import _network_error_classes
10
+ from pandas .io .parsers import TextParser
11
+ from pandas import DataFrame
10
12
11
13
from pandas_datareader ._utils import RemoteDataError
14
+ from pandas_datareader .base import _BaseReader
12
15
13
16
# Items needed for options class
14
17
CUR_MONTH = dt .datetime .now ().month
15
18
CUR_YEAR = dt .datetime .now ().year
16
19
CUR_DAY = dt .datetime .now ().day
17
20
18
-
19
21
def _two_char (s ):
20
22
return '{0:0>2}' .format (s )
21
23
24
+ def _unpack (row , kind = 'td' ):
25
+ return [val .text_content ().strip () for val in row .findall (kind )]
26
+
27
+ def _parse_options_data (table ):
28
+ header = table .findall ('thead/tr' )
29
+ header = _unpack (header [0 ], kind = 'th' )
30
+ rows = table .findall ('tbody/tr' )
31
+ data = [_unpack (r ) for r in rows ]
32
+ if len (data ) > 0 :
33
+ return TextParser (data , names = header ).get_chunk ()
34
+ else : #Empty table
35
+ return DataFrame (columns = header )
22
36
23
- class Options (object ):
37
+ class Options (_BaseReader ):
24
38
"""
25
39
***Experimental***
26
40
This class fetches call/put data for a given stock/expiry month.
@@ -62,13 +76,13 @@ class Options(object):
62
76
>>> all_data = aapl.get_all_data()
63
77
"""
64
78
65
- _TABLE_LOC = {'calls' : 1 , 'puts' : 2 }
66
79
_OPTIONS_BASE_URL = 'http://finance.yahoo.com/q/op?s={sym}'
67
80
_FINANCE_BASE_URL = 'http://finance.yahoo.com'
68
81
69
- def __init__ (self , symbol ):
82
+ def __init__ (self , symbol , session = None ):
70
83
""" Instantiates options_data with a ticker saved as symbol """
71
84
self .symbol = symbol .upper ()
85
+ super (Options , self ).__init__ (symbols = symbol , session = session )
72
86
73
87
def get_options_data (self , month = None , year = None , expiry = None ):
74
88
"""
@@ -156,20 +170,19 @@ def _yahoo_url_from_expiry(self, expiry):
156
170
return self ._FINANCE_BASE_URL + expiry_links [expiry ]
157
171
158
172
def _option_frames_from_url (self , url ):
159
- frames = read_html (url )
160
- nframes = len (frames )
161
- frames_req = max (self ._TABLE_LOC .values ())
162
- if nframes < frames_req :
163
- raise RemoteDataError ("%s options tables found (%s expected)" % (nframes , frames_req ))
173
+
174
+ root = self ._parse_url (url )
175
+ calls = root .xpath ('//*[@id="optionsCallsTable"]/div[2]/div/table' )[0 ]
176
+ puts = root .xpath ('//*[@id="optionsPutsTable"]/div[2]/div/table' )[0 ]
164
177
165
178
if not hasattr (self , 'underlying_price' ):
166
179
try :
167
180
self .underlying_price , self .quote_time = self ._underlying_price_and_time_from_url (url )
168
181
except IndexError :
169
182
self .underlying_price , self .quote_time = np .nan , np .nan
170
183
171
- calls = frames [ self . _TABLE_LOC [ ' calls' ]]
172
- puts = frames [ self . _TABLE_LOC [ ' puts' ]]
184
+ calls = _parse_options_data ( calls )
185
+ puts = _parse_options_data ( puts )
173
186
174
187
calls = self ._process_data (calls , 'call' )
175
188
puts = self ._process_data (puts , 'put' )
@@ -648,15 +661,10 @@ def _parse_url(self, url):
648
661
except ImportError : # pragma: no cover
649
662
raise ImportError ("Please install lxml if you want to use the "
650
663
"{0!r} class" .format (self .__class__ .__name__ ))
651
- try :
652
- doc = parse (url )
653
- except _network_error_classes : # pragma: no cover
654
- raise RemoteDataError ("Unable to parse URL "
655
- "{0!r}" .format (url ))
656
- else :
657
- root = doc .getroot ()
658
- if root is None : # pragma: no cover
659
- raise RemoteDataError ("Parsed URL {0!r} has no root"
664
+ doc = parse (self ._read_url_as_StringIO (url ))
665
+ root = doc .getroot ()
666
+ if root is None : # pragma: no cover
667
+ raise RemoteDataError ("Parsed URL {0!r} has no root"
660
668
"element" .format (url ))
661
669
return root
662
670
@@ -682,6 +690,8 @@ def _process_data(self, frame, type):
682
690
frame ['Underlying_Price' ] = np .nan
683
691
frame ["Quote_Time" ] = np .nan
684
692
frame .rename (columns = {'Open Int' : 'Open_Int' }, inplace = True )
693
+ frame ['IV' ] = frame ['IV' ].str .replace (',' ,'' ).str .strip ('%' ).astype (float )/ 100
694
+ frame ['PctChg' ] = frame ['PctChg' ].str .replace (',' ,'' ).str .strip ('%' ).astype (float )/ 100
685
695
frame ['Type' ] = type
686
696
frame .set_index (['Strike' , 'Expiry' , 'Type' , 'Symbol' ], inplace = True )
687
697
0 commit comments