55import requests
66from pandas import DataFrame
77
8- from pandas_datareader ._utils import (SymbolWarning , _sanitize_dates )
9-
10-
11- class MorningstarDailyReader (object ):
12-
13- def __init__ (self , start = None , end = None , * args , ** kwargs ):
14- if end is None :
15- end = datetime .today ().strftime ("%Y-%m-%d" )
16-
17- self .start , self .end = _sanitize_dates (start , end )
18-
19- self .retry_count = kwargs .get ("retry_count" , 3 )
20- self .pause = kwargs .get ("pause" , 0.001 )
21- self .timeout = kwargs .get ("timeout" , 30 )
22- self .session = kwargs .get ("session" , requests .session ())
23-
24- self .incl_splits = kwargs .get ("incl_splits" , False )
25- self .incl_dividends = kwargs .get ("incl_dividends" , False )
26- self .incl_vol = kwargs .get ("incl_volume" , True )
27- self .currency = kwargs .get ("currency" , "usd" )
28- self .interval = kwargs .get ("interval" , "d" )
29-
30- self .symbols = kwargs .get ("symbols" )
8+ from pandas_datareader ._utils import SymbolWarning
9+ from pandas_datareader .base import _BaseReader
10+
11+
12+ class MorningstarDailyReader (_BaseReader ):
13+ """
14+ Read daily data from Morningstar
15+
16+ Parameters
17+ ----------
18+ symbols : {str, List[str]}
19+ String symbol of like of symbols
20+ start : string, (defaults to '1/1/2010')
21+ Starting date, timestamp. Parses many different kind of date
22+ representations (e.g., 'JAN-01-2010', '1/1/10', 'Jan, 1, 1980')
23+ end : string, (defaults to today)
24+ Ending date, timestamp. Same format as starting date.
25+ retry_count : int, default 3
26+ Number of times to retry query request.
27+ pause : float, default 0.1
28+ Time, in seconds, of the pause between retries.
29+ session : Session, default None
30+ requests.sessions.Session instance to be used
31+ freq : {str, None}
32+ Frequency to use in select readers
33+ incl_splits : bool, optional
34+ Include splits in data
35+ incl_dividends : bool,, optional
36+ Include divdends in data
37+ incl_volume : bool, optional
38+ Include volume in data
39+ currency : str, optional
40+ Currency to use for data
41+ interval : str, optional
42+ Sampling interval to use for downloaded data
43+ """
44+
45+ def __init__ (self , symbols , start = None , end = None , retry_count = 3 ,
46+ pause = 0.1 , timeout = 30 , session = None , freq = None ,
47+ incl_splits = False , incl_dividends = False , incl_volume = True ,
48+ currency = 'usd' , interval = 'd' ):
49+ super (MorningstarDailyReader , self ).__init__ (symbols , start , end ,
50+ retry_count , pause ,
51+ timeout , session , freq )
52+
53+ self .incl_splits = incl_splits
54+ self .incl_dividends = incl_dividends
55+ self .incl_vol = incl_volume
56+ self .currency = currency
57+ self .interval = interval
3158
3259 self ._symbol_data_cache = []
3360
@@ -55,12 +82,15 @@ def _url_params(self):
5582
5683 return p
5784
58- def _check_dates (self , * dates ):
59- if dates [0 ] > dates [1 ]:
60- raise ValueError ("Invalid start & end date! Start date cannot "
61- "be later than end date." )
62- else :
63- return dates [0 ], dates [1 ]
85+ @property
86+ def url (self ):
87+ """API URL"""
88+ return "http://globalquote.morningstar.com/globalcomponent/" \
89+ "RealtimeHistoricalStockData.ashx"
90+
91+ def _get_crumb (self , * args ):
92+ """Not required """
93+ pass
6494
6595 def _dl_mult_symbols (self , symbols ):
6696 failed = []
@@ -69,11 +99,9 @@ def _dl_mult_symbols(self, symbols):
6999
70100 params = self ._url_params ()
71101 params .update ({"ticker" : symbol })
72- _baseurl = "http://globalquote.morningstar.com/globalcomponent/" \
73- "RealtimeHistoricalStockData.ashx"
74102
75103 try :
76- resp = requests .get (_baseurl , params = params )
104+ resp = requests .get (self . url , params = params )
77105 except Exception :
78106 if symbol not in failed :
79107 if self .retry_count == 0 :
@@ -85,8 +113,12 @@ def _dl_mult_symbols(self, symbols):
85113 failed .append (symbol )
86114 else :
87115 if resp .status_code == requests .codes .ok :
116+ jsondata = resp .json ()
117+ if jsondata is None :
118+ failed .append (symbol )
119+ continue
88120 jsdata = self ._restruct_json (symbol = symbol ,
89- jsondata = resp . json () )
121+ jsondata = jsondata )
90122 symbol_data .extend (jsdata )
91123 else :
92124 raise Exception ("Request Error!: %s : %s" % (
@@ -95,12 +127,16 @@ def _dl_mult_symbols(self, symbols):
95127 time .sleep (self .pause )
96128
97129 if len (failed ) > 0 and self .retry_count > 0 :
130+ # TODO: This appears to do nothing since
131+ # TODO: successful symbols are not added to
98132 self ._dl_mult_symbols (symbols = failed )
99133 self .retry_count -= 1
100134 else :
101135 self .retry_count = 0
102136
103- if self .retry_count == 0 and len (failed ) > 0 :
137+ if not symbol_data :
138+ raise ValueError ('All symbols were invalid' )
139+ elif self .retry_count == 0 and len (failed ) > 0 :
104140 warn ("The following symbols were excluded do to http "
105141 "request errors: \n %s" % failed , SymbolWarning )
106142
@@ -117,19 +153,9 @@ def _convert_index2date(enddate, indexvals):
117153 i += 1
118154 yield d .strftime ("%Y-%m-%d" )
119155
120- #
121- # def _adjust_close_price(price, event_type, event_value): #noqa
122- # if event_type is "split":
123- # e, s = event_value.split(":")
124- # adj=(price * int(s))/e
125- # elif event_type is "dividend":
126- # adj = price - float(event_value)
127- # else:
128- # raise ValueError("Invalid event_type")
129- # return adj
130-
131156 def _restruct_json (self , symbol , jsondata ):
132-
157+ if jsondata is None :
158+ return
133159 divdata = jsondata ["DividendData" ]
134160
135161 pricedata = jsondata ["PriceDataList" ][0 ]["Datapoints" ]
@@ -156,12 +182,11 @@ def _restruct_json(self, symbol, jsondata):
156182 if delta .days == 0 :
157183 events .append (x )
158184 for e in events :
159- if (self .incl_dividends is True and
160- e ["Type" ].find ("Div" ) > - 1 ):
185+ if self .incl_dividends and e ["Type" ].find ("Div" ) > - 1 :
161186 val = e ["Desc" ].replace (e ["Type" ], "" )
162187 bardict .update ({"isDividend" : val })
163188 elif (self .incl_splits is True and
164- e ["Type" ].find ("Split" ) > - 1 ):
189+ e ["Type" ].find ("Split" ) > - 1 ):
165190 val = e ["Desc" ].replace (e ["Type" ], "" )
166191 bardict .update ({"isSplit" : val })
167192 else :
@@ -175,19 +200,24 @@ def _restruct_json(self, symbol, jsondata):
175200 return barss
176201
177202 def read (self ):
178- if type (self .symbols ) is str :
179- df = self ._dl_mult_symbols (symbols = [self .symbols ])
180- if len (df .Close .keys ()) == 0 :
181- raise IndexError ("None of the provided symbols were valid" )
182- else :
183- return df
184- elif hasattr (self .symbols , "__iter__" ):
185- df = self ._dl_mult_symbols (symbols = self .symbols )
186- if len (df .Close .keys ()) == 0 :
187- raise IndexError ("None of the provided symbols were valid" )
188- else :
189- return df
203+ """Read data"""
204+ if isinstance (self .symbols , str ):
205+ symbols = [self .symbols ]
206+ else :
207+ symbols = self .symbols
208+
209+ is_str = False
210+ try :
211+ is_str = all (map (lambda v : isinstance (v , str ), symbols ))
212+ except Exception :
213+ pass
214+
215+ if not is_str :
216+ raise TypeError ("symbols must be iterable or string and not "
217+ "type %s" % type (self .symbols ))
218+
219+ df = self ._dl_mult_symbols (symbols = symbols )
220+ if len (df .index .levels [0 ]) == 0 :
221+ raise ValueError ("None of the provided symbols were valid" )
190222 else :
191- raise TypeError (
192- "symbols must be iterable or string and not type %s" %
193- type (self .symbols ))
223+ return df
0 commit comments