Repository: ranaroussi/yfinance

Issue:
dji ticker is not working
Hello, currently I got an error when running:
`yf.download(tickers = "dji", interval = "1d", period = "1d")`
Output:
```
[*********************100%***********************]  1 of 1 completed

1 Failed download:
- DJI: AttributeError("'NoneType' object has no attribute 'tz_convert'")
```
And this returns an empty dataframe.

yfinance version: 0.2.10
Python version: 3.8 on Google Colab


Discussion:
I switched the version back to 0.2.9 and didn't run into this error, is that caused by some changes in yesterday's update of 0.2.10?
If the same problem I'm running into with symbol QCSTIX while accessing fast_info['last_price'] on the ticker then the solution is to change the code in utils.py's format_history_metadata that reads:

    for k in ["firstTradeDate", "regularMarketTime"]:
        if k in md:
            md[k] = _pd.to_datetime(md[k], unit='s', utc=True).tz_convert(tz)

To instead read:

    for k in ["firstTradeDate", "regularMarketTime"]:
        if k in md:
            if md[k] is not None:
                md[k] = _pd.to_datetime(md[k], unit='s', utc=True).tz_convert(tz)


@sdeibel Spot on. Would you mind submitting a pull request? I know it's simple, but it'll introduce you to process in case you want to submit other changes. Read #1084 first.
Sure.  I'm traveling so a bit slow t get to this, but will try to do it soon.

Resolving patch:
diff --git a/yfinance/base.py b/yfinance/base.py
--- a/yfinance/base.py
+++ b/yfinance/base.py
@@ -270,11 +270,15 @@ def last_price(self):
             return self._last_price
         prices = self._get_1y_prices()
         if prices.empty:
-            self._last_price = self._get_exchange_metadata()["regularMarketPrice"]
+            md = self._get_exchange_metadata()
+            if "regularMarketPrice" in md:
+                self._last_price = md["regularMarketPrice"]
         else:
             self._last_price = float(prices["Close"].iloc[-1])
             if _np.isnan(self._last_price):
-                self._last_price = self._get_exchange_metadata()["regularMarketPrice"]
+                md = self._get_exchange_metadata()
+                if "regularMarketPrice" in md:
+                    self._last_price = md["regularMarketPrice"]
         return self._last_price
 
     @property
@@ -282,16 +286,23 @@ def previous_close(self):
         if self._prev_close is not None:
             return self._prev_close
         prices = self._get_1wk_1h_prepost_prices()
-        prices = prices[["Close"]].groupby(prices.index.date).last()
-        if prices.shape[0] < 2:
-            # Very few symbols have previousClose despite no 
-            # no trading data. E.g. 'QCSTIX'.
-            # So fallback to original info[] if available.
-            self._tkr.info  # trigger fetch
-            if "previousClose" in self._tkr._quote._retired_info:
-                self._prev_close = self._tkr._quote._retired_info["previousClose"]
+        fail = False
+        if prices.empty:
+            fail = True
         else:
-            self._prev_close = float(prices["Close"].iloc[-2])
+            prices = prices[["Close"]].groupby(prices.index.date).last()
+            if prices.shape[0] < 2:
+                # Very few symbols have previousClose despite no 
+                # no trading data e.g. 'QCSTIX'.
+                fail = True
+            else:
+                self._prev_close = float(prices["Close"].iloc[-2])
+        if fail:
+            # Fallback to original info[] if available.
+            self._tkr.info  # trigger fetch
+            k = "previousClose"
+            if self._tkr._quote._retired_info is not None and k in self._tkr._quote._retired_info:
+                self._prev_close = self._tkr._quote._retired_info[k]
         return self._prev_close
 
     @property
@@ -309,8 +320,9 @@ def regular_market_previous_close(self):
             # no trading data. E.g. 'QCSTIX'.
             # So fallback to original info[] if available.
             self._tkr.info  # trigger fetch
-            if "regularMarketPreviousClose" in self._tkr._quote._retired_info:
-                self._reg_prev_close = self._tkr._quote._retired_info["regularMarketPreviousClose"]
+            k = "regularMarketPreviousClose"
+            if self._tkr._quote._retired_info is not None and k in self._tkr._quote._retired_info:
+                self._reg_prev_close = self._tkr._quote._retired_info[k]
         else:
             self._reg_prev_close = float(prices["Close"].iloc[-2])
         return self._reg_prev_close
