diff --git a/README.md b/README.md index da1b5e3..49162c3 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ If you are looking to use this library for your Quantopian algorithm, check out the [migration document](./migration.md). ## Data Sources -This library predominantly relies on the [IEX public data API](https://iextrading.com/developer/docs/) for daily -prices and fundamentals, but plans to connect to other data sources in -the future. Currently supported data sources include the following. +This library predominantly relies on the [Alpaca Data API](https://docs.alpaca.markets/api-documentation/api-v2/market-data/) for daily +price data. For users with funded Alpaca brokerage accounts, several [Polygon](https://polygon.io/) fundamental +data endpoints are supported. [IEX Cloud](https://iexcloud.io/docs/api/) data is also supported, though if too much +data is requested, it stops being free. (See the note in the IEX section below.) -- [Alpaca/Polygon](https://docs.alpaca.markets/) ## Install @@ -75,8 +75,47 @@ and returns a DataFrame with the data for the current date (US/Eastern time). Its constructor accepts `list_symbol` function that is supposed to return the full set of symbols as a string list, which is used as the maximum universe inside the engine. +## Alpaca Data API +The [Alpaca Data API](https://docs.alpaca.markets/api-documentation/api-v2/market-data/) is currently the least-limited source of pricing data +supported by pipeline-live. In order to use the Alpaca Data API, you'll need to +register for an Alpaca account [here](https://app.alpaca.markets/signup) and generate API key information with +the dashboard. Once you have your keys generated, you need to store them in +the following environment variables: + +``` +APCA_API_BASE_URL +APCA_API_KEY_ID +APCA_API_SECRET_KEY +``` + +### pipeline_live.data.iex.pricing.USEquityPricing +This class provides the basic price information retrieved from +[Alpaca Data API](https://docs.alpaca.markets/api-documentation/api-v2/market-data/bars/). + +## Polygon Data Source API +You will need to set an [Alpaca](https://alpaca.markets/) API key as `APCA_API_KEY_ID` to use this API. + +### pipeline_live.data.polygon.fundamentals.PolygonCompany +This class provides the DataSet interface using +[Polygon Symbol Details API](https://polygon.io/docs/#!/Meta-Data/get_v1_meta_symbols_symbol_company) + +### pipeline_live.data.polygon.filters.IsPrimaryShareEmulation +Experimental. This class filteres symbols by the following +rule to return something close to +[IsPrimaryShare()](https://www.quantopian.com/help#quantopian_pipeline_filters_fundamentals_IsPrimaryShare) in Quantopian. + +- must be a US company +- must have a valid financial data + ## IEX Data Source API -You don't have to configure anything to use these API +To use IEX-source data, you need to sign up for an IEX Cloud account and save +your IEX token as an environment variable called `IEX_TOKEN`. + +IMPORTANT NOTE: IEX data is now limited for free accounts. In order to +avoid using more messages than you are allotted each month, please +be sure that you are not using IEX-sourced factors too frequently +or on too many securities. For more information about how many messages +each method will cost, please refer to [this part](https://iexcloud.io/docs/api/#data-weighting) of the IEX Cloud documentation. ### pipeline_live.data.iex.pricing.USEquityPricing This class provides the basic price information retrieved from @@ -102,18 +141,3 @@ A shortcut for `IEXCompany.sector.latest` ### pipeline_live.data.iex.classifiers.Industry() A shortcut for `IEXCompany.industry.latest` - -## Alpaca/Polygon Data Source API -You will need to set [Alpaca](https://alpaca.markets/) API key to use these API. - -### pipeline_live.data.polygon.fundamentals.PolygonCompany -This class provides the DataSet interface using -[Polygon Symbol Details API](https://polygon.io/docs/#!/Meta-Data/get_v1_meta_symbols_symbol_company) - -### pipeline_live.data.polygon.filters.IsPrimaryShareEmulation -Experimental. This class filteres symbols by the following -rule to return something close to -[IsPrimaryShare()](https://www.quantopian.com/help#quantopian_pipeline_filters_fundamentals_IsPrimaryShare) in Quantopian. - -- must be a US company -- must have a valid financial data diff --git a/migration.md b/migration.md index 097cf29..aee04e4 100644 --- a/migration.md +++ b/migration.md @@ -12,20 +12,22 @@ pylivetrader can run the pipeline object from this package. ## USEquityPricing The most important class to think about first is the USEquityPricing class and it is well covered by -`pipeline_live.data.iex.pricing.USEquityPricing` class. +`pipeline_live.data.alpaca.pricing.USEquityPricing` class. This class gets the market-wide daily price data (OHLCV) up to the -previous day from [IEX chart API](https://iextrading.com/developer/docs/#chart). -Depending on the requested window length from its upstream pipeline, it -fetches different size of the data range (e.g. 3m, 1y). Again, the volume of -this data is market-wide size, so it's safe to use this with factors such -as AverageDollarVolume. +previous day from [Alpaca data API](https://docs.alpaca.markets/api-documentation/api-v2/market-data/bars/). ## Factors In order to use many of the builtin factors with this price data loader, -you need to use `pipeline_live.data.iex.factors` package which has -all the builtin factor classes ported from zipline. +you need to use `pipeline_live.data.alpaca.factors` package which has +all the builtin factor classes ported from zipline. Use of the Alpaca data API +requires an Alpaca account, which you can sign up for [here](https://app.alpaca.markets/signup). -For example, if you have these lines, +Once you have an Alpaca account, you will need to store your account info +from their dashboard as environment variables. You can find information about +how to do so on [this documentation page](https://docs.alpaca.markets/api-documentation/how-to/). + +To use the Alpaca factors, import them from `pipeline_live.data.alpaca.factors`. +For example, if you have these lines on Quantopian, ```py from quantopian.pipeline.factors import ( @@ -37,10 +39,10 @@ from quantopian.pipeline.data.builtin import USEquityPricing you can rewrite it to something like this. ```py -from pipeline_live.data.iex.factors import ( +from pipeline_live.data.alpaca.factors import ( AverageDollarVolume, SimpleMovingAverage, ) -from pipeline_live.data.iex.pricing import USEquityPricing +from pipeline_live.data.alpaca.pricing import USEquityPricing ``` Of course, the builtin factor classes in the original zipline are mostly @@ -49,7 +51,7 @@ ones, they also work with this `USEquityPricing`. ```py from zipline.pipeline.factors import AverageDollarVolume -from pipeline_live.data.iex.pricing import USEquityPricing +from pipeline_live.data.alpaca.pricing import USEquityPricing dollar_volume = AverageDollarVolume( inputs=[USEquityPricing.close, USEquityPricing.volume], @@ -57,19 +59,27 @@ dollar_volume = AverageDollarVolume( ) ``` -The only difference in the factor classes in `pipeline_live.data.iex.factors` -is that some of the classes have IEX's USEquityPricing as the default +The only difference in the factor classes in `pipeline_live.data.alpaca.factors` +is that some of the classes have Alpaca's USEquityPricing as the default inputs, so you don't need to explicitly specify it. ## Fundamentals The Quantopian platform allows you to retrieve various proprietary data sources through pipeline, including Morningstar fundamentals. While the intention of this pipline-live library is to add more such proprietary -data sources, the free alternative at the moment is IEX. There are two +data sources, the alternative at the moment is IEX. There are two main dataset classes are builtin in this library, `IEXCompany` and `IEXKeyStats`. Those both belong to the `pipeline_live.data.iex.fundamentals` package. +Please note that, in order to use the IEX API data, you will need to sign up +for an IEX Cloud account [here](https://iexcloud.io/cloud-login#/register/) and set your IEX Cloud token in the +`IEX_TOKEN` environment variable. IEX limits your API messages per month. In +order to avoid running over your message quota, please make sure that you +filter your stock universe as much as possible before using IEX API data. +If you wish to use IEX data to frequently filter a larger set of symbols, you +may need to upgrade your IEX Cloud account. + ### IEXCompany This dataset class maps the basic stock information from the [Company API](https://iextrading.com/developer/docs/#company). diff --git a/pipeline_live/data/alpaca/__init__.py b/pipeline_live/data/alpaca/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pipeline_live/data/alpaca/factors.py b/pipeline_live/data/alpaca/factors.py new file mode 100644 index 0000000..3fe1606 --- /dev/null +++ b/pipeline_live/data/alpaca/factors.py @@ -0,0 +1,35 @@ +''' +Duplicate builtin factor classes in zipline with IEX's USEquityPricing +''' + +from zipline.pipeline.data import USEquityPricing as z_pricing +from zipline.pipeline import factors as z_factors + +from .pricing import USEquityPricing as alpaca_pricing + + +def _replace_inputs(inputs): + map = { + z_pricing.open: alpaca_pricing.open, + z_pricing.high: alpaca_pricing.high, + z_pricing.low: alpaca_pricing.low, + z_pricing.close: alpaca_pricing.close, + z_pricing.volume: alpaca_pricing.volume, + } + + if type(inputs) not in (list, tuple, set): + return inputs + return tuple([ + map.get(inp, inp) for inp in inputs + ]) + + +for name in dir(z_factors): + factor = getattr(z_factors, name) + if factor != z_factors.Factor and hasattr( + factor, 'inputs') and issubclass( + factor, z_factors.Factor): + new_factor = type(factor.__name__, (factor,), { + 'inputs': _replace_inputs(factor.inputs) + }) + locals()[factor.__name__] = new_factor diff --git a/pipeline_live/data/alpaca/pricing.py b/pipeline_live/data/alpaca/pricing.py new file mode 100644 index 0000000..2e09dc0 --- /dev/null +++ b/pipeline_live/data/alpaca/pricing.py @@ -0,0 +1,23 @@ +from zipline.pipeline.data.dataset import Column, DataSet +from zipline.utils.numpy_utils import float64_dtype + +from .pricing_loader import USEquityPricingLoader + + +# In order to use it as a cache key, we have to make it singleton +_loader = USEquityPricingLoader() + + +class USEquityPricing(DataSet): + """ + Dataset representing daily trading prices and volumes. + """ + open = Column(float64_dtype) + high = Column(float64_dtype) + low = Column(float64_dtype) + close = Column(float64_dtype) + volume = Column(float64_dtype) + + @staticmethod + def get_loader(): + return _loader diff --git a/pipeline_live/data/alpaca/pricing_loader.py b/pipeline_live/data/alpaca/pricing_loader.py new file mode 100644 index 0000000..500659b --- /dev/null +++ b/pipeline_live/data/alpaca/pricing_loader.py @@ -0,0 +1,115 @@ +import numpy as np +import logbook +import pandas as pd + +from zipline.lib.adjusted_array import AdjustedArray +from zipline.pipeline.loaders.base import PipelineLoader +from zipline.utils.calendars import get_calendar +from zipline.errors import NoFurtherDataError + +from pipeline_live.data.sources import alpaca + + +log = logbook.Logger(__name__) + + +class USEquityPricingLoader(PipelineLoader): + """ + PipelineLoader for US Equity Pricing data + """ + + def __init__(self): + cal = get_calendar('NYSE') + + self._all_sessions = cal.all_sessions + + def load_adjusted_array(self, columns, dates, symbols, mask): + # load_adjusted_array is called with dates on which the user's algo + # will be shown data, which means we need to return the data that would + # be known at the start of each date. We assume that the latest data + # known on day N is the data from day (N - 1), so we shift all query + # dates back by a day. + start_date, end_date = _shift_dates( + self._all_sessions, dates[0], dates[-1], shift=1, + ) + + sessions = self._all_sessions + sessions = sessions[(sessions >= start_date) & (sessions <= end_date)] + + timedelta = pd.Timestamp.utcnow() - start_date + chart_range = timedelta.days + 1 + log.info('chart_range={}'.format(chart_range)) + prices = alpaca.get_stockprices(chart_range) + + dfs = [] + for symbol in symbols: + if symbol not in prices: + df = pd.DataFrame( + {c.name: c.missing_value for c in columns}, + index=sessions + ) + else: + df = prices[symbol] + df = df.reindex(sessions, method='ffill') + dfs.append(df) + + raw_arrays = {} + for c in columns: + colname = c.name + raw_arrays[colname] = np.stack([ + df[colname].values for df in dfs + ], axis=-1) + out = {} + for c in columns: + c_raw = raw_arrays[c.name] + out[c] = AdjustedArray( + c_raw.astype(c.dtype), + {}, + c.missing_value + ) + return out + + +def _shift_dates(dates, start_date, end_date, shift): + try: + start = dates.get_loc(start_date) + except KeyError: + if start_date < dates[0]: + raise NoFurtherDataError( + msg=( + "Pipeline Query requested data starting on {query_start}, " + "but first known date is {calendar_start}" + ).format( + query_start=str(start_date), + calendar_start=str(dates[0]), + ) + ) + else: + raise ValueError("Query start %s not in calendar" % start_date) + + # Make sure that shifting doesn't push us out of the calendar. + if start < shift: + raise NoFurtherDataError( + msg=( + "Pipeline Query requested data from {shift}" + " days before {query_start}, but first known date is only " + "{start} days earlier." + ).format(shift=shift, query_start=start_date, start=start), + ) + + try: + end = dates.get_loc(end_date) + except KeyError: + if end_date > dates[-1]: + raise NoFurtherDataError( + msg=( + "Pipeline Query requesting data up to {query_end}, " + "but last known date is {calendar_end}" + ).format( + query_end=end_date, + calendar_end=dates[-1], + ) + ) + else: + raise ValueError("Query end %s not in calendar" % end_date) + return dates[start - shift], dates[end - shift] diff --git a/pipeline_live/data/polygon/filters.py b/pipeline_live/data/polygon/filters.py index 84963b2..e8e7c70 100644 --- a/pipeline_live/data/polygon/filters.py +++ b/pipeline_live/data/polygon/filters.py @@ -22,6 +22,7 @@ def compute(self, today, symbols, out, *inputs): ], dtype=bool) out[:] = ary + class StaticSymbols(CustomFilter): inputs = () window_length = 1 diff --git a/pipeline_live/data/sources/alpaca.py b/pipeline_live/data/sources/alpaca.py new file mode 100644 index 0000000..33ee265 --- /dev/null +++ b/pipeline_live/data/sources/alpaca.py @@ -0,0 +1,41 @@ +import alpaca_trade_api as tradeapi + +from .util import ( + daily_cache, parallelize +) + + +def list_symbols(): + return [ + a.symbol for a in tradeapi.REST().list_assets() + if a.tradable and a.status == 'active' + ] + + +def get_stockprices(limit=365, timespan='day'): + all_symbols = list_symbols() + + @daily_cache(filename='alpaca_chart_{}'.format(limit)) + def get_stockprices_cached(all_symbols): + return _get_stockprices(all_symbols, limit, timespan) + + return get_stockprices_cached(all_symbols) + + +def _get_stockprices(symbols, limit=365, timespan='day'): + '''Get stock data (key stats and previous) from Alpaca. + Just deal with Alpaca's 200 stocks per request limit. + ''' + + def fetch(symbols): + barset = tradeapi.REST().get_barset(symbols, timespan, limit) + data = {} + for symbol in barset: + df = barset[symbol].df + # Update the index format for comparison with the trading calendar + df.index = df.index.tz_convert('UTC').normalize() + data[symbol] = df.asfreq('C') + + return data + + return parallelize(fetch, splitlen=199)(symbols) diff --git a/pipeline_live/data/sources/iex.py b/pipeline_live/data/sources/iex.py index 2862ce6..3ccfa2e 100644 --- a/pipeline_live/data/sources/iex.py +++ b/pipeline_live/data/sources/iex.py @@ -1,4 +1,5 @@ -import iexfinance +from iexfinance import refdata +from iexfinance.stocks import Stock import pandas as pd from .util import ( @@ -8,7 +9,7 @@ def list_symbols(): return [ - symbol['symbol'] for symbol in iexfinance.refdata.get_symbols() + symbol['symbol'] for symbol in refdata.get_iex_symbols() ] @@ -30,7 +31,7 @@ def __call__(self): def _get(all_symbols): def fetch(symbols): return _ensure_dict( - getattr(iexfinance.stocks.Stock(symbols), method_name)(), + getattr(Stock(symbols), method_name)(), symbols ) @@ -67,7 +68,7 @@ def _get_stockprices(symbols, chart_range='1y'): def fetch(symbols): charts = _ensure_dict( - iexfinance.stocks.Stock(symbols).get_chart(range=chart_range), + Stock(symbols).get_chart(range=chart_range), symbols ) result = {} diff --git a/setup.py b/setup.py index e0d55b9..f70e4a9 100755 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ keywords='financial,zipline,pipeline,stock,screening,api,trade', packages=find_packages(), install_requires=[ - 'alpaca-trade-api', + 'alpaca-trade-api>=0.29', 'iexfinance>=0.4.1', 'zipline==1.3.0', 'numpy==1.16.1', diff --git a/tests/conftest.py b/tests/conftest.py index cdb4999..01e1d9c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,7 @@ from pipeline_live.data.sources import polygon as sources_polygon from pipeline_live.data.sources import iex as sources_iex +from pipeline_live.data.sources import alpaca as sources_alpaca @pytest.fixture @@ -13,9 +14,21 @@ def tradeapi(): @pytest.fixture -def iexfinance(): - with patch.object(sources_iex, 'iexfinance') as iexfinance: - yield iexfinance +def alpaca_tradeapi(): + with patch.object(sources_alpaca, 'tradeapi') as tradeapi: + yield tradeapi + + +@pytest.fixture +def refdata(): + with patch.object(sources_iex, 'refdata') as refdata: + yield refdata + + +@pytest.fixture +def stocks(): + with patch.object(sources_iex, 'Stock') as stocks: + yield stocks @pytest.fixture diff --git a/tests/datamock/mock_iex.py b/tests/datamock/mock_iex.py index fa6757a..f6cd832 100644 --- a/tests/datamock/mock_iex.py +++ b/tests/datamock/mock_iex.py @@ -1,7 +1,7 @@ -def get_available_symbols(iexfinance): - iexfinance.refdata.get_symbols.return_value = [ +def get_available_symbols(refdata): + refdata.get_iex_symbols.return_value = [ { "symbol": "A", "name": "AGILENT TECHNOLOGIES INC", @@ -21,8 +21,8 @@ def get_available_symbols(iexfinance): ] -def get_key_stats(iexfinance): - iexfinance.stocks.Stock().get_key_stats.return_value = { +def get_key_stats(stocks): + stocks().get_key_stats.return_value = { 'A': { 'companyName': 'Agilent Technologies Inc.', 'marketcap': 20626540000, @@ -127,8 +127,8 @@ def get_key_stats(iexfinance): 'day30ChangePercent': -0.12963346448540067}} -def get_financials(iexfinance): - iexfinance.stocks.Stock().get_financials.return_value = {'AA': [ +def get_financials(stocks): + stocks().get_financials.return_value = {'AA': [ {'reportDate': '2018-06-30', 'grossProfit': 947000000, 'costOfRevenue': 2632000000, @@ -211,8 +211,8 @@ def get_financials(iexfinance): 'operatingGainsLosses': 16000000}]} -def get_chart(iexfinance): - iexfinance.stocks.Stock().get_chart.return_value = { +def get_chart(stocks): + stocks().get_chart.return_value = { 'AA': [{ 'date': '2018-08-21', 'open': 41.88, diff --git a/tests/datamock/mock_tradeapi.py b/tests/datamock/mock_tradeapi.py index bfaa092..d7e4928 100644 --- a/tests/datamock/mock_tradeapi.py +++ b/tests/datamock/mock_tradeapi.py @@ -1,4 +1,4 @@ -from alpaca_trade_api.entity import Asset +from alpaca_trade_api.entity import Asset, BarSet def list_assets(tradeapi): @@ -18,6 +18,16 @@ def list_assets(tradeapi): ] +def get_barset(tradeapi): + tradeapi.REST().get_barset.return_value = BarSet({ + "AA": [{"t": 1544129220, + "o": 172.26, + "h": 172.3, + "l": 172.16, + "c": 172.18, + "v": 3892}]}) + + def list_financials(tradeapi): tradeapi.REST().polygon.get.return_value = { 'AA': [{'symbol': 'AA', diff --git a/tests/test_alpaca.py b/tests/test_alpaca.py new file mode 100644 index 0000000..d7dd4b3 --- /dev/null +++ b/tests/test_alpaca.py @@ -0,0 +1,19 @@ +import pandas as pd +import numpy as np + +from .datamock import mock_tradeapi + +from pipeline_live.data.alpaca.pricing import USEquityPricing + +def test_pricing_loader(refdata, alpaca_tradeapi, data_path): + mock_tradeapi.list_assets(alpaca_tradeapi) + mock_tradeapi.get_barset(alpaca_tradeapi) + + loader = USEquityPricing.get_loader() + columns = [USEquityPricing.close] + dates = [pd.Timestamp('2018-08-22', tz='UTC')] + symbols = ['AA'] + mask = np.zeros((1, 1), dtype='bool') + out = loader.load_adjusted_array(columns, dates, symbols, mask) + + assert out[USEquityPricing.close]._data.shape == (1, 1) \ No newline at end of file diff --git a/tests/test_engine.py b/tests/test_engine.py index ff28e35..5b92c62 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -8,13 +8,13 @@ from .datamock import mock_iex -def test_engine(iexfinance, data_path): +def test_engine(refdata, stocks, data_path): def list_symbols(): return ['A', 'AA'] - mock_iex.get_available_symbols(iexfinance) - mock_iex.get_key_stats(iexfinance) - mock_iex.get_chart(iexfinance) + mock_iex.get_available_symbols(refdata) + mock_iex.get_key_stats(stocks) + mock_iex.get_chart(stocks) eng = LivePipelineEngine(list_symbols) ADV = AverageDollarVolume(window_length=20,) diff --git a/tests/test_iex.py b/tests/test_iex.py index 6f9f16e..c9d8fde 100644 --- a/tests/test_iex.py +++ b/tests/test_iex.py @@ -7,10 +7,10 @@ from .datamock import mock_iex -def test_IEXKeyStats(iexfinance, data_path): +def test_IEXKeyStats(refdata, stocks, data_path): - mock_iex.get_available_symbols(iexfinance) - mock_iex.get_key_stats(iexfinance) + mock_iex.get_available_symbols(refdata) + mock_iex.get_key_stats(stocks) marketcap = IEXKeyStats.marketcap loader = marketcap.dataset.get_loader() @@ -20,9 +20,9 @@ def test_IEXKeyStats(iexfinance, data_path): assert out[marketcap][0][0] != 0.0 -def test_pricing_loader(iexfinance, data_path): - mock_iex.get_available_symbols(iexfinance) - mock_iex.get_chart(iexfinance) +def test_pricing_loader(refdata, stocks, data_path): + mock_iex.get_available_symbols(refdata) + mock_iex.get_chart(stocks) loader = USEquityPricing.get_loader() columns = [USEquityPricing.close] diff --git a/tests/test_polygon.py b/tests/test_polygon.py index b90188c..2b62ecb 100644 --- a/tests/test_polygon.py +++ b/tests/test_polygon.py @@ -1,6 +1,8 @@ import pandas as pd from pipeline_live.data.polygon.fundamentals import PolygonCompany -from pipeline_live.data.polygon.filters import IsPrimaryShareEmulation, StaticSymbols +from pipeline_live.data.polygon.filters import ( + IsPrimaryShareEmulation, StaticSymbols +) from .datamock import mock_tradeapi @@ -29,8 +31,9 @@ def test_IsPrimaryShareEmulation(tradeapi, data_path): emu.compute(today, symbols, out) assert not out[0] + def test_StaticSymbols(tradeapi, data_path): - symbols=('A', 'BB') + symbols = ('A', 'BB') emu = StaticSymbols(symbols=symbols) today = object() diff --git a/tests/test_sources.py b/tests/test_sources.py index 382af4c..11c963c 100644 --- a/tests/test_sources.py +++ b/tests/test_sources.py @@ -1,8 +1,16 @@ -from pipeline_live.data.sources import iex, polygon +from pipeline_live.data.sources import alpaca, iex, polygon from .datamock import mock_iex, mock_tradeapi +def test_alpaca(alpaca_tradeapi, data_path): + mock_tradeapi.list_assets(alpaca_tradeapi) + mock_tradeapi.get_barset(alpaca_tradeapi) + + data = alpaca.get_stockprices(2) + assert data['AA'].iloc[0].close == 172.18 + + def test_polygon(tradeapi, data_path): mock_tradeapi.list_assets(tradeapi) mock_tradeapi.list_financials(tradeapi) @@ -17,9 +25,9 @@ def test_polygon(tradeapi, data_path): assert data['AA']['name'] == 'Alcoa Corp' -def test_iex(iexfinance, data_path): - mock_iex.get_available_symbols(iexfinance) - mock_iex.get_key_stats(iexfinance) +def test_iex(refdata, stocks, data_path): + mock_iex.get_available_symbols(refdata) + mock_iex.get_key_stats(stocks) kstats = iex.key_stats() @@ -30,7 +38,7 @@ def test_iex(iexfinance, data_path): kstats = iex.key_stats() assert kstats['AA']['returnOnCapital'] is None - mock_iex.get_financials(iexfinance) + mock_iex.get_financials(stocks) financials = iex.financials() assert len(financials) == 1 @@ -39,7 +47,7 @@ def test_iex(iexfinance, data_path): ed = iex._ensure_dict(1, ['AA']) assert ed['AA'] == 1 - mock_iex.get_chart(iexfinance) + mock_iex.get_chart(stocks) data = iex.get_stockprices() assert len(data) == 1