5
5
import requests
6
6
from pandas import DataFrame
7
7
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
31
58
32
59
self ._symbol_data_cache = []
33
60
@@ -55,12 +82,15 @@ def _url_params(self):
55
82
56
83
return p
57
84
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
64
94
65
95
def _dl_mult_symbols (self , symbols ):
66
96
failed = []
@@ -69,11 +99,9 @@ def _dl_mult_symbols(self, symbols):
69
99
70
100
params = self ._url_params ()
71
101
params .update ({"ticker" : symbol })
72
- _baseurl = "http://globalquote.morningstar.com/globalcomponent/" \
73
- "RealtimeHistoricalStockData.ashx"
74
102
75
103
try :
76
- resp = requests .get (_baseurl , params = params )
104
+ resp = requests .get (self . url , params = params )
77
105
except Exception :
78
106
if symbol not in failed :
79
107
if self .retry_count == 0 :
@@ -85,8 +113,12 @@ def _dl_mult_symbols(self, symbols):
85
113
failed .append (symbol )
86
114
else :
87
115
if resp .status_code == requests .codes .ok :
116
+ jsondata = resp .json ()
117
+ if jsondata is None :
118
+ failed .append (symbol )
119
+ continue
88
120
jsdata = self ._restruct_json (symbol = symbol ,
89
- jsondata = resp . json () )
121
+ jsondata = jsondata )
90
122
symbol_data .extend (jsdata )
91
123
else :
92
124
raise Exception ("Request Error!: %s : %s" % (
@@ -95,12 +127,16 @@ def _dl_mult_symbols(self, symbols):
95
127
time .sleep (self .pause )
96
128
97
129
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
98
132
self ._dl_mult_symbols (symbols = failed )
99
133
self .retry_count -= 1
100
134
else :
101
135
self .retry_count = 0
102
136
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 :
104
140
warn ("The following symbols were excluded do to http "
105
141
"request errors: \n %s" % failed , SymbolWarning )
106
142
@@ -117,19 +153,9 @@ def _convert_index2date(enddate, indexvals):
117
153
i += 1
118
154
yield d .strftime ("%Y-%m-%d" )
119
155
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
-
131
156
def _restruct_json (self , symbol , jsondata ):
132
-
157
+ if jsondata is None :
158
+ return
133
159
divdata = jsondata ["DividendData" ]
134
160
135
161
pricedata = jsondata ["PriceDataList" ][0 ]["Datapoints" ]
@@ -156,12 +182,11 @@ def _restruct_json(self, symbol, jsondata):
156
182
if delta .days == 0 :
157
183
events .append (x )
158
184
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 :
161
186
val = e ["Desc" ].replace (e ["Type" ], "" )
162
187
bardict .update ({"isDividend" : val })
163
188
elif (self .incl_splits is True and
164
- e ["Type" ].find ("Split" ) > - 1 ):
189
+ e ["Type" ].find ("Split" ) > - 1 ):
165
190
val = e ["Desc" ].replace (e ["Type" ], "" )
166
191
bardict .update ({"isSplit" : val })
167
192
else :
@@ -175,19 +200,24 @@ def _restruct_json(self, symbol, jsondata):
175
200
return barss
176
201
177
202
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" )
190
222
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