diff --git a/docs/index.html b/docs/index.html index d9d35c2..b2798da 100644 --- a/docs/index.html +++ b/docs/index.html @@ -78,8 +78,1742 @@

Sub-modules

+

Functions

+
+
+def DisableLogging() +
+
+
+
+
+def EnableLogging(mode=20) +
+
+
+
+
+def download(tickers, threads=True, ignore_tz=None, progress=True, interval='1d', group_by='column', max_age=None, period=None, start=None, end=None, prepost=False, actions=True, adjust_splits=True, adjust_divs=True, keepna=False, proxy=None, rounding=False, debug=True, quiet=False, trigger_at_market_close=False, session=None) +
+
+
+
+
+def verify_cached_tickers_prices(session=None, rtol=0.0001, vol_rtol=0.005, correct=False, halt_on_fail=True, resume_from_tkr=None, debug_tkr=None, debug_interval=None) +
+
+

:Parameters: +session: +Recommend providing a 'requests_cache' session, in case +you have to abort and resume verification (likely). +correct: +False, 'one', 'all' +resume_from_tkr: str +Resume verification from this ticker (alphabetical order). +Because maybe you had to abort verification partway. +debug_tkr: str +Only verify this ticker. +Because maybe you want to investigate a difference.

+
+
+

Classes

+
+
+class AmbiguousComparisonException +(value1, value2, operation, true_prob=None) +
+
+

Common base class for all non-exit exceptions.

+
+ +Expand source code + +
class AmbiguousComparisonException(Exception):
+    def __init__(self, value1, value2, operation, true_prob=None):
+        if not isinstance(operation, str):
+            raise TypeError(f"operation must be a string not {type(operation)}")
+        if true_prob is not None and not isinstance(true_prob, (int, float)):
+            raise TypeError(f"true_prob must be numeric not {type(true_prob)}")
+
+        self.value1 = value1
+        self.value2 = value2
+        self.operation = operation
+        self.true_prob = true_prob
+
+    def __str__(self):
+        msg = f"Ambiguous whether {self.value1} {self.operation} {self.value2}"
+        if self.true_prob is not None:
+            msg += f" (true with probability {self.true_prob*100:.1f}%)"
+        return msg
+
+

Ancestors

+
    +
  • builtins.Exception
  • +
  • builtins.BaseException
  • +
+
+
+class Interval +(*args, **kwds) +
+
+

Create a collection of name/value pairs.

+

Example enumeration:

+
>>> class Color(Enum):
+...     RED = 1
+...     BLUE = 2
+...     GREEN = 3
+
+

Access them by:

+
    +
  • attribute access:
  • +
+
+
+
+

Color.RED +

+
+
+
+
    +
  • value lookup:
  • +
+
+
+
+

Color(1) +

+
+
+
+
    +
  • name lookup:
  • +
+
+
+
+

Color['RED'] +

+
+
+
+

Enumerations can be iterated over, and know how many members they have:

+
>>> len(Color)
+3
+
+
>>> list(Color)
+[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]
+
+

Methods can be added to enumerations, and members can have their own +attributes – see the documentation for details.

+
+ +Expand source code + +
class Interval(Enum):
+    Week = 5
+    Days1 = 10
+    Hours1 = 20
+    Mins90 = 21
+    Mins60 = 22
+    Mins30 = 23
+    Mins15 = 24
+    Mins5 = 25
+    Mins2 = 26
+    Mins1 = 27
+
+

Ancestors

+
    +
  • enum.Enum
  • +
+

Class variables

+
+
var Days1
+
+
+
+
var Hours1
+
+
+
+
var Mins1
+
+
+
+
var Mins15
+
+
+
+
var Mins2
+
+
+
+
var Mins30
+
+
+
+
var Mins5
+
+
+
+
var Mins60
+
+
+
+
var Mins90
+
+
+
+
var Week
+
+
+
+
+
+
+class Period +(*args, **kwds) +
+
+

Create a collection of name/value pairs.

+

Example enumeration:

+
>>> class Color(Enum):
+...     RED = 1
+...     BLUE = 2
+...     GREEN = 3
+
+

Access them by:

+
    +
  • attribute access:
  • +
+
+
+
+

Color.RED +

+
+
+
+
    +
  • value lookup:
  • +
+
+
+
+

Color(1) +

+
+
+
+
    +
  • name lookup:
  • +
+
+
+
+

Color['RED'] +

+
+
+
+

Enumerations can be iterated over, and know how many members they have:

+
>>> len(Color)
+3
+
+
>>> list(Color)
+[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]
+
+

Methods can be added to enumerations, and members can have their own +attributes – see the documentation for details.

+
+ +Expand source code + +
class Period(Enum):
+    Days1 = 0
+    Days5 = 1
+    Months1 = 10
+    Months3 = 11
+    Months6 = 12
+    Years1 = 20
+    Years2 = 21
+    Years5 = 22
+    Ytd = 24
+    Max = 30
+
+

Ancestors

+
    +
  • enum.Enum
  • +
+

Class variables

