Skip to content

yahoo

Daily history and split events from Yahoo Finance, for the adjustment factor builder.

One request per ticker: Ticker.history(period="max", auto_adjust=False, actions=True). With auto_adjust=False, Yahoo's Close is adjusted for splits, bonuses, demergers and rights issues but not for dividends, and Stock Splits lists the split and bonus events Yahoo knows of - 2.0 for a two-for-one split or a 1:1 bonus, 0.3 for a consolidation. Demergers are never listed; they show only in how Close moves against the raw price, which is what factors measures.

Tickers are tried in order: NSE instruments as SYMBOL.NS; BSE instruments as SYMBOL.BO, then as the BSE scrip code, 500325.BO, then - for a company also listed on NSE - as SYMBOL.NS. Yahoo keeps many BSE tickers only as stubs: RELIANCE.BO returns two rows, and PIDILITIND.BO and 500331.BO one and none. A form that does not cover the raw history is passed over for the next.

Dates are read straight off the index, without converting time zones: .NS and .BO histories are labelled Asia/Kolkata, but scrip code tickers come back labelled America/New_York while still carrying Indian trading dates, and converting them would move every bar back a day.

Yahoo rate limits, so requests are paced at roughly one a second with jitter, and a rate limit answer backs off rather than pressing on.

Classes:

Name Description
YahooClient

Paced access to Yahoo Finance daily history.

Functions:

Name Description
ticker_forms

The Yahoo tickers to try for an instrument, in order.

coverage

The share of raw trading days a Yahoo history has a close for.

YahooClient

YahooClient()

Paced access to Yahoo Finance daily history.

Attributes:

Name Type Description
last_request float

When the last request was sent, on the monotonic clock.

Build a client; nothing is requested until asked.

Returns:

Type Description
None

This function returns nothing.

Example programs

examples/stock_brokers/instruments/historical/utilities/unified/yahoo/YahooClient/example_1_reliance_history_and_its_bonus.py
"""Reads RELIANCE's daily history from Yahoo Finance through `YahooClient`, and finds its 1:1 bonus among the listed events.

`YahooClient.history` asks yfinance for one ticker's whole daily history, with prices not adjusted for dividends and with corporate actions included, and returns two dictionaries: Yahoo's close by date, and the ratio of every split or bonus Yahoo lists, by its ex-date. RELIANCE went ex for a 1:1 bonus on 2024-10-28, which Yahoo lists as a ratio of 2.0. Days without an event carry 0 in the `Stock Splits` column and are left out, and a day with no close is left out of the closes.

The example runner routes all HTTP through a dead proxy, and Yahoo must never be contacted from here, so the program replaces the `yf` module inside `yahoo` with a stand-in whose `Ticker(...).history(...)` returns a small pandas frame shaped like the one yfinance returns, labelled Asia/Kolkata as Yahoo labels `.NS` tickers. The stand-in also records the arguments it was asked with. Before the request, `pace` spaces requests about a second apart; the client has sent nothing yet, so it does not wait, and the program shows only that `last_request` moved on, never a duration. It then sets `last_request` back to 0.0, which tells the client its last request was long ago, so the history call does not wait the second that `pace` would otherwise insert after the one just shown; a real caller leaves the attribute alone.

Run it from the project root:

    python examples/stock_brokers/instruments/historical/utilities/unified/yahoo/YahooClient/example_1_reliance_history_and_its_bonus.py
"""

import pandas

from stock_brokers.instruments.historical.utilities.unified import yahoo
from stock_brokers.instruments.historical.utilities.unified.yahoo import (
    YahooClient,
)


