Skip to content

Commit bbc9f7e

Browse files
committed
CLN: Clean morninginstar daily
Clean and make tests pass
1 parent 80d3f2f commit bbc9f7e

File tree

4 files changed

+134
-100
lines changed

4 files changed

+134
-100
lines changed

docs/source/whatsnew/v0.6.0.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ Highlights include:
2424
have been removed. PDR would like to restore these features, and pull
2525
requests are welcome.
2626

27+
- A new connector for Morningstart Open, High, Low, Close and Volume was
28+
introduced (:issue:`467`)
29+
2730
- A new connector for IEX daily price data was introduced (:issue:`465`).
2831

2932
- A new connector for IEX the majority of the IEX API was introduced
@@ -50,7 +53,8 @@ Enhancements
5053
- A new data connector for data provided by the Bank of Canada was
5154
introduced. (:issue:`440`)
5255

53-
- A new data connector for stock pricing data provided by Morningstar was introduced.
56+
- A new data connector for stock pricing data provided by Morningstar
57+
was introduced. (:issue:`467`)
5458

5559
.. _whatsnew_060.api_breaking:
5660

pandas_datareader/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
'get_iex_book', 'get_iex_symbols', 'get_last_iex',
1919
'get_markets_iex', 'get_recent_iex', 'get_records_iex',
2020
'get_summary_iex', 'get_tops_iex',
21-
'get_nasdaq_symbols', 'get_nasdaq_symbols', 'get_data_quandl', 'get_data_moex',
21+
'get_nasdaq_symbols', 'get_data_quandl', 'get_data_moex',
2222
'get_data_fred', 'get_dailysummary_iex', 'get_data_morningstar',
23-
'get_data_stooq','DataReader', 'Options']
23+
'get_data_stooq', 'DataReader', 'Options']

pandas_datareader/mstar/daily.py

Lines changed: 94 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,56 @@
55
import requests
66
from 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

pandas_datareader/tests/mstar/test_daily.py

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from datetime import datetime
22

3+
import numpy as np
34
import pandas as pd
45
import pytest
56
import requests
@@ -12,35 +13,32 @@
1213

1314
class TestMorningstarDaily(object):
1415

15-
1616
@skip_on_exception(RemoteDataError)
17-
def invalid_date(self):
17+
def test_invalid_date(self):
1818
with pytest.raises(ValueError):
1919
web.DataReader("MSFT", 'morningstar', start="1990-03-A")
2020
with pytest.raises(ValueError):
2121
web.DataReader("MSFT", 'morningstar', start="2001-02-02",
2222
end="1999-03-03")
2323

24-
@skip_on_exception(RemoteDataError)
25-
def invalid_partial_multi_symbols(self):
26-
df = web.DataReader(['MSFT', "21##", ""], "morningstar")
27-
assert (len(df.Close.keys()) == 1)
24+
def test_invalid_partial_multi_symbols(self):
25+
df = web.DataReader(['MSFT', "21##", ""], "morningstar", retry_count=0)
26+
assert (len(df.index.levels[0]) == 1)
2827

29-
@skip_on_exception(RemoteDataError)
30-
def invalid_multi_symbols(self):
31-
with pytest.raises(IndexError):
32-
web.DataReader(["#$@", "21122"], "morningstar")
28+
def test_invalid_multi_symbols(self):
29+
with pytest.raises(ValueError):
30+
web.DataReader(["#$@", "21122"], "morningstar", retry_count=0)
3331

34-
@skip_on_exception(RemoteDataError)
35-
def invalid_symbol_type(self):
32+
def test_invalid_symbol_type(self):
3633
with pytest.raises(TypeError):
37-
web.DataReader([12332], data_source='morningstar')
34+
web.DataReader([12332], data_source='morningstar', retry_count=0)
3835

3936
@skip_on_exception(RemoteDataError)
4037
def test_mstar(self):
4138
start = datetime(2014, 3, 5)
42-
assert (web.DataReader('MSFT', 'morningstar', start=start)['Close'][
43-
-1] == 89.8)
39+
end = datetime(2018, 1, 18)
40+
df = web.DataReader('MSFT', 'morningstar', start=start, end=end)
41+
assert (df['Close'][-1] == 89.8)
4442

