# -*- coding: utf-8 -*- from pandas import DataFrame from pandas_ta import Imports, RATE, version from ._core import _camelCase2Title from ._time import ytd_df def av(ticker: str, **kwargs): print(f"[!] kwargs: {kwargs}") verbose = kwargs.pop("verbose", False) kind = kwargs.pop("kind", "history") kind = kind.lower() interval = kwargs.pop("interval", "D") show = kwargs.pop("show", None) # last = kwargs.pop("last", RATE["TRADING_DAYS_PER_YEAR"]) ticker = ticker.upper() if ticker is not None and isinstance(ticker, str) else None if Imports["alphaVantage-api"] and ticker is not None: # from alphaVantageAPI import alphavantage import alphaVantageAPI as AV AVC = {"api_key": "YOUR API KEY", "clean": True, "export": False, "output_size": "full", "premium": False} _config = kwargs.pop("av_kwargs", AVC) av = AV.AlphaVantage(**_config) period = kwargs.pop("period", av.output_size) _all, div = ["all"], "=" * 53 # Max div width is 80 if kind in _all or verbose: pass if kind in _all + ["history", "h"]: if verbose: print("\n==== Chart History " + div + f"\n[*] Pandas TA v{version} & alphaVantage-api") print(f"[+] Downloading {ticker}[{interval}:{period}] from {av.API_NAME} (https://www.alphavantage.co/)") df = av.data(ticker, interval) df.name = ticker if show is not None and isinstance(show, int) and show > 0: print(f"\n{df.name}\n{df.tail(show)}\n") return df return DataFrame() def yf(ticker: str, **kwargs): """yf - yfinance wrapper It retrieves market data (ohlcv) from Yahoo Finance using yfinance. To install yfinance. (pip install yfinance) This method can also pull additional data using the 'kind' kwarg. By default kind=None and retrieves Historical Chart Data. Other options of 'kind' include: * All: "all" - Prints everything below but only returns Chart History to Pandas TA * Company Information: "info" * Institutional Holders: "institutional_holders" or "ih" * Major Holders: "major_holders" or "mh" * Mutual Fund Holders: "mutualfund_holders" or "mfh" * Recommendations (YTD): "recommendations" or "rec" * Earnings Calendar: "calendar" or "cal" * Earnings: "earnings" or "earn" * Sustainability/ESG Scores: "sustainability", "sus" or "esg" * Financials: "financials" or "fin" - Returns in order: Income Statement, Balance Sheet and Cash Flow * Option Chain: "option_chain" or "oc" - Uses the nearest expiration date by default - Change the expiration date using kwarg "exp" - Show ITM options, set kwarg "itm" to True. Or OTM options, set kwarg "itm" to False. * Chart History: - The only data returned to Pandas TA. Args: ticker (str): Any string for a ticker you would use with yfinance. Default: "SPY" Kwargs: calls (bool): When True, prints only Option Calls for the Option Chain. Default: None desc (bool): Will print Company Description when printing Company Information. Default: False exp (str): Used to print other Option Chains for the given Expiration Date. Default: Nearest Expiration Date for the Option Chains interval (str): A yfinance argument. Default: "1d" itm (bool): When printing Option Chains, shows ITM Options when True. When False, it shows OTM Options: Default: None kind (str): Options see above. Default: None period (str): A yfinance argument. Default: "max" proxy (dict): Proxy for yfinance to use. Default: {} puts (bool): When True, prints only Option Puts for the Option Chain. Default: None show (int > 0): How many last rows of Chart History to show. Default: None snd (int): How many recent Splits and Dividends to show in Company Information. Default: 5 verbose (bool): Prints Company Information "info" and a Chart History header to the screen. Default: False Returns: Exits if the DataFrame is empty or None Otherwise it returns a DataFrame of the Chart History """ verbose = kwargs.pop("verbose", False) if ticker is not None and isinstance(ticker, str) and len(ticker): ticker = ticker.upper() else: ticker = "SPY" kind = kwargs.pop("kind", None) if kind is not None and isinstance(kind, str) and len(kind): kind = kind.lower() period = kwargs.pop("period", "max") interval = kwargs.pop("interval", "1d") proxy = kwargs.pop("proxy", {}) show = kwargs.pop("show", None) if not Imports["yfinance"]: print(f"[X] Please install yfinance to use this method. (pip install yfinance)") return if Imports["yfinance"] and ticker is not None: import yfinance as yfra yfra.pdr_override() # Ticker Info & Chart History yfd = yfra.Ticker(ticker) df = yfd.history(period=period, interval=interval, proxy=proxy, **kwargs) if df.empty: return df.name = ticker try: ticker_info = yfd.info except KeyError as ke: print(f"[X] Ticker '{ticker}' not found.") return filtered = {k: v for k, v in ticker_info.items() if v is not None} # print(f"\n{type(ticker_info)}\n{ticker_info}\n{ticker_info.items()}") ticker_info.clear() ticker_info.update(filtered) # Dividends and Splits dividends, splits = yfd.splits, yfd.dividends _all, div = ["all"], "=" * 53 # Max div width is 80 if kind in _all + ["info"] or verbose: description = kwargs.pop("desc", False) snd_length = kwargs.pop("snd", 5) print("\n==== Company Information " + div) ci_header = f"({ticker_info['shortName']}) [{ticker_info['symbol']}]" if "longName" in ticker_info and len(ticker_info["longName"]): print(f"{ticker_info['longName']}" + ci_header) else: print(ci_header) if description: print(f"{ticker_info['longBusinessSummary']}\n") if "address1" in ticker_info and len(ticker_info["address1"]): if "address2" in ticker_info and len(ticker_info["address2"]): print(f"{ticker_info['address1']} {ticker_info['address2']}") else: print(f"{ticker_info['address1']}") if "city" in ticker_info and len(ticker_info["city"]) and "state" in ticker_info and len(ticker_info["state"]) \ and "zip" in ticker_info and len(ticker_info["zip"]) and "country" in ticker_info and len(ticker_info["country"]): print(f"{ticker_info['city']}, {ticker_info['state']} {ticker_info['zip']}, {ticker_info['country']}") else: print(f"{ticker_info['state']} {ticker_info['zip']}, {ticker_info['country']}") print(f"Phone (Fax): {ticker_info['phone']} ({ticker_info['fax'] if 'fax' in ticker_info else 'N/A'})") if "website" in ticker_info and len(ticker_info['website']): s = f"Website: {ticker_info['website']}".ljust(40) if "fullTimeEmployees" in ticker_info: s += f"FT Employees: {ticker_info['fullTimeEmployees']:,}".rjust(40) print(s) elif "fullTimeEmployees" in ticker_info: print(f"FT Employees: {ticker_info['fullTimeEmployees']:,}") if "companyOfficers" in ticker_info and len(ticker_info['companyOfficers']): print(f"Company Officers: {', '.join(ticker_info['companyOfficers'])}".ljust(40)) if "sector" in ticker_info and len(ticker_info["sector"]) and "industry" in ticker_info and len(ticker_info["industry"]): # print(f"Sector: {ticker_info['sector']}".ljust(39), f"Industry: {ticker_info['industry']}".rjust(40)) print(f"Sector | Industry".ljust(29), f"{ticker_info['sector']} | {ticker_info['industry']}".rjust(50)) print("\n==== Market Information " + div) _category = f" | {ticker_info['category']}" if "category" in ticker_info and ticker_info["category"] is not None else "" print( f"Market | Exchange | Symbol{' | Category' if 'category' in ticker_info and ticker_info['category'] is not None else ''}".ljust(39), f"{ticker_info['market'].split('_')[0].upper()} | {ticker_info['exchange']} | {ticker_info['symbol']}{_category}".rjust(40) ) print() if "marketCap" in ticker_info and ticker_info["marketCap"] is not None: print(f"Market Cap.".ljust(39), f"{ticker_info['marketCap']:,} ({ticker_info['marketCap']/1000000:,.2f} MM)".rjust(40)) if "navPrice" in ticker_info and ticker_info["navPrice"] is not None or "yield" in ticker_info and ticker_info["yield"] is not None: print(f"NAV | Yield".ljust(39), f"{ticker_info['navPrice']} | {100 * ticker_info['yield']:.4f}%".rjust(40)) if "sharesOutstanding" in ticker_info and ticker_info["sharesOutstanding"] is not None and "floatShares" in ticker_info and ticker_info["floatShares"] is not None: print(f"Shares Outstanding | Float".ljust(39), f"{ticker_info['sharesOutstanding']:,} | {ticker_info['floatShares']:,}".rjust(40)) if "impliedSharesOutstanding" in ticker_info and ticker_info["impliedSharesOutstanding"] is not None: print(f"Implied Shares Outstanding".ljust(39), f"{ticker_info['impliedSharesOutstanding']:,}".rjust(40)) if "sharesShort" in ticker_info and "shortRatio" in ticker_info and ticker_info["sharesShort"] is not None and ticker_info["shortRatio"] is not None: print(f"Shares Short | Ratio".ljust(39), f"{ticker_info['sharesShort']:,} | {ticker_info['shortRatio']:,}".rjust(40)) if "shortPercentOfFloat" in ticker_info and ticker_info['shortPercentOfFloat'] is not None and "sharesShortPriorMonth" in ticker_info and ticker_info['sharesShortPriorMonth'] is not None: print(f"Short % of Float | Short prior Month".ljust(39), f"{100 * ticker_info['shortPercentOfFloat']:.4f}% | {ticker_info['sharesShortPriorMonth']:,}".rjust(40)) if "heldPercentInstitutions" in ticker_info and ticker_info['heldPercentInstitutions'] is not None or "heldPercentInsiders" in ticker_info and ticker_info['heldPercentInsiders'] is not None: print(f"Insiders % | Institution %".ljust(39), f"{100 * ticker_info['heldPercentInsiders']:.4f}% | {100 * ticker_info['heldPercentInstitutions']:.4f}%".rjust(40)) print() if "bookValue" in ticker_info and ticker_info['bookValue'] is not None or "priceToBook" in ticker_info and ticker_info['priceToBook'] is not None or "pegRatio" in ticker_info and ticker_info['pegRatio'] is not None: print(f"Book Value | Price to Book | Peg Ratio".ljust(39), f"{ticker_info['priceToBook']} | {ticker_info['priceToBook']} | {ticker_info['pegRatio']}".rjust(40)) if "forwardPE" in ticker_info and ticker_info['forwardPE'] is not None: print(f"Forward PE".ljust(39), f"{ticker_info['forwardPE']}".rjust(40)) if "forwardEps" in ticker_info and ticker_info['forwardEps'] is not None or "trailingEps" in ticker_info and ticker_info['trailingEps'] is not None: print(f"Forward EPS | Trailing EPS".ljust(39), f"{ticker_info['forwardEps']} | {ticker_info['trailingEps']}".rjust(40)) if "enterpriseValue" in ticker_info and ticker_info['enterpriseValue'] is not None: print(f"Enterprise Value".ljust(39), f"{ticker_info['enterpriseValue']:,}".rjust(40)) if "enterpriseToRevenue" in ticker_info and ticker_info['enterpriseToRevenue'] is not None or "enterpriseToEbitda" in ticker_info and ticker_info['enterpriseToEbitda'] is not None: print(f"Enterprise to Revenue | to EBITDA".ljust(39), f"{ticker_info['enterpriseToRevenue']} | {ticker_info['enterpriseToEbitda']}".rjust(40)) print() if "netIncomeToCommon" in ticker_info and ticker_info['netIncomeToCommon'] is not None: print(f"Net Income to Common".ljust(39), f"{ticker_info['netIncomeToCommon']:,}".rjust(40)) if "revenueQuarterlyGrowth" in ticker_info and ticker_info['revenueQuarterlyGrowth'] is not None: print(f"Revenue Quarterly Growth".ljust(39), f"{ticker_info['revenueQuarterlyGrowth']}".rjust(40)) if "profitMargins" in ticker_info and ticker_info['profitMargins'] is not None: print(f"Profit Margins".ljust(39), f"{100 * ticker_info['profitMargins']:.4f}%".rjust(40)) if "earningsQuarterlyGrowth" in ticker_info and ticker_info['earningsQuarterlyGrowth'] is not None: print(f"Quarterly Earnings Growth".ljust(39), f"{ticker_info['earningsQuarterlyGrowth']}".rjust(40)) if "annualReportExpenseRatio" in ticker_info and ticker_info['annualReportExpenseRatio'] is not None: print(f"Annual Expense Ratio".ljust(39), f"{ticker_info['annualReportExpenseRatio']}".rjust(40)) print("\n==== Price Information " + div) _o, _h, _l, _c, _v = ticker_info['open'], ticker_info['dayHigh'], ticker_info['dayLow'], ticker_info['regularMarketPrice'], ticker_info['regularMarketVolume'] print(f"Open High Low | Close".ljust(39), f"{_o:.4f} {_o:.4f} {_l:.4f} | {_c:.4f}".rjust(40)) print(f"HL2 | HLC3 | OHLC4 | C - OHLC4".ljust(39), f"{0.5 * (_h + _l):.4f}, {(_h + _l + _c) / 3.:.4f}, {0.25 * (_o + _h + _l + _c):.4f}, {_c - 0.25 * (_o + _h + _l + _c):.4f}".rjust(40)) print(f"Change (%)".ljust(39), f"{_c - ticker_info['previousClose']:.4f} ({100 * ((_c / ticker_info['previousClose']) - 1):.4f}%)".rjust(40)) if "bid" in ticker_info and ticker_info['bid'] is not None \ and "bidSize" in ticker_info and ticker_info['bidSize'] is not None \ and "ask" in ticker_info and ticker_info['ask'] is not None \ and "askSize" in ticker_info and ticker_info['askSize'] is not None: print(f"Bid | Ask | Spread".ljust(39), f"{ticker_info['bid']} x {ticker_info['bidSize']} | {ticker_info['ask']} x {ticker_info['askSize']} | {ticker_info['ask'] - ticker_info['bid']:.4f}".rjust(40)) print(f"Volume | Market | Avg Vol (10Day)".ljust(40)) print(f"{ticker_info['volume']:,} | {_v:,} | {ticker_info['averageVolume']:,} ({ticker_info['averageDailyVolume10Day']:,})".rjust(80)) print() if "52WeekChange" in ticker_info and ticker_info['52WeekChange'] is not None: print(f"52Wk % Change".ljust(39), f"{100 * ticker_info['52WeekChange']:.4f}%".rjust(40)) if "SandP52WeekChange" in ticker_info and ticker_info['SandP52WeekChange'] is not None: print(f"52Wk % Change vs S&P500".ljust(39), f"{100 *ticker_info['SandP52WeekChange']:.4f}%".rjust(40)) if "fiftyTwoWeekHigh" in ticker_info and "fiftyTwoWeekLow" in ticker_info and "previousClose" in ticker_info: # or 'regularMarketPrice' print(f"52Wk Range (% from 52Wk Low)".ljust(39), f"{ticker_info['fiftyTwoWeekLow']} - {ticker_info['fiftyTwoWeekHigh']} : {ticker_info['fiftyTwoWeekHigh'] - ticker_info['fiftyTwoWeekLow']:.4f} ({100 * (ticker_info['regularMarketPrice'] / ticker_info['fiftyTwoWeekLow'] - 1):.4f}%)".rjust(40)) avg50 = "fiftyDayAverage" in ticker_info and ticker_info['fiftyDayAverage'] is not None avg200 = "twoHundredDayAverage" in ticker_info and ticker_info['twoHundredDayAverage'] is not None if avg50 and avg200: print(f"SMA 50 | SMA 200".ljust(39), f"{ticker_info['fiftyDayAverage']:.4f} | {ticker_info['twoHundredDayAverage']:.4f}".rjust(40)) elif avg50: print(f"SMA 50".ljust(39), f"{ticker_info['fiftyDayAverage']:.4f}".rjust(40)) elif avg200: print(f"SMA 200".ljust(39), f"{ticker_info['twoHundredDayAverage']:.4f}".rjust(40)) if "beta" in ticker_info and ticker_info['beta'] is not None and "beta3Year" in ticker_info and ticker_info['beta3Year'] is not None: print(f"Beta | 3Yr".ljust(39), f"{ticker_info['beta']} | {ticker_info['beta3Year']}".rjust(40)) elif "beta" in ticker_info and ticker_info['beta'] is not None: print(f"Beta".ljust(39), f"{ticker_info['beta']}".rjust(40)) if "threeYearAverageReturn" in ticker_info and ticker_info['threeYearAverageReturn'] is not None and "fiveYearAverageReturn" in ticker_info and ticker_info['fiveYearAverageReturn'] is not None: print(f"Avg. Return 3Yr | 5Yr".ljust(39), f"{100 * ticker_info['threeYearAverageReturn']:.4f}% | {100 * ticker_info['fiveYearAverageReturn']:.4f}%".rjust(40)) # Dividends and Splits if not dividends.empty or not splits.empty: print("\n==== Dividends / Splits " + div) if "dividendRate" in ticker_info and ticker_info['dividendRate'] is not None and "dividendYield" in ticker_info and ticker_info['dividendYield'] is not None and "payoutRatio" in ticker_info and ticker_info['payoutRatio'] is not None: print(f"Rate | Yield | Payout Ratio".ljust(39), f"{ticker_info['dividendRate']} | {100 * ticker_info['dividendYield']:.4f}% | {ticker_info['payoutRatio']}".rjust(40)) if "trailingAnnualDividendRate" in ticker_info and ticker_info['trailingAnnualDividendRate'] is not None and "trailingAnnualDividendYield" in ticker_info and ticker_info['trailingAnnualDividendYield'] is not None: print(f"Trailing Annual Dividend Rate | Yield".ljust(40), f"{ticker_info['trailingAnnualDividendRate']} | {100 * ticker_info['trailingAnnualDividendYield']:.4f}%\n".rjust(40)) if not dividends.empty: dividends.name = "Value" total_dividends = dividends.size dividendsdf = DataFrame(dividends.tail(snd_length)[::-1]).T print(f"Dividends (Last {snd_length} of {total_dividends}):\n{dividendsdf}") if not splits.empty: splits.name = "Ratio" total_splits = splits.size splitsdf = DataFrame(splits.tail(snd_length)[::-1]).T print(f"\nStock Splits (Last {snd_length} of {total_splits}):\n{splitsdf}") if kind in _all + ["institutional_holders", "ih"]: ihdf = yfd.institutional_holders if ihdf is not None and "Date Reported" in ihdf.columns: ihdf.set_index("Date Reported", inplace=True) ihdf["Shares"] = ihdf.apply(lambda x: f"{x['Shares']:,}", axis=1) ihdf["Value"] = ihdf.apply(lambda x: f"{x['Value']:,}", axis=1) if kind not in _all: print(f"\n{ticker_info['symbol']}") print("\n==== Instl. Holders " + div + f"\n{ihdf}") if kind in _all + ["major_holders", "mh"]: mhdf = yfd.major_holders if mhdf is not None and "Major Holders" in mhdf.columns: mhdf.columns = ["Percentage", "Major Holders"] mhdf.set_index("Major Holders", inplace=True) mhdf["Shares"] = mhdf.apply(lambda x: f"{x['Shares']:,}", axis=1) mhdf["Value"] = mhdf.apply(lambda x: f"{x['Value']:,}", axis=1) if kind not in _all: print(f"\n{ticker_info['symbol']}") print("\n==== Major Holders " + div + f"\n{mhdf}") if kind in _all + ["mutualfund_holders", "mfh"]: mfhdf = yfd.get_mutualfund_holders() if mfhdf is not None and "Holder" in mfhdf.columns: mfhdf.set_index("Date Reported", inplace=True) mfhdf["Shares"] = mfhdf.apply(lambda x: f"{x['Shares']:,}", axis=1) mfhdf["Value"] = mfhdf.apply(lambda x: f"{x['Value']:,}", axis=1) if kind not in _all: print(f"\n{ticker_info['symbol']}") print("\n==== Mutual Fund Holders " + div + f"\n{mfhdf}") if kind in _all + ["recommendations", "rec"]: recdf = yfd.recommendations if recdf is not None: recdf = ytd_df(recdf) # recdf_grade = recdf["To Grade"].value_counts().T # recdf_grade.name = "Grades" if kind not in _all: print(f"\n{ticker_info['symbol']}") print("\n==== Recommendation(YTD) " + div + f"\n{recdf}") if kind in _all + ["calendar", "cal"]: caldf = yfd.calendar if caldf is not None and "Earnings Date" in caldf.columns: caldf.set_index("Earnings Date", inplace=True) if kind not in _all: print(f"\n{ticker_info['symbol']}") print("\n==== Earnings Calendar " + div + f"\n{caldf}") if kind in _all + ["earnings", "earn"]: earndf = yfd.earnings if not earndf.empty: earndf["Revenue"] = earndf.apply(lambda x: f"{x['Revenue']:,}", axis=1) earndf["Earnings"] = earndf.apply(lambda x: f"{x['Earnings']:,}", axis=1) if kind not in _all: print(f"\n{ticker_info['symbol']}") print("\n==== Earnings " + div + f"\n{earndf}") if kind in _all + ["sustainability", "sus", "esg"]: susdf = yfd.sustainability if susdf is not None: susdf.replace({None: False}, inplace=True) susdf.columns = ["Score"] susdf.drop(susdf[susdf["Score"] == False].index, inplace=True) susdf.rename(index=_camelCase2Title, errors="ignore", inplace=True) susdf.index.name = "Source" if kind not in _all: print(f"\n{ticker_info['symbol']}") print("\n==== Sustainability/ESG " + div + f"\n{susdf}") if kind in _all + ["financials", "fin"]: icdf = yfd.financials bsdf = yfd.balance_sheet cfdf = yfd.cashflow if icdf.empty or bsdf.empty or cfdf.empty: if yfra.__version__ <= "0.1.54": print(f"[!] Best choice: update yfinance to the latest version.") print(f"[!] Ignore if aleady patched. Some tickers do not have financials.") print(f"[!] Otherwise to enable Company Financials, see yfinance Issue #517 patch.") print(f"[!] https://github.com/ranaroussi/yfinance/pull/517/files") else: print("\n==== Company Financials " + div) if not icdf.empty: print(f"Income Statement:\n{icdf}\n") if not bsdf.empty: print(f"Balance Sheet:\n{bsdf}\n") if not cfdf.empty: print(f"Cash Flow:\n{cfdf}\n") if kind in _all + ["option_chain", "oc"]: try: yfd_options = yfd.options except IndexError as ie: yfd_options = None if yfd_options is not None: opt_expirations = list(yfd_options) just_calls = kwargs.pop("calls", None) just_puts = kwargs.pop("puts", None) itm = kwargs.pop("itm", None) opt_date = kwargs.pop("exp", opt_expirations[0]) opt_expirations_str = f"{ticker} Option Expirations:\n\t{', '.join(opt_expirations)}\n" if kind not in _all: print(f"\n{ticker_info['symbol']}") if isinstance(itm, bool) and itm: print("\n==== ITM Option Chains " + div) elif isinstance(itm, bool) and not itm: print("\n==== OTM Option Chains " + div) else: print("\n==== Option Chains " + div) print(opt_expirations_str) if opt_date not in opt_expirations: print(f"[X] No Options for {ticker_info['quoteType']} {ticker_info['symbol']}") else: option_columns = ["Contract", "Last Trade", "Strike", "Price", "Bid", "Ask", "Change", "Percent Change", "Volume", "OI", "IV", "ITM", "Size", "Currency"] cp_chain = yfd.option_chain(proxy=proxy) calls, puts = cp_chain.calls, cp_chain.puts calls.columns = puts.columns = option_columns calls.set_index("Contract", inplace=True) puts.set_index("Contract", inplace=True) calls.name = f"{ticker} Calls for {opt_date}" puts.name = f"{ticker} Puts for {opt_date}" if isinstance(itm, bool): in_or_out = "ITM" if itm else "OTM" calls.name, puts.name = f"{calls.name} {in_or_out}", f"{puts.name} {in_or_out}" itm_calls = f"{calls.name}\n{calls[calls['ITM'] == itm]}" itm_puts = f"{puts.name}\n{puts[puts['ITM'] == itm]}" if just_calls: print(itm_calls) elif just_puts: print(itm_puts) else: print(f"{itm_calls}\n\n{itm_puts}") else: all_calls, all_puts = f"{calls.name}\n{calls}", f"{puts.name}\n{puts}" if just_calls: print(all_calls) elif just_puts: print(all_puts) else: print(f"{all_calls}\n\n{all_puts}") if verbose: print("\n==== Chart History " + div + f"\n[*] Pandas TA v{version} & yfinance v{yfra.__version__}") print(f"[+] Downloading {ticker}[{interval}:{period}] from Yahoo Finance") if show is not None and isinstance(show, int) and show > 0: print(f"\n{df.name}\n{df.tail(show)}\n") if verbose: print("=" * 80 + "\n") # else: print() return df else: return DataFrame()