mirror of
https://github.com/wassname/pandas-ta.git
synced 2026-07-25 13:21:01 +08:00
309 lines
14 KiB
Python
309 lines
14 KiB
Python
import datetime
|
|
|
|
from pandas import DataFrame, DatetimeIndex, merge
|
|
from pandas_ta.maps import Imports, RATE, version
|
|
from pandas_ta.utils import unix_convert
|
|
|
|
|
|
def polygon_api(ticker: str, **kwargs) -> DataFrame:
|
|
r"""
|
|
polygon_api - polygon.io API helper function.
|
|
|
|
It returns OHCLV data from polygon (A valid subscription is required).
|
|
To install the `polygon library <https://github.com/pssolanki111/polygon>`__ ,
|
|
use ``pip install polygon``.
|
|
You can customize the range of data using kwargs ``from_date``,
|
|
``to_date``, ``timespan`` and ``multiplier``. For a description of
|
|
these arguments, see
|
|
`Here <https://polygon.readthedocs.io/en/latest/Stocks.html#get-aggregate-bars-candles>`__
|
|
|
|
To view additional information about the ticker symbols, you can use
|
|
the **kwarg** ``kind``, defaulting to ``None`` which doesn't
|
|
pull/display any additional info.
|
|
|
|
**The function will always return the OHCLV dataframe no matter what
|
|
additional info you ask it to pull.** The additional information is
|
|
used for display only (yet?)
|
|
|
|
Other options for kwarg ``kind`` are as described
|
|
in format: ``value_to_supply: description of that info type``):
|
|
|
|
* ``all`` OR ``info``: Everything below is displayed
|
|
* ``option_chains`` OR ``oc``: Option chains information
|
|
|
|
:param ticker: The ticker symbols of the stock.
|
|
:param \**kwargs:
|
|
Described Below
|
|
|
|
:Keyword Arguments:
|
|
* ``api_key`` - REQUIRED. Your polygon API key.
|
|
Visit your dashboard to get this key.
|
|
* ``show`` - How many last rows of Chart History to show.
|
|
Default: None
|
|
* ``kind`` - Options described above. Default: None
|
|
* ``desc`` - Company description. Default: False
|
|
* ``start_date`` - Start Date for the time range.
|
|
Defaults to roughly a year back. Can be supplied as a
|
|
``datetime`` or ``date`` object or string ``YYYY-MM-DD``
|
|
* ``to_date`` - End date for the time range.
|
|
Defaults to up to most recent data available. Can be supplied as
|
|
a ``datetime`` or ``date`` object or string ``YYYY-MM-DD``
|
|
* ``limit`` - Max number of candles to aggregate.
|
|
Default: 50000 (also the maximum value).
|
|
* ``timespan`` - Type of candles' granularity.
|
|
Default: ``day`` (Daily Candles)
|
|
* ``multiplier`` - Multiplier of granularity. Default: 1.
|
|
Defaults candles are of `1Day` granularity.
|
|
* ``contract_type`` - Can be changed to ``call`` OR ``put``.
|
|
Only applicable when displaying option chains data.
|
|
Default: both
|
|
* ``contract_limit`` - Max number of Contracts to display from
|
|
Option Chains. Default: 10
|
|
* ``verbose`` - Prints Company Information "info" and a Chart History
|
|
header to the screen. Default: False
|
|
"""
|
|
verbose = kwargs.pop("verbose", False)
|
|
kind = kwargs.pop("kind", "nothing").lower()
|
|
api_key = kwargs.pop("api_key", None)
|
|
show = kwargs.pop("show", None)
|
|
# desc = kwargs.pop("desc", False)
|
|
|
|
if api_key is None:
|
|
raise ValueError(
|
|
"Please make sure you pass your polygon api key through kwarg api_key")
|
|
if not Imports["polygon"]:
|
|
raise ValueError(
|
|
"Please install package polygon to use this function (pip install polygon)")
|
|
|
|
if ticker is not None and isinstance(ticker, str):
|
|
ticker = ticker.upper()
|
|
else:
|
|
raise ValueError(
|
|
f"Ticker symbol name must be a valid name string. Eg: \'AMD\'")
|
|
|
|
start_date = kwargs.pop(
|
|
"start_date", (datetime.date.today() - datetime.timedelta(days=525))
|
|
)
|
|
end_date = kwargs.pop("end_date", datetime.date.today())
|
|
limit = kwargs.pop("limit", 50000)
|
|
multiplier = kwargs.pop("multiplier", 1)
|
|
timespan = kwargs.pop("timespan", "day")
|
|
|
|
if not Imports["polygon"]:
|
|
print(f"[X] Please install yfinance to use this method. (pip install yfinance)")
|
|
return
|
|
if Imports["polygon"] and ticker is not None:
|
|
import polygon as polyapi
|
|
|
|
with polyapi.StocksClient(api_key) as polygon_client:
|
|
resp = polygon_client.get_aggregate_bars(
|
|
ticker, start_date, end_date, limit=limit,
|
|
multiplier=multiplier, timespan=timespan
|
|
)
|
|
|
|
df = DataFrame()
|
|
if "results" in resp.keys():
|
|
df = DataFrame.from_dict(resp["results"])
|
|
df = df.set_index(DatetimeIndex(unix_convert(df["t"])))
|
|
df.index.name = "DateTime"
|
|
# reorder then rename
|
|
df = df[["o", "h", "l", "c", "v", "vw", "n"]]
|
|
_columns = {
|
|
"o": "Open", "h": "High", "l": "Low", "c": "Close",
|
|
"v": "Volume", "vw": "VWAP", "n": "Trades"
|
|
}
|
|
df.rename(columns=_columns, errors="ignore", inplace=True)
|
|
|
|
if df.empty:
|
|
print(f"[X] Could not find: {ticker} with 'get_aggregate_bars()'.")
|
|
return
|
|
df.name = ticker
|
|
|
|
# ADDITIONAL DATA FLOW
|
|
ref_client, stock_client = polyapi.ReferenceClient(
|
|
api_key), polyapi.StocksClient(api_key)
|
|
|
|
div = "=" * 53 # Max div width is 80
|
|
# ALL THE INFORMATION
|
|
if kind in ["all", "info"] or verbose:
|
|
print("\n==== Company Information " + div)
|
|
details = ref_client.get_ticker_details(ticker)
|
|
details_vx = ref_client.get_ticker_details_vx(ticker)["results"]
|
|
|
|
has_name = "name" in details_vx and len(details_vx['name'])
|
|
has_ticker = "ticker" in details_vx and len(details_vx['ticker'])
|
|
if not has_ticker:
|
|
details_vx['ticker'] = ticker
|
|
if has_name and has_ticker:
|
|
print(f"{details_vx['name']} [{details_vx['ticker']}]")
|
|
else:
|
|
print(f"{details_vx['ticker']}")
|
|
|
|
# TODO: polygon returns a lot of data for market info across a few endpoints. I don't know which ones to
|
|
# include here lol. I wrote the ones i felt were important. Feel free to suggest more.
|
|
# Yeah. It needs some additional modifications since details and details_vx are
|
|
# not equal and sparse depending on asset of ticker
|
|
|
|
# Common Information
|
|
# print(f"{details['hq_address']}. {details['hq_country']}\nPhone: {details_vx['phone_number']}\n"
|
|
# f"Website: {details['url']} || Employees: {details['employees']}\nSector: {details['sector']} || "
|
|
# f"Industry: {details['industry']}\n\n==== Market Information {div}\n"
|
|
# f"Market: {details_vx['market'].upper()} || locale: {details_vx['locale'].upper()} || "
|
|
# f"Exchange: {details['exchange']} || Symbol: {details['symbol']}\nMarket Shares: "
|
|
# f"{details_vx['market_cap']} || Outstanding Shares:
|
|
# {details_vx['outstanding_shares']}\n")
|
|
has_hq_address = "hq_address" in details and len(
|
|
details['hq_address'])
|
|
has_vx_address = "address" in details_vx and len(
|
|
details_vx["address"])
|
|
if has_hq_address:
|
|
print(
|
|
f"{details['hq_address']}\n{details['hq_state']}, {details['hq_country']}")
|
|
elif has_vx_address:
|
|
has_vx_address1 = "address1" in details_vx['address'] and len(
|
|
details_vx['address']['address1'])
|
|
has_vx_address2 = "address2" in details_vx['address'] and len(
|
|
details_vx['address']['address2'])
|
|
if has_vx_address1 and has_vx_address2:
|
|
print(
|
|
f"{details_vx['address']['address1']}\n{details_vx['address']['address2']}\n{details_vx['address']['city']}, {details_vx['address']['state']} {details_vx['address']['postal_code']}")
|
|
elif has_vx_address1:
|
|
print(
|
|
f"{details_vx['address']['address1']}\n{details_vx['address']['city']}, {details_vx['address']['state']} {details_vx['address']['postal_code']}")
|
|
|
|
has_phone = "phone" in details and len(details['phone'])
|
|
has_vx_phone = "phone_number" in details_vx and len(
|
|
details_vx['phone_number'])
|
|
if has_phone or has_vx_phone:
|
|
_phone = details_vx['phone_number'] or details['phone']
|
|
if len(_phone):
|
|
print(f"Phone: {_phone}")
|
|
|
|
# Market Information
|
|
has_market = "locale" in details_vx and len(details_vx["locale"])
|
|
has_exchange = "primary_exchange" in details_vx and len(
|
|
details_vx["primary_exchange"])
|
|
print("\n==== Market Information " + div)
|
|
if has_market and has_exchange and has_ticker:
|
|
print(
|
|
f"Market | Exchange | Symbol".ljust(39),
|
|
f"{details_vx['locale'].upper()} | {details_vx['primary_exchange']} | {details_vx['ticker']}".rjust(40))
|
|
|
|
print()
|
|
if "market_cap" in details_vx:
|
|
print(
|
|
f"Market Cap.".ljust(39),
|
|
f"{details_vx['market_cap']:,} ({details_vx['market_cap']/1000000:,.2f} MM)".rjust(40))
|
|
if "outstanding_shares" in details_vx:
|
|
print(f"Shares Outstanding".ljust(39),
|
|
f"{details_vx['outstanding_shares']:,}".rjust(40))
|
|
|
|
# Price Info
|
|
snap_res = stock_client.get_snapshot(ticker)
|
|
print(f"\n==== Price Information ==={div}")
|
|
try:
|
|
snap = snap_res["ticker"]
|
|
|
|
# TODO: Convert to DF and print similar to YF
|
|
print(f"\nCurrent Price: {snap['lastTrade']['p']} || Today\'s Change: ${snap_res['todaysChange']} - "
|
|
f"{snap_res['todaysChangePerc']}%\nBid: {snap['lastQuote']['p']} x {snap['lastQuote']['s']} || Ask: "
|
|
f"{snap['lastQuote']['P']} x {snap['lastQuote']['S']} || Spread: "
|
|
f"{round(snap['lastQuote']['P'] - snap['lastQuote']['p'], 4)}\nOpen: {snap['day']['o']} || High: "
|
|
f"{snap['day']['h']} || Low: {snap['day']['l']} || Close: {snap['day']['c']} || Volume: "
|
|
f"{snap['day']['v']} || VWA: {snap['day']['vw']}")
|
|
except KeyError:
|
|
print(f"* Snapshot not found for {ticker}. Can not print price information.\n"
|
|
f"* Note: Snapshot data is cleared at 12am EST and gets populated as data is\n"
|
|
f" received from the exchanges. This can happen as early as 4am EST.\n"
|
|
f'* Requires a "Stocks Starter" subscription')
|
|
|
|
# Splits and Dividends
|
|
# divs, splits = ref_client.get_stock_dividends(ticker), ref_client.get_stock_splits(ticker)
|
|
# # TODO: spits and dividends endpoints from polygon return a huge list. not sure if that entire list is useful
|
|
# print(f"\nNumber of dividends: {divs['count']} || Number of splits: {splits['count']}\n")
|
|
|
|
# TODO: financials endpoint on polygon returns a huge response. I
|
|
# doubt if that's useful to be displayed.
|
|
|
|
# Option Chains
|
|
if kind in ["option_chains", "oc"]:
|
|
_contract_type = kwargs.pop("contract_type", "all").lower()
|
|
contract_type = None if _contract_type == "all" else _contract_type
|
|
contract_limit = kwargs.pop("contract_limit", 10)
|
|
|
|
call_chain = put_chain = None
|
|
if contract_type is None:
|
|
call_chain = ref_client.get_option_contracts(
|
|
ticker, limit=contract_limit,
|
|
contract_type="call"
|
|
)
|
|
|
|
put_chain = ref_client.get_option_contracts(
|
|
ticker, limit=contract_limit,
|
|
contract_type="put"
|
|
)
|
|
else:
|
|
if contract_type == "call":
|
|
call_chain = ref_client.get_option_contracts(
|
|
ticker, limit=contract_limit,
|
|
contract_type="call"
|
|
)
|
|
if contract_type == "put":
|
|
put_chain = ref_client.get_option_contracts(
|
|
ticker, limit=contract_limit,
|
|
contract_type="put"
|
|
)
|
|
|
|
if call_chain is not None or put_chain is not None:
|
|
print(f"\n==== Option Chains {div}")
|
|
|
|
def _cleandf(chain: dict):
|
|
exp_dates = [x["expiration_date"] for x in chain]
|
|
df = DataFrame().from_records(chain)
|
|
df = df[["ticker", "strike_price",
|
|
"expiration_date", "exercise_style"]]
|
|
df.columns = ["Contract", "Strike", "Exp. Date", "Style"]
|
|
df.set_index("Exp. Date", inplace=True)
|
|
return exp_dates, df
|
|
|
|
if call_chain is not None and len(call_chain["results"]):
|
|
exp_dates, calldf = _cleandf(call_chain["results"])
|
|
if contract_type == "call":
|
|
print(f"\n{ticker} Calls for {exp_dates[0]}\n{calldf}")
|
|
|
|
if put_chain is not None and len(put_chain["results"]):
|
|
exp_dates, putdf = _cleandf(put_chain["results"])
|
|
if contract_type == "put":
|
|
print(f"\n{ticker} Puts for {exp_dates[0]}\n{putdf}")
|
|
|
|
if contract_type is None:
|
|
alldf = merge(
|
|
calldf.reset_index(), putdf.reset_index(), on="Strike"
|
|
)
|
|
alldf.rename(
|
|
columns={
|
|
"Contract_x": "Calls",
|
|
"Contract_y": "Puts",
|
|
"Exp. Date_x": "Exp. Date"
|
|
}, inplace=True
|
|
)
|
|
alldf.set_index("Exp. Date", inplace=True)
|
|
alldf = alldf[["Calls", "Strike", "Puts"]]
|
|
print(f"\n{ticker} Calls & Puts for {exp_dates[0]}\n{alldf}")
|
|
else:
|
|
print(f"\nNo option chains data found for {ticker}.")
|
|
|
|
if verbose:
|
|
_chart_history = \
|
|
f"\n==== Chart History " + div + \
|
|
f"\n[*] Pandas TA v{version} & polygon API" + \
|
|
f"\n[+] Downloading {ticker} [{start_date} : {end_date}] from Polygon (www.polygon.io/)\n{'='*80}\n"
|
|
print(_chart_history)
|
|
|
|
if show is not None and isinstance(show, int) and show > 0:
|
|
print(f"\n{df.name}\n{df.tail(show)}\n")
|
|
|
|
return df
|
|
else:
|
|
return DataFrame()
|