4543
@skip_on_exception(RemoteDataError)
4644
def test_get_data_single_symbol(self):
@@ -51,17 +49,17 @@ def test_get_data_single_symbol(self):
5149
@skip_on_exception(RemoteDataError)
5250
def test_get_data_interval(self):
5351
# daily interval data
54-
pan = web.get_data_morningstar(symbol='XOM', start='2013-01-01',
52+
pan = web.get_data_morningstar(symbols='XOM', start='2013-01-01',
5553
end='2013-12-31', interval='d')
5654
assert len(pan) == 261
5755

5856
# weekly interval data
59-
pan = web.get_data_morningstar(symbol='XOM', start='2013-01-01',
57+
pan = web.get_data_morningstar(symbols='XOM', start='2013-01-01',
6058
end='2013-12-31', interval='w')
6159
assert len(pan) == 54
6260

6361
# monthly interval data
64-
pan = web.get_data_morningstar(symbol='XOM', start='2013-01-01',
62+
pan = web.get_data_morningstar(symbols='XOM', start='2013-01-01',
6563
end='2013-12-31', interval='m')
6664
assert len(pan) == 13
6765

@@ -80,30 +78,31 @@ def test_get_data_multiple_symbols_two_dates(self):
8078
df = web.get_data_morningstar(symbols=['XOM', 'MSFT'],
8179
start='2013-01-01',
8280
end='2013-03-04')
83-
result = df.keys()
8481

85-
assert len(result) == 6
82+
assert len(df.index.levels[0]) == 2
83+
assert 'XOM' in df.index.levels[0]
84+
assert 'MSFT' in df.index.levels[0]
8685

8786
# sanity checking
87+
assert df.dtypes['Close'] == np.float64
88+
assert df.dtypes['Open'] == np.float64
89+
assert df.dtypes['Low'] == np.float64
90+
assert df.dtypes['High'] == np.float64
91+
assert df.dtypes['Volume'] == np.int64
8892

89-
assert all([type(i) == float for i in df.Close.values[0:3]])
90-
91-
@skip_on_exception(RemoteDataError)
9293
def incl_dividend_column_multi(self):
9394
df = web.get_data_morningstar(symbols=['XOM', 'MSFT'],
9495
start='2013-01-01',
9596
end='2013-03-04', incl_dividends=True)
9697

97-
assert ("isDividend" in df.keys())
98+
assert ("isDividend" in df)
9899

99-
@skip_on_exception(RemoteDataError)
100100
def incl_splits_column_multi(self):
101101
df = web.get_data_morningstar(symbols=['XOM', 'MSFT'],
102102
start='2013-01-01',
103103
end='2013-03-04', incl_dividends=True)
104-
assert ("isSplit" in df.keys())
104+
assert ("isSplit" in df)
105105

106-
@skip_on_exception(RemoteDataError)
107106
def excl_volume_column_multi(self):
108107
df = web.get_data_morningstar(symbols=["XOM", "MSFT"],
109108
start='2013-01-01',
@@ -112,15 +111,16 @@ def excl_volume_column_multi(self):
112111

113112
@skip_on_exception(RemoteDataError)
114113
def test_mstar_reader_class(self):
115-
df = MorningstarDailyReader(symbols="GOOG", interval="d")
116-
df = df.read().Volume["2014-04-07"]
117-
assert df == 4401618
114+
dr = MorningstarDailyReader(symbols="GOOG", interval="d")
115+
df = dr.read()
116+
117+
assert df.Volume[('GOOG', '2017-12-13')] == 1279659
118118

119119
session = requests.Session()
120120

121-
r = MorningstarDailyReader('GOOG', session=session)
122-
df = r.read()
123-
assert (df.session is session)
121+
dr = MorningstarDailyReader('GOOG', session=session)
122+
dr.read()
123+
assert dr.session is session
124124

125125
@skip_on_exception(RemoteDataError)
126126
def test_mstar_DataReader_multi(self):

0 commit comments

Comments
 (0)