+
+
var Days1
+
+
+
+
var Days5
+
+
+
+
var Max
+
+
+
+
var Months1
+
+
+
+
var Months3
+
+
+
+
var Months6
+
+
+
+
var Years1
+
+
+
+
var Years2
+
+
+
+
var Years5
+
+
+
+
var Ytd
+
+
+
+
+
+
+class Ticker +(ticker, session=None) +
+
+
+
+ +Expand source code + +
class Ticker:
+    def __init__(self, ticker, session=None):
+        self.ticker = ticker.upper()
+
+        self.session = session
+        self.dat = yf.Ticker(self.ticker, session=self.session)
+
+        self._yf_lag = None
+
+        self._histories_manager = None
+
+        self._info = None
+        self._fast_info = None
+
+        self._splits = None
+
+        self._shares = None
+
+        self._major_holders = None
+
+        self._institutional_holders = None
+
+        self._sustainability = None
+
+        self._recommendations = None
+
+        self._calendar = None
+
+        self._isin = None
+
+        self._options = None
+
+        self._news = None
+
+        self._debug = False
+        # self._debug = True
+
+        self._tz = None
+        self._exchange = None
+
+        exchange, tz_name = self._getExchangeAndTz()
+        self._financials_manager = yfcf.FinancialsManager(ticker, exchange, tz_name, session=self.session)
+
+    def history(self,
+                interval="1d",
+                max_age=None,  # defaults to half of interval
+                period=None,
+                start=None, end=None, prepost=False, actions=True,
+                adjust_splits=True, adjust_divs=True,
+                keepna=False,
+                proxy=None, rounding=False,
+                debug=True, quiet=False,
+                trigger_at_market_close=False):
+        """
+        Fetch historical market data for this ticker.
+
+        This method retrieves historical price and volume data, as well as information
+        about stock splits and dividend payments if requested.
+
+        Args:
+            interval (str, optional): Data interval. Defaults to "1d" (daily).
+                Other options: "1m" (minute), "5m", "15m", "30m", "60m", "90m", "1h", "1d", "5d", "1wk", "1mo", "3mo".
+            max_age (int, optional): Maximum age of cached data in seconds. Defaults to None (half of the interval).
+            period (str, optional): Data period to download. Defaults to None.
+                Options: "1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y", "10y", "ytd", "max".
+            start (str, optional): Download start date string (YYYY-MM-DD) or datetime. Defaults to None.
+            end (str, optional): Download end date string (YYYY-MM-DD) or datetime. Defaults to None.
+            prepost (bool, optional): Include pre and post market data. Defaults to False.
+            actions (bool, optional): Include stock splits and dividend data. Defaults to True.
+            adjust_splits (bool, optional): Adjust data for stock splits. Defaults to True.
+            adjust_divs (bool, optional): Adjust data for dividends. Defaults to True.
+            keepna (bool, optional): Keep NaN values. Defaults to False.
+            proxy (str, optional): Proxy URL. Defaults to None.
+            rounding (bool, optional): Round values to 2 decimal places. Defaults to False.
+            debug (bool, optional): Print debug messages. Defaults to True.
+            quiet (bool, optional): Suppress output messages. Defaults to False.
+            trigger_at_market_close (bool, optional): Trigger requests at market close. Defaults to False.
+
+        Returns:
+            pandas.DataFrame: A DataFrame containing the historical data. Columns typically include:
+                Date, Open, High, Low, Close, Volume, Dividends, Stock Splits.
+
+        Raises:
+            ValueError: If invalid date parameters are provided.
+            YFinanceException: If there's an error fetching the data from Yahoo Finance.
+
+        Note:
+            Either 'period' or 'start' and 'end' should be provided, but not both.
+        """
+
+        # t0 = perf_counter()
+
+        if prepost:
+            raise Exception("pre and post-market caching currently not implemented. If you really need it raise an issue on Github")
+
+        debug_yfc = self._debug
+        # debug_yfc = True
+
+        if start is not None or end is not None:
+            log_msg = f"Ticker::history(tkr={self.ticker} interval={interval} start={start} end={end} max_age={max_age} trigger_at_market_close={trigger_at_market_close} adjust_splits={adjust_splits}, adjust_divs={adjust_divs})"
+        else:
+            log_msg = f"Ticker::history(tkr={self.ticker} interval={interval} period={period} max_age={max_age} trigger_at_market_close={trigger_at_market_close} adjust_splits={adjust_splits}, adjust_divs={adjust_divs})"
+        yfcl.TraceEnter(log_msg)
+
+        td_1d = datetime.timedelta(days=1)
+        exchange, tz_name = self._getExchangeAndTz()
+        tz_exchange = ZoneInfo(tz_name)
+        yfct.SetExchangeTzName(exchange, tz_name)
+        dt_now = pd.Timestamp.utcnow()
+
+        # Type checks
+        if max_age is not None:
+            if isinstance(max_age, str):
+                if max_age.endswith("wk"):
+                    max_age = re.sub("wk$", "w", max_age)
+                max_age = pd.Timedelta(max_age)
+            if not isinstance(max_age, (datetime.timedelta, pd.Timedelta)):
+                raise Exception("Argument 'max_age' must be Timedelta or equivalent string")
+        if period is not None:
+            if start is not None or end is not None:
+                raise Exception("Don't set both 'period' and 'start'/'end'' arguments")
+            if isinstance(period, str):
+                if period in ["max", "ytd"]:
+                    period = yfcd.periodStrToEnum[period]
+                else:
+                    if period.endswith("wk"):
+                        period = re.sub("wk$", "w", period)
+                    if period.endswith("y"):
+                        period = relativedelta(years=int(re.sub("y$", "", period)))
+                    elif period.endswith("mo"):
+                        period = relativedelta(months=int(re.sub("mo", "", period)))
+                    else:
+                        period = pd.Timedelta(period)
+            if not isinstance(period, (yfcd.Period, datetime.timedelta, pd.Timedelta, relativedelta)):
+                raise Exception(f"Argument 'period' must be one of: 'max', 'ytd', Timedelta or equivalent string. Not {type(period)}")
+        if isinstance(interval, str):
+            if interval not in yfcd.intervalStrToEnum.keys():
+                raise Exception("'interval' if str must be one of: {}".format(yfcd.intervalStrToEnum.keys()))
+            interval = yfcd.intervalStrToEnum[interval]
+        if not isinstance(interval, yfcd.Interval):
+            raise Exception("'interval' must be yfcd.Interval")
+
+        start_d = None ; end_d = None
+        start_dt = None ; end_dt = None
+        interday = interval in [yfcd.Interval.Days1, yfcd.Interval.Week]#, yfcd.Interval.Months1, yfcd.Interval.Months3]
+        if start is not None:
+            start_dt, start_d = self._process_user_dt(start)
+            if start_dt > dt_now:
+                return None
+            if interval == yfcd.Interval.Week:
+                # Note: if start is on weekend then Yahoo can return weekly data starting
+                #       on Saturday. This breaks YFC, start must be Monday! So fix here:
+                if start_dt is None:
+                    # Working with simple dates, easy
+                    if start_d.weekday() in [5, 6]:
+                        start_d += datetime.timedelta(days=7-start_d.weekday())
+                else:
+                    wd = start_d.weekday()
+                    if wd in [5, 6]:
+                        start_d += datetime.timedelta(days=7-wd)
+                        start_dt = datetime.datetime.combine(start_d, datetime.time(0), tz_exchange)
+
+        if end is not None:
+            end_dt, end_d = self._process_user_dt(end)
+
+        if start_dt is not None and end_dt is not None and start_dt >= end_dt:
+            raise ValueError("start must be < end")
+
+        if debug_yfc:
+            print("- start_dt={} , end_dt={}".format(start_dt, end_dt))
+
+        if (start_dt is not None) and start_dt == end_dt:
+            return None
+
+        if max_age is None:
+            if interval == yfcd.Interval.Days1:
+                max_age = datetime.timedelta(hours=4)
+            elif interval == yfcd.Interval.Week:
+                max_age = datetime.timedelta(hours=60)
+            # elif interval == yfcd.Interval.Months1:
+            #     max_age = datetime.timedelta(days=15)
+            # elif interval == yfcd.Interval.Months3:
+            #     max_age = datetime.timedelta(days=45)
+            else:
+                max_age = 0.5*yfcd.intervalToTimedelta[interval]
+            if start is not None:
+                max_age = min(max_age, dt_now-start_dt)
+
+        if period is not None:
+            if isinstance(period, (datetime.timedelta, pd.Timedelta)):
+                if (dt_now - max_age) < (dt_now - period):
+                    raise Exception(f"max_age={max_age} must be less than period={period}")
+            elif period == yfcd.Period.Ytd:
+                dt_now_ex = dt_now.tz_convert(tz_exchange)
+                dt_year_start = pd.Timestamp(year=dt_now_ex.year, month=1, day=1).tz_localize(tz_exchange)
+                if (dt_now - max_age) < dt_year_start:
+                    raise Exception(f"max_age={max_age} must be less than days since this year start")
+        elif start is not None:
+            if (dt_now - max_age) < start_dt:
+                raise Exception(f"max_age={max_age} must be closer to now than start={start}")
+
+
+        if start_dt is not None:
+            try:
+                sched_14d = yfct.GetExchangeSchedule(exchange, start_dt.date(), start_dt.date()+14*td_1d)
+            except Exception as e:
+                if "Need to add mapping" in str(e):
+                    raise Exception("Need to add mapping of exchange {} to xcal (ticker={})".format(exchange, self.ticker))
+                else:
+                    raise
+            if sched_14d is None:
+                raise Exception("sched_14d is None for date range {}->{} and ticker {}".format(start_dt.date(), start_dt.date()+14*td_1d, self.ticker))
+            if sched_14d["open"].iloc[0] > dt_now:
+                # Requested date range is in future
+                return None
+        else:
+            sched_14d = None
+
+        # All date checks passed so can begin fetching
+
+        if ((start_d is None) or (end_d is None)) and (start_dt is not None) and (end_dt is not None):
+            # if start_d/end_d not set then start/end are datetimes, so need to inspect
+            # schedule opens/closes to determine days
+            if sched_14d is not None:
+                sched = sched_14d.iloc[0:1]
+            else:
+                sched = yfct.GetExchangeSchedule(exchange, start_dt.date(), end_dt.date()+td_1d)
+            n = sched.shape[0]
+            start_d = start_dt.date() if start_dt < sched["open"].iloc[0] else start_dt.date()+td_1d
+            end_d = end_dt.date()+td_1d if end_dt >= sched["close"].iloc[n-1] else end_dt.date()
+        else:
+            if exchange not in yfcd.exchangeToXcalExchange:
+                raise Exception("Need to add mapping of exchange {} to xcal (ticker={})".format(exchange, self.ticker))
+
+        if self._histories_manager is None:
+            self._histories_manager = yfcp.HistoriesManager(self.ticker, exchange, tz_name, self.session, proxy)
+
+        # t1_setup = perf_counter()
+
+        hist = self._histories_manager.GetHistory(interval)
+        if period is not None:
+            h = hist.get(start=None, end=None, period=period, max_age=max_age, trigger_at_market_close=trigger_at_market_close, quiet=quiet)
+        elif interday:
+            h = hist.get(start_d, end_d, period=None, max_age=max_age, trigger_at_market_close=trigger_at_market_close, quiet=quiet)
+        else:
+            h = hist.get(start_dt, end_dt, period=None, max_age=max_age, trigger_at_market_close=trigger_at_market_close, quiet=quiet)
+        if (h is None) or h.shape[0] == 0:
+            msg = f"YFC: history() exiting without price data (tkr={self.ticker}"
+            if start_dt is not None or end_dt is not None:
+                msg += f" start_dt={start_dt} end_dt={end_dt}"
+            else:
+                msg += f" period={period}"
+            msg += f" max_age={max_age}"
+            msg += f" interval={yfcd.intervalToString[interval]})"
+            raise Exception(msg)
+
+        # t2_sync = perf_counter()
+
+        f_dups = h.index.duplicated()
+        if f_dups.any():
+            raise Exception("{}: These timepoints have been duplicated: {}".format(self.ticker, h.index[f_dups]))
+
+        # Present table for user:
+        h_copied = False
+        if (start_dt is not None) and (end_dt is not None):
+            h = h.loc[start_dt:end_dt-datetime.timedelta(milliseconds=1)].copy()
+            h_copied = True
+
+        if not keepna:
+            price_data_cols = [c for c in yfcd.yf_data_cols if c in h.columns]
+            mask_nan_or_zero = (np.isnan(h[price_data_cols].to_numpy()) | (h[price_data_cols].to_numpy() == 0)).all(axis=1)
+            if mask_nan_or_zero.any():
+                h = h.drop(h.index[mask_nan_or_zero])
+                h_copied = True
+        # t3_filter = perf_counter()
+
+        if h.shape[0] == 0:
+            h = None
+        else:
+            if adjust_splits:
+                if not h_copied:
+                    h = h.copy()
+                for c in ["Open", "Close", "Low", "High", "Dividends"]:
+                    h[c] = np.multiply(h[c].to_numpy(), h["CSF"].to_numpy())
+                h["Volume"] = np.round(np.divide(h["Volume"].to_numpy(), h["CSF"].to_numpy()), 0).astype('int')
+            if adjust_divs:
+                if not h_copied:
+                    h = h.copy()
+                for c in ["Open", "Close", "Low", "High"]:
+                    h[c] = np.multiply(h[c].to_numpy(), h["CDF"].to_numpy())
+            else:
+                if not h_copied:
+                    h = h.copy()
+                h["Adj Close"] = np.multiply(h["Close"].to_numpy(), h["CDF"].to_numpy())
+            h = h.drop(["CSF", "CDF"], axis=1)
+
+            if rounding:
+                # Round to 4 sig-figs
+                if not h_copied:
+                    h = h.copy()
+                f_na = h["Close"].isna()
+                na = f_na.any()
+                if na:
+                    f_nna = ~f_na
+                    if not f_nna.any():
+                        raise Exception(f"{self.ticker}: price table is entirely NaNs. Delisted?" +" \n" + log_msg)
+                    last_close = h["Close"][f_nna].iloc[-1]
+                else:
+                    last_close = h["Close"].iloc[-1]
+                rnd = yfcu.CalculateRounding(last_close, 4)
+                for c in ["Open", "Close", "Low", "High"]:
+                    if na:
+                        h.loc[f_nna, c] = np.round(h.loc[f_nna, c].to_numpy(), rnd)
+                    else:
+                        h[c] = np.round(h[c].to_numpy(), rnd)
+
+            if debug_yfc:
+                print("- h:")
+                cols = [c for c in ["Close", "Dividends", "Volume", "CDF", "CSF"] if c in h.columns]
+                print(h[cols])
+                if "Dividends" in h.columns:
+                    f = h["Dividends"] != 0.0
+                    if f.any():
+                        print("- dividends:")
+                        print(h.loc[f, cols])
+                print("")
+            yfcl.TraceExit("Ticker::history() returning")
+
+        # t4_adjust = perf_counter()
+        # t_setup = t1_setup - t0
+        # t_sync = t2_sync - t1_setup
+        # t_filter = t3_filter - t2_sync
+        # t_adjust = t4_adjust - t3_filter
+        # t_sum = t_setup + t_sync + t_filter + t_adjust
+        # print("TIME: {:.4f}s: setup={:.4f} sync={:.4f} filter={:.4f} adjust={:.4f}".format(t_sum, t_setup, t_sync, t_filter, t_adjust))
+        # t_setup *= 100/t_sum
+        # t_sync *= 100/t_sum
+        # t_cache *= 100/t_sum
+        # t_filter *= 100/t_sum
+        # t_adjust *= 100/t_sum
+        # print("TIME %:        setup={:.1f}%  sync={:.1f}%  filter={:.1f}%  adjust={:.1f}%".format(t_setup, t_sync, t_filter, t_adju
+
+        return h
+
+    def _getCachedPrices(self, interval, proxy=None):
+        if self._histories_manager is None:
+            exchange, tz_name = self._getExchangeAndTz()
+            self._histories_manager = yfcp.HistoriesManager(self.ticker, exchange, tz_name, self.session, proxy)
+
+        if isinstance(interval, str):
+            if interval not in yfcd.intervalStrToEnum.keys():
+                raise Exception("'interval' if str must be one of: {}".format(yfcd.intervalStrToEnum.keys()))
+            interval = yfcd.intervalStrToEnum[interval]
+
+        return self._histories_manager.GetHistory(interval).h
+
+    def _getExchangeAndTz(self):
+        if self._tz is not None and self._exchange is not None:
+            return self._exchange, self._tz
+
+        exchange, tz_name = None, None
+        try:
+            exchange = self.get_info('9999d')['exchange']
+            if "exchangeTimezoneName" in self.get_info('9999d'):
+                tz_name = self.get_info('9999d')["exchangeTimezoneName"]
+            else:
+                tz_name = self.get_info('9999d')["timeZoneFullName"]
+        except Exception:
+            md = yf.Ticker(self.ticker, session=self.session).history_metadata
+            if 'exchangeName' in md.keys():
+                exchange = md['exchangeName']
+            if 'exchangeTimezoneName' in md.keys():
+                tz_name = md['exchangeTimezoneName']
+
+        if exchange is None or tz_name is None:
+            raise Exception(f"{self.ticker}: exchange and timezone not available")
+        self._tz = tz_name
+        self._exchange = exchange
+        return self._exchange, self._tz
+
+    def verify_cached_prices(self, rtol=0.0001, vol_rtol=0.005, correct='none', discard_old=False, quiet=True, debug=False, debug_interval=None):
+        if debug:
+            quiet = False
+        if debug_interval is not None and isinstance(debug_interval, str):
+            debug_interval = yfcd.intervalStrToEnum[debug_interval]
+
+        fn_locals = locals()
+        del fn_locals["self"]
+
+        interval = yfcd.Interval.Days1
+        cache_key = "history-"+yfcd.intervalToString[interval]
+        if not yfcm.IsDatumCached(self.ticker, cache_key):
+            return True
+
+        yfcl.TraceEnter(f"Ticker::verify_cached_prices(tkr={self.ticker} {fn_locals})")
+
+        if self._histories_manager is None:
+            exchange, tz_name = self._getExchangeAndTz()
+            self._histories_manager = yfcp.HistoriesManager(self.ticker, exchange, tz_name, self.session, proxy=None)
+
+        v = True
+
+        # First verify 1d
+        dt0 = self._histories_manager.GetHistory(interval)._getCachedPrices().index[0]
+        self.history(start=dt0.date(), quiet=quiet, trigger_at_market_close=True)  # ensure have all dividends
+        v = self._verify_cached_prices_interval(interval, rtol, vol_rtol, correct, discard_old, quiet, debug)
+        if debug_interval == yfcd.Interval.Days1:
+            yfcl.TraceExit(f"Ticker::verify_cached_prices() returning {v} (1st pass)")
+            return v
+        if not v:
+            if debug or not correct:
+                yfcl.TraceExit(f"Ticker::verify_cached_prices() returning {v} (1st pass)")
+                return v
+        if correct in ['one', 'all']:
+            # Rows were removed so re-fetch. Only do for 1d data
+            self.history(start=dt0.date(), quiet=quiet)
+
+            # repeat verification, because 'fetch backporting' may be buggy
+            v2 = self._verify_cached_prices_interval(interval, rtol, vol_rtol, correct, discard_old, quiet, debug)
+            if not v2 and debug:
+                yfcl.TraceExit(f"Ticker::verify_cached_prices() returning {v2} (post-correction)")
+                return v2
+            if not v2:
+                yfcl.TraceExit(f"Ticker::verify_cached_prices() returning {v2} (post-correction)")
+                return v2
+
+            if not v:
+                # Stop after correcting first problem, because user won't have been shown the next problem yet
+                yfcl.TraceExit(f"Ticker::verify_cached_prices() returning {v} (corrected but user should review next problem)")
+                return v
+
+        if debug_interval is not None:
+            if debug_interval == yfcd.Interval.Days1:
+                intervals = []
+            else:
+                intervals = [debug_interval]
+            debug = True
+        else:
+            intervals = yfcd.Interval
+        for interval in intervals:
+            if interval == yfcd.Interval.Days1:
+                continue
+            istr = yfcd.intervalToString[interval]
+            cache_key = "history-"+istr
+            if not yfcm.IsDatumCached(self.ticker, cache_key):
+                continue
+            vi = self._verify_cached_prices_interval(interval, rtol, vol_rtol, correct, discard_old, quiet, debug)
+            yfcl.TracePrint(f"{istr}: vi={vi}")
+
+            if not vi and correct != 'all':
+                # Stop after correcting first problem, because user won't have been shown the next problem yet
+                yfcl.TraceExit(f"Ticker::verify_cached_prices() returning {vi}")
+                return vi
+
+            v = v and vi
+
+        yfcl.TraceExit(f"Ticker::verify_cached_prices() returning {v}")
+
+        return v
+
+    def _verify_cached_prices_interval(self, interval, rtol=0.0001, vol_rtol=0.005, correct=False, discard_old=False, quiet=True, debug=False):
+        if debug:
+            quiet = False
+
+        fn_locals = locals()
+        del fn_locals["self"]
+
+        if isinstance(interval, str):
+            if interval not in yfcd.intervalStrToEnum.keys():
+                raise Exception("'interval' if str must be one of: {}".format(yfcd.intervalStrToEnum.keys()))
+            interval = yfcd.intervalStrToEnum[interval]
+
+        istr = yfcd.intervalToString[interval]
+        cache_key = "history-"+istr
+        if not yfcm.IsDatumCached(self.ticker, cache_key):
+            return True
+
+        yfcl.TraceEnter(f"Ticker::_verify_cached_prices_interval(tkr={self.ticker}, {fn_locals})")
+
+        if self._histories_manager is None:
+            exchange, tz_name = self._getExchangeAndTz()
+            self._histories_manager = yfcp.HistoriesManager(self.ticker, exchange, tz_name, self.session, proxy=None)
+
+        v = self._histories_manager.GetHistory(interval)._verifyCachedPrices(rtol, vol_rtol, correct, discard_old, quiet, debug)
+
+        yfcl.TraceExit(f"Ticker::_verify_cached_prices_interval() returning {v}")
+        return v
+
+    def _process_user_dt(self, dt):
+        exchange, tz_name = self._getExchangeAndTz()
+        return yfcu.ProcessUserDt(dt, tz_name)
+
+    @property
+    def info(self):
+        return self.get_info()
+
+    def get_info(self, max_age=None):
+        if self._info is not None:
+            return self._info
+
+        if max_age is None:
+            max_age = pd.Timedelta(yfcm._option_manager.max_ages.info)
+        elif not isinstance(max_age, (datetime.timedelta, pd.Timedelta)):
+            max_age = pd.Timedelta(max_age)
+        if max_age < pd.Timedelta(0):
+            raise Exception(f"'max_age' must be positive timedelta not {max_age}")
+
+        md = None
+        if yfcm.IsDatumCached(self.ticker, "info"):
+            self._info, md = yfcm.ReadCacheDatum(self.ticker, "info", True)
+            if 'FetchDate' not in self._info.keys():
+                # Old bug meant this could happen
+                fp = yfcm.GetFilepath(self.ticker, 'info')
+                mod_dt = datetime.datetime.fromtimestamp(os.path.getmtime(fp))
+                self._info['FetchDate'] = mod_dt
+                md['LastCheck'] = mod_dt
+                yfcm.StoreCacheDatum(self.ticker, "info", self._info, metadata=md)
+
+            if self._info is not None:
+                if md is None:
+                    md = {}
+                if 'LastCheck' not in md.keys():
+                    # Old bug meant this could happen
+                    md['LastCheck'] = self._info['FetchDate']
+                    yfcm.WriteCacheMetadata(self.ticker, "info", 'LastCheck', md['LastCheck'])
+                if max(self._info['FetchDate'], md['LastCheck']) + max_age > pd.Timestamp.now():
+                    return self._info
+
+        i = self.dat.info
+        i['FetchDate'] = pd.Timestamp.now()
+
+        if self._info is not None:
+            # Check new info is not downgrade
+            diff = len(i) - len(self._info)
+            diff_pct = float(diff) / float(len(self._info))
+            if diff_pct < -0.1 and diff < -10:
+                msg = 'When fetching new info, significant amount of data has disappeared\n'
+                missing_keys = [k for k in self._info.keys() if k not in i.keys()]
+                new_keys = [k for k in i.keys() if k not in self._info.keys()]
+                msg += "- missing: "
+                msg += str({k:self._info[k] for k in missing_keys}) + '\n'
+                msg += "- new: "
+                msg += str({k:i[k] for k in new_keys}) + '\n'
+
+                # msg += "\nKeep new data?"
+                # keep = click.confirm(msg, default=False)
+                # if not keep:
+                #     return self._info
+                #
+                msg += "\nDiscarding fetched info."
+                print(f'{self.ticker}: {msg}')
+                yfcm.WriteCacheMetadata(self.ticker, "info", 'LastCheck', i['FetchDate'])
+                return self._info
+
+        self._info = i
+        if md is None:
+            md = {}
+        md['LastCheck'] = i['FetchDate']
+        yfcm.StoreCacheDatum(self.ticker, "info", self._info, metadata=md)
+
+        exchange, tz_name = self._getExchangeAndTz()
+        yfct.SetExchangeTzName(exchange, tz_name)
+
+        return self._info
+
+    @property
+    def fast_info(self):
+        if self._fast_info is not None:
+            return self._fast_info
+
+        if yfcm.IsDatumCached(self.ticker, "fast_info"):
+            try:
+                self._fast_info = yfcm.ReadCacheDatum(self.ticker, "fast_info")
+            except Exception:
+                pass
+            else:
+                return self._fast_info
+
+        # self._fast_info = self.dat.fast_info
+        self._fast_info = {}
+        for k in self.dat.fast_info.keys():
+            try:
+                self._fast_info[k] = self.dat.fast_info[k]
+            except Exception as e:
+                if "decrypt" in str(e):
+                    pass
+                else:
+                    print(f"TICKER = {self.ticker}")
+                    raise
+        yfcm.StoreCacheDatum(self.ticker, "fast_info", self._fast_info)
+
+        yfct.SetExchangeTzName(self._fast_info["exchange"], self._fast_info["timezone"])
+
+        return self._fast_info
+
+    @property
+    def splits(self):
+        if self._splits is not None:
+            return self._splits
+
+        if yfcm.IsDatumCached(self.ticker, "splits"):
+            self._splits = yfcm.ReadCacheDatum(self.ticker, "splits")
+            return self._splits
+
+        self._splits = self.dat.splits
+        yfcm.StoreCacheDatum(self.ticker, "splits", self._splits)
+        return self._splits
+
+
+    def get_shares(self, start=None, end=None, max_age='30d'):
+        debug = False
+        # debug = True
+
+        max_age = pd.Timedelta(max_age)
+
+        # Process dates
+        exchange, tz = self._getExchangeAndTz()
+        dt_now = pd.Timestamp.utcnow().tz_convert(tz)
+        if start is not None:
+            start_dt, start_d = self._process_user_dt(start)
+            start = start_d
+        if end is not None:
+            end_dt, end_d = self._process_user_dt(end)
+            end = end_d
+        if end is None:
+            end_dt = dt_now
+            end = dt_now.date()
+        if start is None:
+            start = end - pd.Timedelta(days=548)  # 18 months
+            start_dt = end_dt - pd.Timedelta(days=548)
+        if start >= end:
+            raise Exception("Start date must be before end")
+        if debug:
+            print("- start =", start, " end =", end)
+
+        if self._shares is None:
+            if yfcm.IsDatumCached(self.ticker, "shares"):
+                if debug:
+                    print("- init shares from cache")
+                self._shares = yfcm.ReadCacheDatum(self.ticker, "shares")
+        if self._shares is None or self._shares.empty:
+            # Loaded from cache. Either re-fetch or return None
+            lastFetchDt = yfcm.ReadCacheMetadata(self.ticker, "shares", 'LastFetch')
+            do_fetch = (lastFetchDt is None) or ((dt_now - lastFetchDt) > max_age)
+            if do_fetch:
+                self._shares = self._fetch_shares(start, end)
+                if self._shares is None:
+                    self._shares = pd.DataFrame()
+                yfcm.StoreCacheDatum(self.ticker, "shares", self._shares, metadata={'LastFetch':pd.Timestamp.utcnow().tz_convert(tz)})
+                if self._shares.empty:
+                    return None
+            else:
+                return None
+
+        if debug:
+            print("- self._shares:", self._shares.index[0], '->', self._shares.index[-1])
+
+        td_1d = datetime.timedelta(days=1)
+        last_row = self._shares.iloc[-1]
+        if pd.isna(last_row['Shares']):# and last_row['FetchDate'].date() == last_row.name:
+            if debug:
+                print("- dropping last row from cached")
+            self._shares = self._shares.drop(self._shares.index[-1])
+
+        if not isinstance(self._shares.index, pd.DatetimeIndex):
+            self._shares.index = pd.to_datetime(self._shares.index).tz_localize(tz)
+        if self._shares['Shares'].dtype == 'float':
+            # Convert to Int, and add a little to avoid rounding errors
+            self._shares['Shares'] = (self._shares['Shares']+0.01).round().astype('Int64')
+
+        if start < self._shares.index[0].date():
+            df_pre = self._fetch_shares(start, self._shares.index[0])
+            yfcm.WriteCacheMetadata(self.ticker, "shares", 'LastFetch', pd.Timestamp.utcnow().tz_convert(tz))
+            if df_pre is not None:
+                self._shares = pd.concat([df_pre, self._shares])
+        if (end-td_1d) > self._shares.index[-1].date() and \
+            (end - self._shares.index[-1].date()) > max_age:
+            df_post = self._fetch_shares(self._shares.index[-1] + td_1d, end)
+            yfcm.WriteCacheMetadata(self.ticker, "shares", 'LastFetch', pd.Timestamp.utcnow().tz_convert(tz))
+            if df_post is not None:
+                self._shares = pd.concat([self._shares, df_post])
+
+        self._shares = self._shares
+        yfcm.StoreCacheDatum(self.ticker, "shares", self._shares)
+
+        f_na = self._shares['Shares'].isna()
+        shares = self._shares[~f_na]
+        if start is not None:
+            i0 = np.searchsorted(shares.index, start_dt)
+        else:
+            i0 = None
+        if end is not None:
+            i1 = np.searchsorted(shares.index, end_dt)
+        else:
+            i1 = None
+        if i0 is not None and i1 is not None:
+            return shares.iloc[i0:i1]
+        elif i0 is not None:
+            return shares[i0:]
+        elif i1 is not None:
+            return shares[:i1]
+        return shares
+
+    def _fetch_shares(self, start, end):
+        td_1d = datetime.timedelta(days=1)
+
+        exchange, tz = self._getExchangeAndTz()
+        if isinstance(end, datetime.datetime):
+            end_dt = end
+            end_d = end.date()
+        else:
+            end_dt = pd.Timestamp(end).tz_localize(tz)
+            end_d = end
+        if isinstance(start, datetime.datetime):
+            start_dt = start
+            start_d = start.date()
+        else:
+            start_dt = pd.Timestamp(start).tz_localize(tz)
+            start_d = start
+
+        end_d = min(end_d, datetime.date.today() + td_1d)
+
+        df = self.dat.get_shares_full(start_d, end_d)
+        if df is None:
+            return df
+        if df.empty:
+            return None
+
+        # Convert to Pandas Int for NaN support
+        df = df.astype('Int64')
+
+        # Currently, yfinance uses ceil(end), so fix:
+        if df.index[-1].date() == end_d:
+            df.drop(df.index[-1])
+            if df.empty:
+                return None
+
+        fetch_dt = pd.Timestamp.utcnow().tz_convert(tz)
+        df = pd.DataFrame(df, columns=['Shares'])
+
+        if start_d < df.index[0].date():
+            df.loc[start_dt, 'Shares'] = np.nan
+        if (end_d-td_1d) > df.index[-1].date():
+            df.loc[end_dt, 'Shares'] = np.nan
+        df = df.sort_index()
+
+        df['FetchDate'] = fetch_dt
+
+        return df
+
+    @property
+    def major_holders(self):
+        if self._major_holders is not None:
+            return self._major_holders
+
+        if yfcm.IsDatumCached(self.ticker, "major_holders"):
+            self._major_holders = yfcm.ReadCacheDatum(self.ticker, "major_holders")
+            return self._major_holders
+
+        self._major_holders = self.dat.major_holders
+        yfcm.StoreCacheDatum(self.ticker, "major_holders", self._major_holders)
+        return self._major_holders
+
+    @property
+    def institutional_holders(self):
+        if self._institutional_holders is not None:
+            return self._institutional_holders
+
+        if yfcm.IsDatumCached(self.ticker, "institutional_holders"):
+            self._institutional_holders = yfcm.ReadCacheDatum(self.ticker, "institutional_holders")
+            return self._institutional_holders
+
+        self._institutional_holders = self.dat.institutional_holders
+        yfcm.StoreCacheDatum(self.ticker, "institutional_holders", self._institutional_holders)
+        return self._institutional_holders
+
+    @property
+    def earnings(self):
+        return self._financials_manager.get_earnings()
+
+    @property
+    def quarterly_earnings(self):
+        return self._financials_manager.get_quarterly_earnings()
+
+    @property
+    def income_stmt(self):
+        return self._financials_manager.get_income_stmt()
+
+    @property
+    def quarterly_income_stmt(self):
+        return self._financials_manager.get_quarterly_income_stmt()
+
+    @property
+    def financials(self):
+        return self._financials_manager.get_income_stmt()
+
+    @property
+    def quarterly_financials(self):
+        return self._financials_manager.get_quarterly_income_stmt()
+
+    @property
+    def balance_sheet(self):
+        return self._financials_manager.get_balance_sheet()
+
+    @property
+    def quarterly_balance_sheet(self):
+        return self._financials_manager.get_quarterly_balance_sheet()
+
+    @property
+    def cashflow(self):
+        return self._financials_manager.get_cashflow()
+
+    @property
+    def quarterly_cashflow(self):
+        return self._financials_manager.get_quarterly_cashflow()
+
+    def get_earnings_dates(self, limit=12):
+        return self._financials_manager.get_earnings_dates(limit)
+
+    def get_release_dates(self, period='quarterly', as_df=False, check=True):
+        if period not in ['annual', 'quarterly']:
+            raise ValueError(f'period argument must be "annual" or "quarterly", not "{period}"')
+        if period == 'annual':
+            period = yfcd.ReportingPeriod.Full
+        else:
+            period = yfcd.ReportingPeriod.Interim
+
+        releases = self._financials_manager.get_release_dates(period, as_df, refresh=True, check=check)
+        if releases is None:
+            return releases
+
+        if as_df:
+            # Format:
+            releases['Period end uncertainty'] = '0d'
+            f = releases['PE confidence'] == yfcd.Confidence.Medium
+            if f.any():
+                releases.loc[f, 'Period end uncertainty'] = '+-7d'
+            f = releases['PE confidence'] == yfcd.Confidence.Low
+            if f.any():
+                releases.loc[f, 'Period end uncertainty'] = '+-45d'
+            releases = releases.drop('PE confidence', axis=1)
+
+            releases['Release date uncertainty'] = '0d'
+            f = releases['RD confidence'] == yfcd.Confidence.Medium
+            if f.any():
+                releases.loc[f, 'Release date uncertainty'] = '+/-7d'
+            f = releases['RD confidence'] == yfcd.Confidence.Low
+            if f.any():
+                releases.loc[f, 'Release date uncertainty'] = '+/-45d'
+            releases = releases.drop('RD confidence', axis=1)
+
+            releases = releases.drop('Delay', axis=1)
+
+        return releases
+
+    @property
+    def sustainability(self):
+        if self._sustainability is not None:
+            return self._sustainability
+
+        if yfcm.IsDatumCached(self.ticker, "sustainability"):
+            self._sustainability = yfcm.ReadCacheDatum(self.ticker, "sustainability")
+            return self._sustainability
+
+        self._sustainability = self.dat.sustainability
+        yfcm.StoreCacheDatum(self.ticker, "sustainability", self._sustainability)
+        return self._sustainability
+
+    @property
+    def recommendations(self):
+        if self._recommendations is not None:
+            return self._recommendations
+
+        if yfcm.IsDatumCached(self.ticker, "recommendations"):
+            self._recommendations = yfcm.ReadCacheDatum(self.ticker, "recommendations")
+            return self._recommendations
+
+        self._recommendations = self.dat.recommendations
+        yfcm.StoreCacheDatum(self.ticker, "recommendations", self._recommendations)
+        return self._recommendations
+
+    @property
+    def calendar(self):
+        return self._financials_manager.get_calendar()
+
+    @property
+    def inin(self):
+        if self._inin is not None:
+            return self._inin
+
+        if yfcm.IsDatumCached(self.ticker, "inin"):
+            self._inin = yfcm.ReadCacheDatum(self.ticker, "inin")
+            return self._inin
+
+        self._inin = self.dat.inin
+        yfcm.StoreCacheDatum(self.ticker, "inin", self._inin)
+        return self._inin
+
+    @property
+    def options(self):
+        if self._options is not None:
+            return self._options
+
+        if yfcm.IsDatumCached(self.ticker, "options"):
+            self._options = yfcm.ReadCacheDatum(self.ticker, "options")
+            return self._options
+
+        self._options = self.dat.options
+        yfcm.StoreCacheDatum(self.ticker, "options", self._options)
+        return self._options
+
+    @property
+    def news(self):
+        if self._news is not None:
+            return self._news
+
+        if yfcm.IsDatumCached(self.ticker, "news"):
+            self._news = yfcm.ReadCacheDatum(self.ticker, "news")
+            return self._news
+
+        self._news = self.dat.news
+        yfcm.StoreCacheDatum(self.ticker, "news", self._news)
+        return self._news
+
+    @property
+    def yf_lag(self):
+        if self._yf_lag is not None:
+            return self._yf_lag
+
+        exchange, tz_name = self._getExchangeAndTz()
+        exchange_str = "exchange-{0}".format(exchange)
+        if yfcm.IsDatumCached(exchange_str, "yf_lag"):
+            self._yf_lag = yfcm.ReadCacheDatum(exchange_str, "yf_lag")
+            if self._yf_lag:
+                return self._yf_lag
+
+        # Just use specified lag
+        specified_lag = yfcd.exchangeToYfLag[exchange]
+        self._yf_lag = specified_lag
+        return self._yf_lag
+
+

