| 
          #!/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®ion=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®ion=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) |