@@ -483,8 +495,9 @@ def market_cap(self):
             # E.g. 'BTC-USD'
             # So fallback to original info[] if available.
             self._tkr.info
-            if "marketCap" in self._tkr._quote._retired_info:
-                self._mcap = self._tkr._quote._retired_info["marketCap"]
+            k = "marketCap"
+            if self._tkr._quote._retired_info is not None and k in self._tkr._quote._retired_info:
+                self._mcap = self._tkr._quote._retired_info[k]
         else:
             self._mcap = float(shares * self.last_price)
         return self._mcap
diff --git a/yfinance/utils.py b/yfinance/utils.py
--- a/yfinance/utils.py
+++ b/yfinance/utils.py
@@ -699,7 +699,7 @@ def format_history_metadata(md):
     tz = md["exchangeTimezoneName"]
 
     for k in ["firstTradeDate", "regularMarketTime"]:
-        if k in md:
+        if k in md and md[k] is not None:
             md[k] = _pd.to_datetime(md[k], unit='s', utc=True).tz_convert(tz)
 
     if "currentTradingPeriod" in md:


Test patch:
diff --git a/tests/ticker.py b/tests/ticker.py
--- a/tests/ticker.py
+++ b/tests/ticker.py
@@ -52,12 +52,16 @@ def test_getTz(self):
     def test_badTicker(self):
         # Check yfinance doesn't die when ticker delisted
 
-        tkr = "AM2Z.TA"
+        tkr = "DJI"  # typo of "^DJI"
         dat = yf.Ticker(tkr, session=self.session)
         dat.history(period="1wk")
         dat.history(start="2022-01-01")
         dat.history(start="2022-01-01", end="2022-03-01")
         yf.download([tkr], period="1wk")
+
+        for k in dat.fast_info:
+            dat.fast_info[k]
+
         dat.isin
         dat.major_holders
         dat.institutional_holders
@@ -91,43 +95,48 @@ def test_badTicker(self):
     def test_goodTicker(self):
         # that yfinance works when full api is called on same instance of ticker
 
-        tkr = "IBM"
-        dat = yf.Ticker(tkr, session=self.session)
-
-        dat.isin
-        dat.major_holders
-        dat.institutional_holders
-        dat.mutualfund_holders
-        dat.dividends
-        dat.splits
-        dat.actions
-        dat.shares
-        dat.get_shares_full()
-        dat.info
-        dat.calendar
-        dat.recommendations
-        dat.earnings
-        dat.quarterly_earnings
-        dat.income_stmt
-        dat.quarterly_income_stmt
-        dat.balance_sheet
-        dat.quarterly_balance_sheet
-        dat.cashflow
-        dat.quarterly_cashflow
-        dat.recommendations_summary
-        dat.analyst_price_target
-        dat.revenue_forecasts
-        dat.sustainability
-        dat.options
-        dat.news
-        dat.earnings_trend
-        dat.earnings_dates
-        dat.earnings_forecasts
+        tkrs = ["IBM"]
+        tkrs.append("QCSTIX")  # weird ticker, no price history but has previous close
+        for tkr in tkrs:
+            dat = yf.Ticker(tkr, session=self.session)
 
-        dat.history(period="1wk")
-        dat.history(start="2022-01-01")
-        dat.history(start="2022-01-01", end="2022-03-01")
-        yf.download([tkr], period="1wk")
+            dat.history(period="1wk")
+            dat.history(start="2022-01-01")
+            dat.history(start="2022-01-01", end="2022-03-01")
+            yf.download([tkr], period="1wk")
+
+            for k in dat.fast_info:
+                dat.fast_info[k]
+
+            dat.isin
+            dat.major_holders
+            dat.institutional_holders
+            dat.mutualfund_holders
+            dat.dividends
+            dat.splits
+            dat.actions
+            dat.shares
+            dat.get_shares_full()
+            dat.info
+            dat.calendar
+            dat.recommendations
+            dat.earnings
+            dat.quarterly_earnings
+            dat.income_stmt
+            dat.quarterly_income_stmt
+            dat.balance_sheet
+            dat.quarterly_balance_sheet
+            dat.cashflow
+            dat.quarterly_cashflow
+            dat.recommendations_summary
+            dat.analyst_price_target
+            dat.revenue_forecasts
+            dat.sustainability
+            dat.options
+            dat.news
+            dat.earnings_trend
+            dat.earnings_dates
+            dat.earnings_forecasts
 
 
 class TestTickerHistory(unittest.TestCase):