class StandInTicker:
    """A stand-in for `yfinance.Ticker` that returns a recorded-looking daily frame.

    Attributes:
        symbol (str): The ticker asked for.
        requests (list): Where each history request's arguments are recorded.
    """

    def __init__(self, symbol, requests):
        """Remembers the ticker and where to record requests.

        Args:
            symbol (str): The ticker asked for.
            requests (list): Where each history request's arguments are recorded.

        Returns:
            None: This method returns nothing.
        """
        self.symbol = symbol
        self.requests = requests

    def history(self, period, auto_adjust, actions):
        """Returns five days of RELIANCE around its bonus.

        Args:
            period (str): How much history to return.
            auto_adjust (bool): Whether to adjust prices for dividends.
            actions (bool): Whether to include dividends and splits.

        Returns:
            pandas.DataFrame: Rows indexed by date, with Close, Dividends and Stock Splits columns.
        """
        self.requests.append(f'{self.symbol} period={period} auto_adjust={auto_adjust} actions={actions}')
        dates = pandas.to_datetime(
            [
                '2024-10-23',
                '2024-10-24',
                '2024-10-25',
                '2024-10-28',
                '2024-10-29',
            ]
        )
        frame = pandas.DataFrame(
            {
                'Close': [
                    1370.35,
                    1360.9,
                    1331.4,
                    1333.3,
                    float('nan'),
                ],
                'Dividends': [
                    0.0,
                    0.0,
                    0.0,
                    0.0,
                    0.0,
                ],
                'Stock Splits': [
                    0.0,
                    0.0,
                    0.0,
                    2.0,
                    0.0,
                ],
            },
            index=dates.tz_localize('Asia/Kolkata'),
        )
        return frame


class StandInYfinance:
    """A stand-in for the `yfinance` module, offering only `Ticker`.

    Attributes:
        requests (list): Every history request made through it.
    """

    def __init__(self):
        """Starts with no requests.

        Returns:
            None: This method returns nothing.
        """
        self.requests = []

    def Ticker(self, symbol):
        """Builds a stand-in ticker, under the name yfinance uses.

        Args:
            symbol (str): The Yahoo ticker.

        Returns:
            StandInTicker: The ticker.
        """
        return StandInTicker(symbol, self.requests)


class RelianceHistoryExample:
    """Reads RELIANCE.NS through the stand-in and prints the closes and events.

    Attributes:
        yfinance (StandInYfinance): The stand-in yfinance module.
        client (YahooClient): The client being shown.
    """

    def __init__(self):
        """Installs the stand-in and builds the client.

        Returns:
            None: This method returns nothing.
        """
        self.yfinance = StandInYfinance()
        yahoo.yf = self.yfinance
        self.client = YahooClient()

    def run(self):
        """Paces once, reads the history and prints it.

        Returns:
            None: This method returns nothing.
        """
        print(f'last_request before any request: {self.client.last_request}')
        self.client.pace()
        print(f'last_request moved on after pace: {self.client.last_request > 0}')
        self.client.last_request = 0.0
        closes, events = self.client.history('RELIANCE.NS')
        print(f'Requests sent: {self.yfinance.requests}')
        for day, close in closes.items():
            print(f'  {day} close {close}')
        for day, ratio in events.items():
            print(f'Listed event: {day} ratio {ratio}')


if __name__ == '__main__':
    RelianceHistoryExample().run()

It prints:

last_request before any request: 0.0
last_request moved on after pace: True
Requests sent: ['RELIANCE.NS period=max auto_adjust=False actions=True']
  2024-10-23 close 1370.35
  2024-10-24 close 1360.9
  2024-10-25 close 1331.4
  2024-10-28 close 1333.3
Listed event: 2024-10-28 ratio 2.0
examples/stock_brokers/instruments/historical/utilities/unified/yahoo/YahooClient/example_2_trying_ticker_forms_for_a_bse_share.py
"""Tries the Yahoo ticker forms for a BSE share in order, passing over a stub, the way the factor builder does.

Yahoo keeps many BSE tickers only as stubs: RELIANCE.BO returns two rows. So a BSE instrument is tried as SYMBOL.BO first, then as its BSE scrip code, 500325.BO, and the first form whose closes cover at least 80% of the stored raw trading days is used. `ticker_forms` lists the forms and `coverage` measures each one; `YahooClient.history` fetches them. A ticker Yahoo has nothing for comes back as an empty frame, which `history` turns into two empty dictionaries.

Scrip code tickers come back labelled America/New_York while still carrying Indian trading dates, so `history` reads the date straight off each timestamp rather than converting it; the output shows 2024-10-28 staying 2024-10-28. The example runner routes all HTTP through a dead proxy, and Yahoo must never be contacted from here, so the program replaces the `yf` module inside `yahoo` with a stand-in whose `Ticker(...).history(...)` returns small pandas frames shaped like yfinance's. Before each request it sets the client's `last_request` back to 0.0, which tells the client its last request was long ago, so `pace` does not wait the second it would normally insert between requests; a real caller leaves the attribute alone.

Run it from the project root:

    python examples/stock_brokers/instruments/historical/utilities/unified/yahoo/YahooClient/example_2_trying_ticker_forms_for_a_bse_share.py
"""

import datetime

import pandas

from stock_brokers.instruments.historical.utilities.unified import yahoo
from stock_brokers.instruments.historical.utilities.unified.yahoo import (
    YahooClient,
)


class StandInTicker:
    """A stand-in for `yfinance.Ticker` that returns a recorded-looking frame per ticker.

    Attributes:
        symbol (str): The ticker asked for.
        requests (list): Where each history request's arguments are recorded.
    """

    def __init__(self, symbol, requests):
        """Remembers the ticker and where to record requests.

        Args:
            symbol (str): The ticker asked for.
            requests (list): Where each history request's arguments are recorded.

        Returns:
            None: This method returns nothing.
        """
        self.symbol = symbol
        self.requests = requests

    def history(self, period, auto_adjust, actions):
        """Returns the frame recorded for this ticker.

        Args:
            period (str): How much history to return.
            auto_adjust (bool): Whether to adjust prices for dividends.
            actions (bool): Whether to include dividends and splits.

        Returns:
            pandas.DataFrame: Rows indexed by date, with Close and Stock Splits columns, empty when Yahoo has nothing.
        """
        self.requests.append(self.symbol)
        if self.symbol == 'RELIANCE.BO':
            return self.frame(
                [
                    '2024-10-28',
                    '2024-10-29',
                ],
                'Asia/Kolkata',
            )
        if self.symbol == '500325.BO':
            return self.frame(
                [
                    '2024-10-22',
                    '2024-10-23',
                    '2024-10-24',
                    '2024-10-25',
                    '2024-10-28',
                ],
                'America/New_York',
            )
        return pandas.DataFrame()

    def frame(self, dates, timezone_name):
        """Builds a frame with a steady close and a 1:1 bonus on 2024-10-28.

        Args:
            dates (list): The trading dates, as ISO strings.
            timezone_name (str): The time zone Yahoo labels the index with.

        Returns:
            pandas.DataFrame: The frame.
        """
        closes = []
        splits = []
        for date_text in dates:
            closes.append(1333.3)
            if date_text == '2024-10-28':
                splits.append(2.0)
            else:
                splits.append(0.0)
        index = pandas.to_datetime(dates).tz_localize(timezone_name)
        return pandas.DataFrame(
            {
                'Close': closes,
                'Stock Splits': splits,
            },
            index=index,
        )


class StandInYfinance:
    """A stand-in for the `yfinance` module, offering only `Ticker`.

    Attributes:
        requests (list): Every history request made through it.
    """

    def __init__(self):
        """Starts with no requests.

        Returns:
            None: This method returns nothing.
        """
        self.requests = []

    def Ticker(self, symbol):
        """Builds a stand-in ticker, under the name yfinance uses.

        Args:
            symbol (str): The Yahoo ticker.

        Returns:
            StandInTicker: The ticker.
        """
        return StandInTicker(symbol, self.requests)


class TryingTickerFormsExample:
    """Tries each ticker form for RELIANCE on BSE and keeps the first that covers the raw history.

    Attributes:
        yfinance (StandInYfinance): The stand-in yfinance module.
        client (YahooClient): The client being shown.
        raw_days (list): The dates the stored raw bars have.
    """

    def __init__(self):
        """Installs the stand-in and builds the client.

        Returns:
            None: This method returns nothing.
        """
        self.yfinance = StandInYfinance()
        yahoo.yf = self.yfinance
        self.client = YahooClient()
        self.raw_days = [
            datetime.date(2024, 10, 22),
            datetime.date(2024, 10, 23),
            datetime.date(2024, 10, 24),
            datetime.date(2024, 10, 25),
            datetime.date(2024, 10, 28),
        ]

    def run(self):
        """Walks the forms and prints each one's coverage.

        Returns:
            None: This method returns nothing.
        """
        forms = yahoo.ticker_forms('bse', 'RELIANCE', '500325', listed_on_nse=True)
        print(f'Forms to try: {forms}')
        for ticker, kind in forms:
            self.client.last_request = 0.0
            closes, events = self.client.history(ticker)
            share = yahoo.coverage(closes, self.raw_days)
            print(f'{ticker} ({kind}): {len(closes)} closes, coverage {share:.1f}, events {events}')
            if share >= yahoo.MINIMUM_COVERAGE:
                print(f'Using {ticker}')
                break
        self.client.last_request = 0.0
        closes, events = self.client.history('NOSUCHSHARE.BO')
        print(f'A ticker Yahoo has nothing for: closes {closes}, events {events}')
        print(f'Requests sent: {self.yfinance.requests}')

if __name__ == '__main__':
    TryingTickerFormsExample().run()

It prints:

Forms to try: [('RELIANCE.BO', 'ticker'), ('500325.BO', 'scrip_code'), ('RELIANCE.NS', 'nse_ticker')]
RELIANCE.BO (ticker): 2 closes, coverage 0.2, events {datetime.date(2024, 10, 28): 2.0}
500325.BO (scrip_code): 5 closes, coverage 1.0, events {datetime.date(2024, 10, 28): 2.0}
Using 500325.BO
A ticker Yahoo has nothing for: closes {}, events {}
Requests sent: ['RELIANCE.BO', '500325.BO', 'NOSUCHSHARE.BO']

Methods:

Name Description
pace

Sleep until the next request is allowed.

history

One ticker's full daily history.

Source code in stock_brokers/instruments/historical/utilities/unified/yahoo.py
def __init__(self):
    """
    Build a client; nothing is requested until asked.

    Returns:
        None: This function returns nothing.
    """
    self.last_request = 0.0

pace

pace()

Sleep until the next request is allowed.

Returns:

Type Description
None

This function returns nothing.

Example usage

From example_1_reliance_history_and_its_bonus.py
self.client.pace()
Source code in stock_brokers/instruments/historical/utilities/unified/yahoo.py
def pace(self):
    """
    Sleep until the next request is allowed.

    Returns:
        None: This function returns nothing.
    """
    wait = PACE_SECONDS + random.uniform(0, 0.5) - (time.monotonic() - self.last_request)
    if wait > 0:
        time.sleep(wait)
    self.last_request = time.monotonic()

history

history(ticker)

One ticker's full daily history.

Parameters:

Name Type Description Default
ticker str

A Yahoo ticker, for example "RELIANCE.NS".

required

Returns:

Type Description
tuple

(closes, events) - closes maps datetime.date to Yahoo's Close, events maps the date of each listed split or bonus to its ratio. Both are empty when Yahoo has nothing.

Example usage

From example_1_reliance_history_and_its_bonus.py
closes, events = self.client.history('RELIANCE.NS')
From example_2_trying_ticker_forms_for_a_bse_share.py
closes, events = self.client.history('NOSUCHSHARE.BO')
From example_2_trying_ticker_forms_for_a_bse_share.py
closes, events = self.client.history(ticker)
Source code in stock_brokers/instruments/historical/utilities/unified/yahoo.py
def history(self, ticker):
    """
    One ticker's full daily history.

    Args:
        ticker (str): A Yahoo ticker, for example "RELIANCE.NS".

    Returns:
        tuple: (closes, events) - closes maps datetime.date to Yahoo's Close, events maps the
            date of each listed split or bonus to its ratio. Both are empty when Yahoo has nothing.
    """
    backoff = RATE_LIMIT_BACKOFF_SECONDS
    for attempt in range(1, RATE_LIMIT_ATTEMPTS + 1):
        self.pace()
        try:
            frame = yf.Ticker(ticker).history(period="max", auto_adjust=False, actions=True)
            break
        except YFRateLimitError:
            if attempt == RATE_LIMIT_ATTEMPTS:
                raise
            LOGGER.warning("Yahoo rate limited on %s, waiting %ds", ticker, backoff)
            time.sleep(backoff)
            backoff *= 2

    closes = {}
    events = {}
    if frame is None or frame.empty:
        return closes, events
    splits = frame["Stock Splits"] if "Stock Splits" in frame else pd.Series(0.0, index=frame.index)
    for moment, close, split in zip(frame.index, frame["Close"], splits):
        day = moment.date()
        if pd.notna(close) and close > 0:
            closes[day] = float(close)
        if pd.notna(split) and split not in (0, 1):
            events[day] = float(split)
    return closes, events

ticker_forms

ticker_forms(exchange, symbol, scrip_code, listed_on_nse=False)

The Yahoo tickers to try for an instrument, in order.

Parameters:

Name Type Description Default
exchange str

"nse" or "bse".

required
symbol str

The exchange trading symbol.

required
scrip_code str | None

The BSE scrip code, for BSE instruments.

required
listed_on_nse bool

Whether a BSE instrument's symbol is also listed on NSE.

False

Returns:

Type Description
list[tuple[str, str]]

(ticker, kind) pairs, kind being "ticker", "scrip_code" or "nse_ticker".

Source code in stock_brokers/instruments/historical/utilities/unified/yahoo.py
def ticker_forms(exchange, symbol, scrip_code, listed_on_nse=False):
    """
    The Yahoo tickers to try for an instrument, in order.

    Args:
        exchange (str): "nse" or "bse".
        symbol (str): The exchange trading symbol.
        scrip_code (str | None): The BSE scrip code, for BSE instruments.
        listed_on_nse (bool): Whether a BSE instrument's symbol is also listed on NSE.

    Returns:
        list[tuple[str, str]]: (ticker, kind) pairs, kind being "ticker", "scrip_code" or "nse_ticker".
    """
    if exchange == "nse":
        return [(f"{symbol}.NS", "ticker")]
    forms = [(f"{symbol}.BO", "ticker")]
    if scrip_code:
        forms.append((f"{scrip_code}.BO", "scrip_code"))
    if listed_on_nse:
        # Yahoo keeps some BSE tickers only as stubs; the company's actions are the same on NSE.
        forms.append((f"{symbol}.NS", "nse_ticker"))
    return forms

coverage

coverage(closes, raw_days)

The share of raw trading days a Yahoo history has a close for.

Parameters:

Name Type Description Default
closes dict

Yahoo closes by date.

required
raw_days list[date]

The raw bars' dates.

required

Returns:

Type Description
float

Between 0 and 1.

Source code in stock_brokers/instruments/historical/utilities/unified/yahoo.py
def coverage(closes, raw_days):
    """
    The share of raw trading days a Yahoo history has a close for.

    Args:
        closes (dict): Yahoo closes by date.
        raw_days (list[datetime.date]): The raw bars' dates.

    Returns:
        float: Between 0 and 1.
    """
    if not raw_days:
        return 0.0
    return sum(1 for day in raw_days if day in closes) / len(raw_days)