Instance variables

+
+
prop balance_sheet
+
+
+
+ +Expand source code + +
@property
+def balance_sheet(self):
+    return self._financials_manager.get_balance_sheet()
+
+
+
prop calendar
+
+
+
+ +Expand source code + +
@property
+def calendar(self):
+    return self._financials_manager.get_calendar()
+
+
+
prop cashflow
+
+
+
+ +Expand source code + +
@property
+def cashflow(self):
+    return self._financials_manager.get_cashflow()
+
+
+
prop earnings
+
+
+
+ +Expand source code + +
@property
+def earnings(self):
+    return self._financials_manager.get_earnings()
+
+
+
prop fast_info
+
+
+
+ +Expand source code + +
@property
+def fast_info(self):
+    if self._fast_info is not None:
+        return self._fast_info
+
+    if yfcm.IsDatumCached(self.ticker, "fast_info"):
+        try:
+            self._fast_info = yfcm.ReadCacheDatum(self.ticker, "fast_info")
+        except Exception:
+            pass
+        else:
+            return self._fast_info
+
+    # self._fast_info = self.dat.fast_info
+    self._fast_info = {}
+    for k in self.dat.fast_info.keys():
+        try:
+            self._fast_info[k] = self.dat.fast_info[k]
+        except Exception as e:
+            if "decrypt" in str(e):
+                pass
+            else:
+                print(f"TICKER = {self.ticker}")
+                raise
+    yfcm.StoreCacheDatum(self.ticker, "fast_info", self._fast_info)
+
+    yfct.SetExchangeTzName(self._fast_info["exchange"], self._fast_info["timezone"])
+
+    return self._fast_info
+
+
+
prop financials
+
+
+
+ +Expand source code + +
@property
+def financials(self):
+    return self._financials_manager.get_income_stmt()
+
+
+
prop income_stmt
+
+
+
+ +Expand source code + +
@property
+def income_stmt(self):
+    return self._financials_manager.get_income_stmt()
+
+
+
prop info
+
+
+
+ +Expand source code + +
@property
+def info(self):
+    return self.get_info()
+
+
+
prop inin
+
+
+
+ +Expand source code + +
@property
+def inin(self):
+    if self._inin is not None:
+        return self._inin
+
+    if yfcm.IsDatumCached(self.ticker, "inin"):
+        self._inin = yfcm.ReadCacheDatum(self.ticker, "inin")
+        return self._inin
+
+    self._inin = self.dat.inin
+    yfcm.StoreCacheDatum(self.ticker, "inin", self._inin)
+    return self._inin
+
+
+
prop institutional_holders
+
+
+
+ +Expand source code + +
@property
+def institutional_holders(self):
+    if self._institutional_holders is not None:
+        return self._institutional_holders
+
+    if yfcm.IsDatumCached(self.ticker, "institutional_holders"):
+        self._institutional_holders = yfcm.ReadCacheDatum(self.ticker, "institutional_holders")
+        return self._institutional_holders
+
+    self._institutional_holders = self.dat.institutional_holders
+    yfcm.StoreCacheDatum(self.ticker, "institutional_holders", self._institutional_holders)
+    return self._institutional_holders
+
+
+
prop major_holders
+
+
+
+ +Expand source code + +
@property
+def major_holders(self):
+    if self._major_holders is not None:
+        return self._major_holders
+
+    if yfcm.IsDatumCached(self.ticker, "major_holders"):
+        self._major_holders = yfcm.ReadCacheDatum(self.ticker, "major_holders")
+        return self._major_holders
+
+    self._major_holders = self.dat.major_holders
+    yfcm.StoreCacheDatum(self.ticker, "major_holders", self._major_holders)
+    return self._major_holders
+
+
+
prop news
+
+
+
+ +Expand source code + +
@property
+def news(self):
+    if self._news is not None:
+        return self._news
+
+    if yfcm.IsDatumCached(self.ticker, "news"):
+        self._news = yfcm.ReadCacheDatum(self.ticker, "news")
+        return self._news
+
+    self._news = self.dat.news
+    yfcm.StoreCacheDatum(self.ticker, "news", self._news)
+    return self._news
+
+
+
prop options
+
+
+
+ +Expand source code + +
@property
+def options(self):
+    if self._options is not None:
+        return self._options
+
+    if yfcm.IsDatumCached(self.ticker, "options"):
+        self._options = yfcm.ReadCacheDatum(self.ticker, "options")
+        return self._options
+
+    self._options = self.dat.options
+    yfcm.StoreCacheDatum(self.ticker, "options", self._options)
+    return self._options
+
+
+
prop quarterly_balance_sheet
+
+
+
+ +Expand source code + +
@property
+def quarterly_balance_sheet(self):
+    return self._financials_manager.get_quarterly_balance_sheet()
+
+
+
prop quarterly_cashflow
+
+
+
+ +Expand source code + +
@property
+def quarterly_cashflow(self):
+    return self._financials_manager.get_quarterly_cashflow()
+
+
+
prop quarterly_earnings
+
+
+
+ +Expand source code + +
@property
+def quarterly_earnings(self):
+    return self._financials_manager.get_quarterly_earnings()
+
+
+
prop quarterly_financials
+
+
+
+ +Expand source code + +
@property
+def quarterly_financials(self):
+    return self._financials_manager.get_quarterly_income_stmt()
+
+
+
prop quarterly_income_stmt
+
+
+
+ +Expand source code + +
@property
+def quarterly_income_stmt(self):
+    return self._financials_manager.get_quarterly_income_stmt()
+
+
+
prop recommendations
+
+
+
+ +Expand source code + +
@property
+def recommendations(self):
+    if self._recommendations is not None:
+        return self._recommendations
+
+    if yfcm.IsDatumCached(self.ticker, "recommendations"):
+        self._recommendations = yfcm.ReadCacheDatum(self.ticker, "recommendations")
+        return self._recommendations
+
+    self._recommendations = self.dat.recommendations
+    yfcm.StoreCacheDatum(self.ticker, "recommendations", self._recommendations)
+    return self._recommendations
+
+
+
prop splits
+
+
+
+ +Expand source code + +
@property
+def splits(self):
+    if self._splits is not None:
+        return self._splits
+
+    if yfcm.IsDatumCached(self.ticker, "splits"):
+        self._splits = yfcm.ReadCacheDatum(self.ticker, "splits")
+        return self._splits
+
+    self._splits = self.dat.splits
+    yfcm.StoreCacheDatum(self.ticker, "splits", self._splits)
+    return self._splits
+
+
+
prop sustainability
+
+
+
+ +Expand source code + +
@property
+def sustainability(self):
+    if self._sustainability is not None:
+        return self._sustainability
+
+    if yfcm.IsDatumCached(self.ticker, "sustainability"):
+        self._sustainability = yfcm.ReadCacheDatum(self.ticker, "sustainability")
+        return self._sustainability
+
+    self._sustainability = self.dat.sustainability
+    yfcm.StoreCacheDatum(self.ticker, "sustainability", self._sustainability)
+    return self._sustainability
+
+
+
prop yf_lag
+
+
+
+ +Expand source code + +
@property
+def yf_lag(self):
+    if self._yf_lag is not None:
+        return self._yf_lag
+
+    exchange, tz_name = self._getExchangeAndTz()
+    exchange_str = "exchange-{0}".format(exchange)
+    if yfcm.IsDatumCached(exchange_str, "yf_lag"):
+        self._yf_lag = yfcm.ReadCacheDatum(exchange_str, "yf_lag")
+        if self._yf_lag:
+            return self._yf_lag
+
+    # Just use specified lag
+    specified_lag = yfcd.exchangeToYfLag[exchange]
+    self._yf_lag = specified_lag
+    return self._yf_lag
+
+
+
+

Methods

+
+
+def get_earnings_dates(self, limit=12) +
+
+
+
+
+def get_info(self, max_age=None) +
+
+
+
+
+def get_release_dates(self, period='quarterly', as_df=False, check=True) +
+
+
+
+
+def get_shares(self, start=None, end=None, max_age='30d') +
+
+
+
+
+def history(self, interval='1d', max_age=None, period=None, start=None, end=None, prepost=False, actions=True, adjust_splits=True, adjust_divs=True, keepna=False, proxy=None, rounding=False, debug=True, quiet=False, trigger_at_market_close=False) +
+
+

Fetch historical market data for this ticker.

+

This method retrieves historical price and volume data, as well as information +about stock splits and dividend payments if requested.

+

Args

+
+
interval : str, optional
+
Data interval. Defaults to "1d" (daily). +Other options: "1m" (minute), "5m", "15m", "30m", "60m", "90m", "1h", "1d", "5d", "1wk", "1mo", "3mo".
+
max_age : int, optional
+
Maximum age of cached data in seconds. Defaults to None (half of the interval).
+
period : str, optional
+
Data period to download. Defaults to None. +Options: "1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y", "10y", "ytd", "max".
+
start : str, optional
+
Download start date string (YYYY-MM-DD) or datetime. Defaults to None.
+
end : str, optional
+
Download end date string (YYYY-MM-DD) or datetime. Defaults to None.
+
prepost : bool, optional
+
Include pre and post market data. Defaults to False.
+
actions : bool, optional
+
Include stock splits and dividend data. Defaults to True.
+
adjust_splits : bool, optional
+
Adjust data for stock splits. Defaults to True.
+
adjust_divs : bool, optional
+
Adjust data for dividends. Defaults to True.
+
keepna : bool, optional
+
Keep NaN values. Defaults to False.
+
proxy : str, optional
+
Proxy URL. Defaults to None.
+
rounding : bool, optional
+
Round values to 2 decimal places. Defaults to False.
+
debug : bool, optional
+
Print debug messages. Defaults to True.
+
quiet : bool, optional
+
Suppress output messages. Defaults to False.
+
trigger_at_market_close : bool, optional
+
Trigger requests at market close. Defaults to False.
+
+

Returns

+
+
pandas.DataFrame
+
A DataFrame containing the historical data. Columns typically include: +Date, Open, High, Low, Close, Volume, Dividends, Stock Splits.
+
+

Raises

+
+
ValueError
+
If invalid date parameters are provided.
+
YFinanceException
+
If there's an error fetching the data from Yahoo Finance.
+
+

Note

+

Either 'period' or 'start' and 'end' should be provided, but not both.

+
+
+def verify_cached_prices(self, rtol=0.0001, vol_rtol=0.005, correct='none', discard_old=False, quiet=True, debug=False, debug_interval=None) +
+
+
+
+
+
+
diff --git a/yfinance_cache/__init__.py b/yfinance_cache/__init__.py index 75ebe32..5b30161 100644 --- a/yfinance_cache/__init__.py +++ b/yfinance_cache/__init__.py @@ -6,6 +6,20 @@ from .yfc_logging import EnableLogging, DisableLogging from .yfc_cache_manager import _option_manager as options +# Define the public API +__all__ = [ + 'Period', + 'Interval', + 'AmbiguousComparisonException', + 'Ticker', + 'verify_cached_tickers_prices', + 'download', + 'EnableLogging', + 'DisableLogging', + 'options' +] + +# Upgrade the data in cache, e.g. fix errors, update structures: from .yfc_upgrade import _init_options, _reset_cached_cals _init_options() _reset_cached_cals()