Skip to content

Instantly share code, notes, and snippets.

@robmcelhinney
Last active May 6, 2022 18:22
Show Gist options
  • Save robmcelhinney/505bc0dcb08d3cdd0ce16f4ce787152c to your computer and use it in GitHub Desktop.
Save robmcelhinney/505bc0dcb08d3cdd0ce16f4ce787152c to your computer and use it in GitHub Desktop.
Command line stock tracker using yahoo finance api

TickerTok

Simple command line portfolio tracker

Description

Python script that returns table with stocks/cryptos

Getting Started

Executing program

> python3 tickerTok.py -f ticker.yaml 
> python3 tickerTok.py -w ETH-USD,XM,AAPL

To auto update use watch

> watch -t -c python3 tickerTok.py -f ticker.yaml
> watch -t -c python3 tickerTok.py -f holdings.yaml

Help

Any advise for common problems or issues.

> python3 tickerTok.py -h

License

This project is licensed under the GNU Lesser General Public License v3.0

Acknowledgments

Using the Yahoo Finance API

holdings:
ETH-USD: 32
ENS-USD: 10000
XM: 10000
watchlist:
- ETH-USD
- BTC-USD
- MIOTA-USD
- AAPL
- XM
#!/usr/bin/env python
from rich.console import Console
from rich.table import Table
from rich import box
import sys, argparse, logging
import requests
import math
import time
import yaml
millnames = ['', ' thousand',' m',' b',' t']
def millify(n):
n = float(n)
millidx = max(0, min(len(millnames) - 1,
int(math.floor(0 if n == 0 else math.log10(abs(n))/3))))
return '{:.2f}{}'.format(n / 10**(3 * millidx), millnames[millidx])
API_ENDPOINT="https://query1.finance.yahoo.com/v7/finance/quote?lang=en-US&region=US&corsDomain=finance.yahoo.com"
fields = ["symbol", "marketState", "regularMarketPrice", "regularMarketChange", "regularMarketChangePercent", "displayName", "marketCap"]
# ex = "https://query1.finance.yahoo.com/v7/finance/quote?symbols=AAPL,NFLX"
# fulldata = "https://query1.finance.yahoo.com/v7/finance/quote?lang=en-US&region=US&corsDomain=finance.yahoo.com&symbols=BBBY,GME"
def apiCall(symbols):
fieldParameters = ""
for i in range(0, len(fields)):
fieldParameters += fields[i]
if i != len(fields) - 1:
fieldParameters += ","
symbolParameters = ""
for i in range(0, len(symbols)):
symbolParameters += symbols[i]
if i != len(symbols) - 1:
symbolParameters += ","
# Get a copy of the default headers that requests would use
headers = requests.utils.default_headers()
headers.update(
{
'User-agent': 'Mozilla/5.0',
'From': '[email protected]' # This is another valid field
}
)
url = "{}&fields={}&symbols={}".format(API_ENDPOINT, fieldParameters, symbolParameters)
logging.debug("url: {}".format(url))
r = requests.get(url, headers=headers)
logging.debug("status_code: {}".format(r.status_code))
quoteResponse = r.json()["quoteResponse"]
error = quoteResponse["error"]
if error:
logging.error("WARNING ERROR!!!!")
results = quoteResponse["result"]
return results
def createTable(data, holdings=None):
console = Console()
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
table.add_column("Ticker", width=10)
table.add_column("Name")
table.add_column("Price", justify="right")
table.add_column("Percent", justify="right")
table.add_column("Change", justify="right")
table.add_column("Market Cap", justify="right")
if holdings:
table.add_column("Holdings", justify="right")
table.add_column(":thumbs_up:/:thumbs_down:")
totalHoldings = 0
totalChange = 0
totalChangePercent = 0
for ticker in data:
changePercent = ticker["regularMarketChangePercent"]
change = ticker["regularMarketChange"]
changePercentStr = "{:.2f}".format(changePercent)
changeStr = "{:,.2f}".format(change)
if changePercent > 0:
changePercentStr = "[green3]{}%[green3]".format(changePercentStr)
changeStr = "[green3]{}[green3]".format(changeStr)
elif changePercent < 0:
changePercentStr = "[red1]{}%[red1]".format(changePercentStr.replace("-", ""))
changeStr = "[red1]{}[red1]".format(changeStr.replace("-", ""))
if changePercent > 10:
emoji = ":rocket:"
elif changePercent < -10:
emoji = ":pile_of_poo:"
else:
emoji = ":neutral_face:"
marketCap = millify(ticker["marketCap"])
logging.debug("ticker: {}".format(ticker))
displayNameStr = ticker["symbol"]
if "displayName" in ticker:
displayNameStr = str(ticker["displayName"])
if holdings:
logging.debug("holdings: {}".format(holdings))
holding = float(holdings[ticker["symbol"]])
holdingValue = holding * float(ticker["regularMarketPrice"])
totalHoldings += holdingValue
logging.debug("holdingValue: {}".format(holdingValue))
holdingRow = "{:,.2f}".format(holdingValue)
table.add_row(
ticker["symbol"],
displayNameStr,
"{:.2f}".format(ticker["regularMarketPrice"]),
changePercentStr,
changeStr,
marketCap,
holdingRow,
emoji,
)
totalChange += change * holding
else:
table.add_row(
ticker["symbol"],
displayNameStr,
"{:.2f}".format(ticker["regularMarketPrice"]),
changePercentStr,
changeStr,
marketCap,
emoji,
)
console.print(table)
if holdings:
totalChangePercent = (totalChange / (totalHoldings - totalChange)) * 100
if totalChange >= 0:
console.print("Daily Change: [green3]~${:.2f}[green3]".format(totalChange))
console.print("Daily % Change: [green3]~{:.2f}%[green3]".format(totalChangePercent))
else:
console.print("Daily Change: [red1]~${:.2f}[red1]".format(totalChange))
console.print("Daily % Change: [red1]~{:.2f}%[red1]".format(totalChangePercent))
console.print("Daily Change Percent: ~${:.2f}".format(totalChangePercent))
console.print("Total Holdings: [bold dodger_blue1]~${:.2f}[bold dodger_blue1]".format(totalHoldings))
def refreshDataTable(watchlist, holdings):
data = apiCall(watchlist)
logging.debug("data: {}".format(data))
createTable(data, holdings)
# Gather our code in a main() function
def main(args):
logging.debug("Your Argument: {}".format(args))
if not args.watchlist and not args.file:
logging.error("Must give watchlists as parameter or file")
sys.exit()
watchlist = []
if args.watchlist:
watchlist = (args.watchlist).split(",")
logging.debug("watchlist: {}".format(watchlist))
holdings = None
file = (args.file)
if file:
try:
yamlFile = open(file)
yamlFile = yaml.load(yamlFile, Loader=yaml.FullLoader)
if "watchlist" in yamlFile:
yamlWatchlist = yamlFile["watchlist"]
watchlist = list(set(watchlist + yamlWatchlist))
elif "holdings" in yamlFile:
holdings = yamlFile["holdings"]
watchlist = set(holdings.keys())
except IOError as err:
logging.error("Could not open file {}. Error: {}".format(file, err))
sys.exit()
except yaml.YAMLError as err:
logging.error("Could not load yaml from file {}. Error: {}".format(file, err))
sys.exit()
watchlist = sorted(watchlist)
if (args.interval):
while True:
data = apiCall(watchlist)
logging.debug("data: {}".format(data))
print(chr(27) + "[2J")
createTable(data, holdings)
time.sleep(args.interval)
refreshDataTable(watchlist, holdings)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description = "Tracks stocks using yahoo finance api.",
epilog = "As an alternative to the commandline, params can be placed in a file, one per line, and specified on the commandline like '%(prog)s @params.conf'.",
fromfile_prefix_chars = '@' )
parser.add_argument(
"-w"
"--watchlist",
dest="watchlist",
action="store",
help = "pass watchlist to the program. Comma separated",
default=None)
parser.add_argument(
"-f",
"--file",
dest="file",
action="store",
help="file storing watchlist (yaml)",
default=None)
parser.add_argument(
"-i",
"--interval",
dest="interval",
action="store",
type=int,
help="interval to refresh table",
default=None)
parser.add_argument(
"-v",
"--verbose",
help="increase output verbosity",
action="store_true")
args = parser.parse_args()
# Setup logging
if args.verbose:
loglevel = logging.DEBUG
else:
loglevel = logging.INFO
logging.basicConfig(format="%(levelname)s: %(message)s", level=loglevel)
main(args)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment