Source code for pyEX.stocks.chart

# *****************************************************************************
#
# Copyright (c) 2020, the pyEX authors.
#
# This file is part of the pyEX library, distributed under the terms of
# the Apache License 2.0.  The full license can be found in the LICENSE file.
#
from functools import wraps

import pandas as pd

from ..common import (
    _EST,
    _RANGE_CHART,
    PyEXception,
    _expire,
    _get,
    _quoteSymbols,
    _raiseIfNotStr,
    _reindex,
    _strOrDate,
    _toDatetime,
)


@_expire(hour=4, tz=_EST)
def chart(
    symbol,
    range="1m",
    date=None,
    exactDate=None,
    last=-1,
    closeOnly=False,
    byDay=False,
    simplify=False,
    interval=-1,
    changeFromClose=False,
    displayPercent=False,
    sort="desc",
    includeToday=False,
    token="",
    version="stable",
    filter="",
    format="json",
):
    """Historical price/volume data, daily and intraday

    https://iexcloud.io/docs/api/#historical-prices
    Data Schedule
    1d: -9:30-4pm ET Mon-Fri on regular market trading days
        -9:30-1pm ET on early close trading days
    All others:
        -Prior trading day available after 4am ET Tue-Sat

    Args:
        symbol (str): Ticker to request
        range (str): Range to request e.g. 1m
        date (datetime): date, if requesting intraday
        exactDate (str): Same as `date`, takes precedence
        last (int): If passed, chart data will return the last N elements from the time period defined by the range parameter
        closeOnly (bool): Will return adjusted data only with keys date, close, and volume.
        byDay (bool): Used only when range is date to return OHLCV data instead of minute bar data.
        simplify (bool) If true, runs a polyline simplification using the Douglas-Peucker algorithm. This is useful if plotting sparkline charts.
        interval (int) If passed, chart data will return every Nth element as defined by chartInterval
        changeFromClose (bool): If true, changeOverTime and marketChangeOverTime will be relative to previous day close instead of the first value.
        displayPercent (bool): If set to true, all percentage values will be multiplied by a factor of 100 (Ex: /stock/twtr/chart?displayPercent=true)
        range (str): Same format as the path parameter. This can be used for batch calls.
        sort (str): Can be "asc" or "desc" to sort results by date. Defaults to "desc"
        includeToday (bool): If true, current trading day data is appended
        token (str): Access token
        version (str): API version
        filter (str): filters: https://iexcloud.io/docs/api/#filter-results
        format (str): return format, defaults to json

    Returns:
        dict or DataFrame: result
    """
    _raiseIfNotStr(symbol)

    base_url = "stock/{}/chart/{}?".format(_quoteSymbols(symbol), range)

    # exactDate takes precedence
    date = exactDate or date
    if date:
        date = _strOrDate(date)

    if range is not None and range != "1d":
        if range not in _RANGE_CHART:
            raise PyEXception("Range must be in {}".format(_RANGE_CHART))

    # Assemble params
    params = {}

    # TODO need these?
    # if date:
    #     params["exactDate"] = date
    # if range:
    #     params["range"] = range

    if last > 0:
        params["chartLast"] = last

    if closeOnly:
        params["chartCloseOnly"] = closeOnly

    if byDay:
        params["chartByDay"] = byDay

    if simplify:
        params["chartSimplify"] = simplify

    if interval > 0:
        params["chartInterval"] = interval

    if changeFromClose:
        params["changeFromClose"] = changeFromClose

    if displayPercent:
        params["displayPercent"] = displayPercent

    if exactDate:
        params["exactDate"] = exactDate

    if sort:
        if sort.lower() not in (
            "asc",
            "desc",
        ):
            raise PyEXception("Sort must be in (asc, desc), got: {}".format(sort))

        params["sort"] = sort.lower()

    if includeToday:
        params["includeToday"] = includeToday

    if date:
        base_url = "stock/{}/chart/date/{}?".format(_quoteSymbols(symbol), date)

        if params:
            base_url += "&".join("{}={}".format(k, v) for k, v in params.items())
        return _get(
            base_url, token=token, version=version, filter=filter, format=format
        )

    if params:
        base_url += "&".join("{}={}".format(k, v) for k, v in params.items())

    return _get(base_url, token=token, version=version, filter=filter, format=format)


def _chartToDF(c):
    """internal"""
    return _reindex(_toDatetime(pd.DataFrame(c)), "date")


@wraps(chart)
def chartDF(
    symbol,
    range="1m",
    date=None,
    exactDate=None,
    last=-1,
    closeOnly=False,
    byDay=False,
    simplify=False,
    interval=-1,
    changeFromClose=False,
    displayPercent=False,
    sort="desc",
    includeToday=False,
    token="",
    version="stable",
    filter="",
    format="json",
):
    c = chart(
        symbol=symbol,
        range=range,
        date=date,
        exactDate=exactDate,
        last=last,
        closeOnly=closeOnly,
        byDay=byDay,
        simplify=simplify,
        interval=interval,
        changeFromClose=changeFromClose,
        displayPercent=displayPercent,
        sort=sort,
        includeToday=includeToday,
        token=token,
        version=version,
        filter=filter,
        format=format,
    )
    df = _toDatetime(pd.DataFrame(c))
    if range is not None and range != "1d":
        _reindex(df, "date")
    else:
        if not df.empty and "date" in df.columns and "minute" in df.columns:
            df.set_index(["date", "minute"], inplace=True)
        elif not df.empty and "date" in df.columns:
            _reindex(df, "date")
        elif not df.empty:
            # Nothing to do
            ...
        else:
            df = pd.DataFrame()
    return df