feature/yfinance-multi-ticker: Adds support to openbb-yfinance for multi-ticker download. (#5845)

* yfinance multi-ticker support

* multi-ticker support for market indices

* crypto multi-ticker support

* recapture tests

* yfinance cache and tests

* currency historical multi-ticker support

* style things

* black

* missed a values

---------

Co-authored-by: James Maslek <jmaslek11@gmail.com>
This commit is contained in:
Danglewood
2023-12-06 17:32:19 -08:00
committed by GitHub
parent 1604c78ee5
commit d951dc0401
7 changed files with 2599 additions and 2324 deletions

View File

@@ -63,7 +63,7 @@ class YFinanceCryptoHistoricalFetcher(
if params.get("end_date") is None:
transformed_params["end_date"] = now
return YFinanceCryptoHistoricalQueryParams(**params)
return YFinanceCryptoHistoricalQueryParams(**transformed_params)
@staticmethod
def extract_data(
@@ -72,12 +72,20 @@ class YFinanceCryptoHistoricalFetcher(
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the Yahoo Finance endpoint."""
if "-" not in query.symbol:
position = len(query.symbol) - 3
query.symbol = query.symbol[:position] + "-" + query.symbol[position:]
tickers = query.symbol.split(",")
new_tickers = []
for ticker in tickers:
if "-" not in ticker:
new_ticker = ticker[:-3] + "-" + ticker[-3:]
if "-" in ticker:
new_ticker = ticker
new_tickers.append(new_ticker)
symbols = ",".join(new_tickers)
data = yf_download(
query.symbol,
symbols,
start=query.start_date,
end=query.end_date,
interval=query.interval,
@@ -99,12 +107,9 @@ class YFinanceCryptoHistoricalFetcher(
data.set_index("date", inplace=True)
data.index = to_datetime(data.index)
start_date_dt = datetime.combine(query.start_date, datetime.min.time())
end_date_dt = datetime.combine(query.end_date, datetime.min.time())
data = data[
(data.index >= start_date_dt + timedelta(days=days))
& (data.index <= end_date_dt)
(data.index >= to_datetime(query.start_date))
& (data.index <= to_datetime(query.end_date + timedelta(days=days)))
]
data.reset_index(inplace=True)

View File

@@ -48,12 +48,14 @@ class YFinanceCurrencyHistoricalFetcher(
params: Dict[str, Any]
) -> YFinanceCurrencyHistoricalQueryParams:
"""Transform the query."""
transformed_params = params
transformed_params["symbol"] = (
f"{transformed_params['symbol'].upper()}=X"
if "=X" not in transformed_params["symbol"].upper()
else transformed_params["symbol"].upper()
)
symbols = params["symbol"].split(",")
new_symbols = [
f"{s.upper()}=X" if "=X" not in s.upper() else s.upper() for s in symbols
]
transformed_params["symbol"] = ",".join(new_symbols)
now = datetime.now().date()
if params.get("start_date") is None:
@@ -94,12 +96,9 @@ class YFinanceCurrencyHistoricalFetcher(
data.set_index("date", inplace=True)
data.index = to_datetime(data.index)
start_date_dt = datetime.combine(query.start_date, datetime.min.time())
end_date_dt = datetime.combine(query.end_date, datetime.min.time())
data = data[
(data.index >= start_date_dt + timedelta(days=days))
& (data.index <= end_date_dt)
(data.index >= to_datetime(query.start_date))
& (data.index <= to_datetime(query.end_date + timedelta(days=days)))
]
data.reset_index(inplace=True)

View File

@@ -58,6 +58,31 @@ class YFinanceMarketIndicesFetcher(
if params.get("end_date") is None:
transformed_params["end_date"] = now
tickers = params.get("symbol").lower().split(",")
new_tickers = []
for ticker in tickers:
_ticker = ""
indices = pd.DataFrame(INDICES).transpose().reset_index()
indices.columns = ["code", "name", "symbol"]
if ticker in indices["code"].values:
_ticker = indices[indices["code"] == ticker]["symbol"].values[0]
if ticker.title() in indices["name"].values:
_ticker = indices[indices["name"] == ticker.title()]["symbol"].values[0]
if "^" + ticker.upper() in indices["symbol"].values:
_ticker = "^" + ticker.upper()
if ticker.upper() in indices["symbol"].values:
_ticker = ticker.upper()
if _ticker != "":
new_tickers.append(_ticker)
transformed_params["symbol"] = ",".join(new_tickers)
return YFinanceMarketIndicesQueryParams(**params)
@staticmethod
@@ -67,21 +92,9 @@ class YFinanceMarketIndicesFetcher(
**kwargs: Any,
) -> List[dict]:
"""Return the raw data from the Yahoo Finance endpoint."""
symbol = query.symbol.lower()
indices = pd.DataFrame(INDICES).transpose().reset_index()
indices.columns = ["code", "name", "symbol"]
if symbol in indices["code"].to_list():
symbol = indices[indices["code"] == symbol]["symbol"].values[0]
if symbol.title() in indices["name"].to_list():
symbol = indices[indices["name"] == symbol.title()]["symbol"].values[0]
if "^" + symbol.upper() in indices["symbol"].to_list():
symbol = "^" + symbol.upper()
data = yf_download(
symbol=symbol,
symbol=query.symbol,
start_date=query.start_date,
end_date=query.end_date,
interval=query.interval,
@@ -104,12 +117,9 @@ class YFinanceMarketIndicesFetcher(
data.set_index("date", inplace=True)
data.index = to_datetime(data.index)
start_date_dt = datetime.combine(query.start_date, datetime.min.time())
end_date_dt = datetime.combine(query.end_date, datetime.min.time())
data = data[
(data.index >= start_date_dt + timedelta(days=days))
& (data.index <= end_date_dt)
(data.index >= to_datetime(query.start_date))
& (data.index <= to_datetime(query.end_date + timedelta(days=days)))
]
data.reset_index(inplace=True)

View File

@@ -10,6 +10,7 @@ from typing import Any, Literal, Optional, Union
import pandas as pd
import yfinance as yf
from dateutil.relativedelta import relativedelta
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_yfinance.utils.references import MONTHS
@@ -91,7 +92,7 @@ def yf_download(
keepna: bool = False,
repair: bool = False,
rounding: bool = False,
group_by: Literal["symbol", "column"] = "column",
group_by: Literal["symbol", "column"] = "symbol",
adjusted: bool = False,
**kwargs: Any,
) -> pd.DataFrame:
@@ -115,22 +116,40 @@ def yf_download(
if adjusted is False:
kwargs = dict(auto_adjust=False, back_adjust=False)
data = yf.download(
tickers=symbol,
start=_start_date,
end=None,
interval=interval,
period=period,
prepost=prepost,
actions=actions,
progress=progress,
ignore_tz=ignore_tz,
keepna=keepna,
repair=repair,
rounding=rounding,
group_by=group_by,
**kwargs,
)
try:
data = yf.download(
tickers=symbol,
start=_start_date,
end=None,
interval=interval,
period=period,
prepost=prepost,
actions=actions,
progress=progress,
ignore_tz=ignore_tz,
keepna=keepna,
repair=repair,
rounding=rounding,
group_by=group_by,
**kwargs,
)
except ValueError:
raise EmptyDataError()
tickers = symbol.split(",")
if len(tickers) > 1:
_data = pd.DataFrame()
for ticker in tickers:
temp = data[ticker].copy().dropna(how="all")
for i in temp.index:
temp.loc[i, "symbol"] = ticker
temp = temp.reset_index().rename(
columns={"Date": "date", "Datetime": "date"}
)
_data = pd.concat([_data, temp])
_data = _data.set_index(["date", "symbol"]).sort_index()
data = _data
if not data.empty:
data = data.reset_index()
data = data.rename(columns={"Date": "date", "Datetime": "date"})
@@ -159,11 +178,9 @@ def yf_download(
"5y",
"10y",
]:
data["date"] = (
data["date"].dt.tz_localize(None).dt.strftime("%Y-%m-%d %H:%M:%S")
)
data["date"] = data["date"].dt.strftime("%Y-%m-%d %H:%M:%S")
if interval not in ["1m", "2m", "5m", "15m", "30m", "90m", "60m", "1h"]:
data["date"] = data["date"].dt.tz_localize(None).dt.strftime("%Y-%m-%d")
data["date"] = data["date"].dt.strftime("%Y-%m-%d")
if adjusted is False:
data = data.drop(columns=["Adj Close"])

View File

@@ -1,4 +1,82 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate, br
Connection:
- keep-alive
method: GET
uri: https://fc.yahoo.com/
response:
body:
string: "<!doctype html public \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html><head><title>Yahoo
- 404 Not Found</title><style>\n/* nn4 hide */ \n/*/*/\nbody {font:small/1.2em
arial,helvetica,clean,sans-serif;font:x-small;text-align:center;}table {font-size:inherit;font:x-small;}\nhtml>body
{font:83%/1.2em arial,helvetica,clean,sans-serif;}input {font-size:100%;vertical-align:middle;}p,
form {margin:0;padding:0;}\np {padding-bottom:6px;margin-bottom:10px;}#doc
{width:48.5em;margin:0 auto;border:1px solid #fff;text-align:center;}#ygma
{text-align:right;margin-bottom:53px}\n#ygma img {float:left;}#ygma div {border-bottom:1px
solid #ccc;padding-bottom:8px;margin-left:152px;}#bd {clear:both;text-align:left;width:75%;margin:0
auto 20px;}\nh1 {font-size:135%;text-align:center;margin:0 0 15px;}legend
{display:none;}fieldset {border:0 solid #fff;padding:.8em 0 .8em 4.5em;}\nform
{position:relative;background:#eee;margin-bottom:15px;border:1px solid #ccc;border-width:1px
0;}\n#s1p {width:15em;margin-right:.1em;}\nform span {position:absolute;left:70%;top:.8em;}form
a {font:78%/1.2em arial;display:block;padding-left:.8em;white-space:nowrap;background:
url(https://s.yimg.com/lq/i/s/bullet.gif) no-repeat left center;} \nform .sep
{display:none;}.more {text-align:center;}#ft {padding-top:10px;border-top:1px
solid #999;}#ft p {text-align:center;font:78% arial;}\n/* end nn4 hide */\n</style></head>\n<body><div
id=\"doc\">\n<div id=\"ygma\"><a href=\"https://www.yahoo.com/\"><img src=https://s.yimg.com/lq/i/yahoo.gif
width=147 height=31 border=0 alt=\"Yahoo\"></a><div><a href=\"https://www.yahoo.com\">Yahoo</a>
- <a href=\"https://help.yahoo.com\">Help</a></div></div><div id=\"bd\"><p>The
requested URL was not found on this server.</p>\n<p>Please check the URL for
proper spelling and capitalization. If\nyou're having trouble locating a destination
on Yahoo, try visiting the \n<strong><a \nhref=\"https://www.yahoo.com\">Yahoo
home\npage</a></strong> or you may find what you're looking for\nif you try
searching below.</p>\n<form name=\"s1\" action=\"https://search.yahoo.com/search\"><fieldset>\n<legend><label
for=\"s1p\">Search the Web</label></legend>\n<input type=\"text\" size=30
name=\"p\" id=\"s1p\" title=\"enter search terms here\">\n<input type=\"submit\"
value=\"Search\">\n</fieldset></form>\n<p class=\"more\">Please try <strong><a
href=\"https://help.yahoo.com\">Yahoo Help Central</a></strong> if you need
more assistance.</p>\n</div><div id=\"ft\"><p>Copyright &copy; 2023 Yahoo
Inc. All rights reserved. <a href=\"https://privacy.yahoo.com\">Privacy Policy</a>
- <a href=\"https://info.yahoo.com/legal/us/yahoo/utos/terms/\">Terms of Service</a></p></div>\n</div></body></html>\n"
headers:
Age:
- '0'
Connection:
- keep-alive
Content-Length:
- '2659'
Content-Type:
- text/html; charset=iso-8859-1
Date:
- Fri, 01 Dec 2023 18:53:01 GMT
Expect-CT:
- max-age=31536000, report-uri="http://csp.yahoo.com/beacon/csp?src=yahoocom-expect-ct-report-only"
P3P:
- policyref="https://policies.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM
DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND
PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV"
Referrer-Policy:
- no-referrer-when-downgrade
Server:
- ATS
Set-Cookie:
- A3=d=AQABBI0ramUCEFKCuPaLYqHEwIBxhoLwCDEFEgEBAQF9a2V0ZSXUxyMA_eMAAA&S=AQAAAuO1tSCJA6SjMjYh5yTo1x4;
Expires=Sun, 1 Dec 2024 00:53:01 GMT; Max-Age=31557600; Domain=.yahoo.com;
Path=/; SameSite=None; Secure; HttpOnly
Strict-Transport-Security:
- max-age=31536000
X-Content-Type-Options:
- nosniff
X-XSS-Protection:
- 1; mode=block
status:
code: 404
message: Not Found
- request:
body: null
headers:
@@ -11,10 +89,60 @@ interactions:
Cookie:
- MOCK_COOKIE
method: GET
uri: https://query2.finance.yahoo.com/v8/finance/chart/SPY?crumb=MOCK_CRUMB&interval=1d&range=1d
uri: https://query1.finance.yahoo.com/v1/test/getcrumb
response:
body:
string: '{"chart":{"result":[{"meta":{"currency":"USD","symbol":"SPY","exchangeName":"PCX","instrumentType":"ETF","firstTradeDate":728317800,"regularMarketTime":1701118800,"gmtoffset":-18000,"timezone":"EST","exchangeTimezoneName":"America/New_York","regularMarketPrice":454.48,"chartPreviousClose":455.3,"priceHint":2,"currentTradingPeriod":{"pre":{"timezone":"EST","start":1701075600,"end":1701095400,"gmtoffset":-18000},"regular":{"timezone":"EST","start":1701095400,"end":1701118800,"gmtoffset":-18000},"post":{"timezone":"EST","start":1701118800,"end":1701133200,"gmtoffset":-18000}},"dataGranularity":"1d","range":"1d","validRanges":["1d","5d","1mo","3mo","6mo","1y","2y","5y","10y","ytd","max"]},"timestamp":[1701118800],"indicators":{"quote":[{"volume":[49500027],"low":[454.07989501953125],"high":[455.4901123046875],"open":[454.6499938964844],"close":[454.4800109863281]}],"adjclose":[{"adjclose":[454.4800109863281]}]}}],"error":null}}'
string: pzrtQzapOQy
headers:
Age:
- '0'
Connection:
- keep-alive
Expect-CT:
- max-age=31536000, report-uri="http://csp.yahoo.com/beacon/csp?src=yahoocom-expect-ct-report-only"
Referrer-Policy:
- no-referrer-when-downgrade
Strict-Transport-Security:
- max-age=31536000
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
X-XSS-Protection:
- 1; mode=block
cache-control:
- private
content-type:
- text/plain;charset=utf-8
date:
- Fri, 01 Dec 2023 18:53:01 GMT
server:
- ATS
x-envoy-decorator-operation:
- finance-yql--mtls-default-production-gq1.finance-k8s.svc.yahoo.local:4080/*
x-envoy-upstream-service-time:
- '1'
x-frame-options:
- SAMEORIGIN
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate, br
Connection:
- keep-alive
Cookie:
- MOCK_COOKIE
method: GET
uri: https://query2.finance.yahoo.com/v8/finance/chart/%5EGSPC?crumb=MOCK_CRUMB&interval=1d&range=1d
response:
body:
string: '{"chart":{"result":[{"meta":{"currency":"USD","symbol":"^GSPC","exchangeName":"SNP","instrumentType":"INDEX","firstTradeDate":-1325583000,"regularMarketTime":1701456780,"gmtoffset":-18000,"timezone":"EST","exchangeTimezoneName":"America/New_York","regularMarketPrice":4586.99,"chartPreviousClose":4567.8,"priceHint":2,"currentTradingPeriod":{"pre":{"timezone":"EST","start":1701421200,"end":1701441000,"gmtoffset":-18000},"regular":{"timezone":"EST","start":1701441000,"end":1701464400,"gmtoffset":-18000},"post":{"timezone":"EST","start":1701464400,"end":1701478800,"gmtoffset":-18000}},"dataGranularity":"1d","range":"1d","validRanges":["1d","5d","1mo","3mo","6mo","1y","2y","5y","10y","ytd","max"]},"timestamp":[1701456780],"indicators":{"quote":[{"high":[4599.39013671875],"volume":[1354097000],"open":[4559.43017578125],"low":[4554.7099609375],"close":[4586.990234375]}],"adjclose":[{"adjclose":[4586.990234375]}]}}],"error":null}}'
headers:
Age:
- '0'
@@ -33,11 +161,11 @@ interactions:
cache-control:
- public, max-age=10, stale-while-revalidate=20
content-length:
- '937'
- '936'
content-type:
- application/json;charset=utf-8
date:
- Mon, 27 Nov 2023 23:48:17 GMT
- Fri, 01 Dec 2023 18:53:01 GMT
server:
- ATS
vary:
@@ -45,7 +173,7 @@ interactions:
x-envoy-decorator-operation:
- finance-chart-api--mtls-production-gq1.finance-k8s.svc.yahoo.local:4080/*
x-envoy-upstream-service-time:
- '72'
- '2'
status:
code: 200
message: OK
@@ -61,139 +189,125 @@ interactions:
Cookie:
- MOCK_COOKIE
method: GET
uri: https://query2.finance.yahoo.com/v8/finance/chart/SPY?crumb=MOCK_CRUMB&events=div%2Csplits%2CcapitalGains&includePrePost=True&interval=1d&period1=MOCK_PERIOD_1&period2=MOCK_PERIOD_2
uri: https://query2.finance.yahoo.com/v8/finance/chart/%5EGSPC?crumb=MOCK_CRUMB&events=div%2Csplits%2CcapitalGains&includePrePost=True&interval=1d&period1=MOCK_PERIOD_1&period2=MOCK_PERIOD_2
response:
body:
string: !!binary |
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
Q5bSZAAA
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
AA==
headers:
Age:
- '1'
- '0'
Connection:
- keep-alive
Expect-CT:
@@ -215,7 +329,7 @@ interactions:
content-type:
- application/json;charset=utf-8
date:
- Mon, 27 Nov 2023 23:48:17 GMT
- Fri, 01 Dec 2023 18:53:01 GMT
server:
- ATS
vary:
@@ -223,7 +337,7 @@ interactions:
x-envoy-decorator-operation:
- finance-chart-api--mtls-production-gq1.finance-k8s.svc.yahoo.local:4080/*
x-envoy-upstream-service-time:
- '50'
- '5'
status:
code: 200
message: OK

View File

@@ -47,12 +47,14 @@ def vcr_config():
@pytest.mark.record_http
def test_y_finance_equity_historical_fetcher(credentials=test_credentials):
def test_y_finance_market_indices_fetcher(credentials=test_credentials):
params = {
"symbol": "AAPL",
"symbol": "^GSPC",
"start_date": date(2023, 1, 1),
"end_date": date(2023, 1, 10),
}
fetcher = YFinanceEquityHistoricalFetcher()
fetcher = YFinanceMarketIndicesFetcher()
result = fetcher.test(params, credentials)
assert result is None
@@ -60,7 +62,7 @@ def test_y_finance_equity_historical_fetcher(credentials=test_credentials):
@pytest.mark.record_http
def test_y_finance_crypto_historical_fetcher(credentials=test_credentials):
params = {
"symbol": "BTC-USD",
"symbol": "BTCUSD",
"start_date": date(2023, 1, 1),
"end_date": date(2023, 1, 10),
}
@@ -70,6 +72,17 @@ def test_y_finance_crypto_historical_fetcher(credentials=test_credentials):
assert result is None
@pytest.mark.record_http
def test_y_finance_equity_historical_fetcher(credentials=test_credentials):
params = {
"symbol": "AAPL",
}
fetcher = YFinanceEquityHistoricalFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_y_finance_currency_historical_fetcher(credentials=test_credentials):
params = {
@@ -83,19 +96,6 @@ def test_y_finance_currency_historical_fetcher(credentials=test_credentials):
assert result is None
@pytest.mark.record_http
def test_y_finance_market_indices_fetcher(credentials=test_credentials):
params = {
"symbol": "SPY",
"start_date": date(2023, 1, 1),
"end_date": date(2023, 1, 10),
}
fetcher = YFinanceMarketIndicesFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_y_finance_futures_historical_fetcher(credentials=test_credentials):
params = {