matryer / xbar

Put the output from any script or program into your macOS Menu Bar (the BitBar reboot)
https://xbarapp.com
MIT License
17.58k stars 642 forks source link

How to print two rows in the menu bar? #888

Open carlos1172 opened 1 year ago

carlos1172 commented 1 year ago

Is it possible to have xbar print two rows such that the first row is on top of the either?

I'm using a modified version of @longpdo's Yahoo Stock Ticker but when I try to accomplish this, by adding "\n" to the function, it indeed prints a new line, but it cycles through line 1 and line 2 instead of printing them at the same time on top of each other.

carlos1172 commented 1 year ago

image right now it looks like this, and takes up way too much of my menu bar

carlos1172 commented 1 year ago

Here's @longpdo's adapted code

#
# <xbar.title>USD Ticker</xbar.title>
# <xbar.version>v1.1</xbar.version>
# <xbar.author>Long Do</xbar.author>
# <xbar.author.github>longpdo</xbar.author.github>
# <xbar.desc>Shows major stock indices in the menu bar and stock symbols in the dropdown menu by pulling data from the Yahoo Finance API. Similar to finance.yahoo.com the prices are delayed, but no API key is necessary. You can also set price alarms for BUY/SELL limits, which will notify you when the limit is reached.</xbar.desc>
# <xbar.image>https://github.com/longpdo/bitbar-plugins-custom/raw/master/images/yahoo-stock-ticker.png</xbar.image>
# <xbar.dependencies>python3</xbar.dependencies>
# <xbar.abouturl>https://github.com/longpdo/bitbar-plugins-custom/blob/master/README.md#yahoo-stock-ticker</xbar.abouturl>
#
# by longpdo (https://github.com/longpdo)

from datetime import datetime
import json
import os
import re
import sys
import subprocess

import pandas as pd

SHEET_ID = '13MHMYt9obuVuwYjhbq4ZKqNapv8Uw7nsunwBO-zCU_Y'
SHEET_NAME = 'Sheet1'
url = f'https://docs.google.com/spreadsheets/d/{SHEET_ID}/gviz/tq?tqx=out:csv&sheet={SHEET_NAME}'
df = pd.read_csv(url)

# ---------------------------------------------------------------------------------------------------------------------
# Enter your stock symbols here in the format: ["symbol1", "symbol2", ...]
symbols = ["USDPHP=X","SWRD.L","WSML.L","EIMI.L","IMID.L"]

# Enter the order how you want to sort the stock list:
# 'name'                     : Sort alphabetically by name from A to Z
# 'market_change_winners'    : Sort by value from top winners to losers
# 'market_change_losers'     : Sort by value from top losers to winners
# 'market_change_volatility' : Sort by absolute value from top to bottom
# '' or other values         : Sort by your custom order from the symbols array above
sort_by = ''
# ---------------------------------------------------------------------------------------------------------------------

# ---------------------------------------------------------------------------------------------------------------------
# CODE STARTING BELOW HERE, DO NOT EDIT IF YOU ARE A REGULAR USER
# Variables
indices_dict = {
    'USDPHP=X':'USD',
    'SWRD.L':'SWRD',
    'WSML.L':'WSML',
    'EIMI.L':'EIMI',
    'IMID.L':' IMID',
}
GREEN = '\033[32m'
RED = '\033[31m'
RESET = '\033[0m'
WHITE = '\033[97m'
FONT = "| font='Menlo'"

AVECOSTSWRD = float(df.iat[0, 1])
AVECOSTWSML = float(df.iat[1, 1])
AVECOSTEIMI = float(df.iat[2, 1])

AVECOSTVDST = 51.3587

WTSWRD = float(df.iat[0, 0])
WTWSML = float(df.iat[1, 0])
WTEIMI = float(df.iat[2, 0])

IMIDHLD = str(int(df.iat[0, 3]))
IMIDAUM = "$" + str(int(float(df.iat[1, 3])/1000000)) + "M"
THREEFPHLD = str(int(df.iat[0, 4]))
THREEFPAUM = "$" + str(round(float(df.iat[1, 4])/1000000000,1)) + "B"

# Price of SWRD*WT+WSML*WT+EIMI*WT since Inception (4/24/2023)
SWRDSTART = 29.09
WSMLSTART = 6.314
EIMISTART = 29.38
IMIDSTART = 182.43
SWRDNAVSTART = 29.11970
WSMLNAVSTART = 6.32
EIMINAVSTART = 29.39
IMIDNAVSTART = 182.39530
SWRDNAVLAST = round(float(df.iat[1, 6]),2)/SWRDNAVSTART*100
WSMLNAVLAST = round(float(df.iat[1, 7]),2)/WSMLNAVSTART*100
EIMINAVLAST = round(float(df.iat[1, 8]),2)/EIMINAVSTART*100
IMIDNAVLAST = round(float(df.iat[1, 5]),2)/IMIDNAVSTART*100

colorIMIDHLDPct = ''
if round(int(df.iat[0, 3])-int(df.iat[2, 3]),2) > 0:
    colorIMIDHLDPct = GREEN
if round(int(df.iat[0, 3])-int(df.iat[2, 3]),2) < 0:
    colorIMIDHLDPct = RED
IMIDHLDPct = colorIMIDHLDPct + "(" + str(round(int(df.iat[0, 3])-int(df.iat[2, 3]),2)) + ")" + RESET 

colorIMIDAUMPct = ''
if round((float(df.iat[1, 3])-float(df.iat[3, 3]))/float(df.iat[3, 3])*100,2) > 0:
    colorIMIDAUMPct = GREEN
if round((float(df.iat[1, 3])-float(df.iat[3, 3]))/float(df.iat[3, 3])*100,2) < 0:
    colorIMIDAUMPct = RED
IMIDAUMPct = colorIMIDAUMPct + "(" + str(round((float(df.iat[1, 3])-float(df.iat[3, 3]))/float(df.iat[3, 3])*100,2)) + " bps)" + RESET

colorTHREEFPHLDPct = ''
if round(int(df.iat[0, 4])-int(df.iat[2, 4]),2) > 0:
    colorTHREEFPHLDPct = GREEN
if round(int(df.iat[0, 4])-int(df.iat[2, 4]),2) < 0:
    colorTHREEFPHLDPct = RED
THREEFPHLDPct = colorTHREEFPHLDPct + "(" + str(round(int(df.iat[0, 4])-int(df.iat[2, 4]),2)) + ")" + RESET 

colorTHREEFPAUMPct = ''
if round((float(df.iat[1, 4])-float(df.iat[3, 4]))/float(df.iat[3, 4])*100,2) > 0:
    colorTHREEFPAUMPct = GREEN
if round((float(df.iat[1, 4])-float(df.iat[3, 4]))/float(df.iat[3, 4])*100,2) < 0:
    colorTHREEFPAUMPct = RED
THREEFPAUMPct = colorTHREEFPAUMPct + "(" + str(round((float(df.iat[1, 4])-float(df.iat[3, 4]))/float(df.iat[3, 4])*100,2)) + " bps)" + RESET
# ---------------------------------------------------------------------------------------------------------------------

# macOS Alerts, Prompts and Notifications -----------------------------------------------------------------------------
# Display a macOS specific alert dialog to get confirmation from user to continue
def alert(alert_title='', alert_text='', alert_buttons=['Cancel', 'OK']):
    try:
        d = locals()
        user_input = subprocess.check_output(['osascript', '-l', 'JavaScript', '-e', '''
            const app = Application.currentApplication()
            app.includeStandardAdditions = true
            const response = app.displayAlert('{alert_title}', {{
                message: '{alert_text}',
                as: 'critical',
                buttons: ['{alert_buttons[0]}', '{alert_buttons[1]}'],
                defaultButton: '{alert_buttons[1]}',
                cancelButton: '{alert_buttons[0]}'
            }})
            response
        '''.format(**d)]).decode('ascii').rstrip()
        return user_input
    except subprocess.CalledProcessError:
        pass

# Display a macOS specific prompt dialog to get text input from the user
def prompt(prompt_text=''):
    try:
        d = locals()
        user_input = subprocess.check_output(['osascript', '-l', 'JavaScript', '-e', '''
            const app = Application.currentApplication()
            app.includeStandardAdditions = true
            const response = app.displayDialog('{prompt_text}', {{
                defaultAnswer: '',
                buttons: ['Cancel', 'OK'],
                defaultButton: 'OK'
            }})
            response.textReturned
        '''.format(**d)]).decode('ascii').rstrip()
        if user_input == '':
            sys.exit()
        return user_input
    except subprocess.CalledProcessError:
        pass

# Display a macOS specific prompt dialog prompting user for a choice from a list
def prompt_selection(prompt_text='', choices=''):
    try:
        d = locals()
        user_selection = subprocess.check_output(['osascript', '-l', 'JavaScript', '-e', '''
            const app = Application.currentApplication()
            app.includeStandardAdditions = true
            var choices = {choices}
            const response = app.chooseFromList(choices, {{
                withPrompt: '{prompt_text}',
                defaultItems: [choices[0]]
            }})
            response
        '''.format(**d)]).decode('ascii').rstrip()
        if user_selection == 'false':
            sys.exit()
        return user_selection
    except subprocess.CalledProcessError:
        pass

# Display a macOS specific notification
def notify(text, title, subtitle, sound='Glass'):
    cmd = 'osascript -e \'display notification "{}" with title "{}" subtitle "{}" sound name "{}"\''
    os.system(cmd.format(text, title, subtitle, sound))
# ---------------------------------------------------------------------------------------------------------------------

# Methods to read, write, remove data from the hidden .db file --------------------------------------------------------
def read_data_file(data_file):
    with open(data_file, 'r') as f:
        content = f.readlines()
    f.close()
    content = [x.strip() for x in content]
    return content

def write_data_file(data_file, imit_type, symbol, price):
    with open(data_file, 'a') as f:
        f.write(limit_type + ' ' + symbol + ' ' + price + '\n')
    f.close()

def remove_line_from_data_file(data_file, line_to_be_removed):
    with open(data_file, 'r') as f:
        content = f.readlines()
    with open(data_file, 'w') as f:
        for line in content:
            if line.strip('\n') != line_to_be_removed:
                f.write(line)
    f.close()
# ---------------------------------------------------------------------------------------------------------------------

# Curl the yahoo api for data
def get_stock_data(symbol):
    # Building the curl command as a string
    library = 'curl --silent '
    api = 'https://query1.finance.yahoo.com/v7/finance/quote?'
    fields = [
    'symbol', 
    'marketState', 
    'regularMarketTime', 
    'regularMarketPrice', 
    'regularMarketChangePercent',
    'fullExchangeName', 
    'currency', 
    'navPrice', 
    'regularMarketPreviousClose',
    'regularMarketOpen', 
    'bid', 
    'ask',
    'regularMarketDayRange', 
    'fiftyTwoWeekRange', 
    'fiftyDayAverage', 
    'twoHundredDayAverage', 
    'shortName',
    'fiftyDayAverageChangePercent', 
    'twoHundredDayAverageChangePercent'
    ]
    fields_string = 'fields=' + ','.join(fields)
    cmd = library + "'" + api + fields_string + '&symbols=' + symbol + "'"

    # Popen to run the curl command and retrieve the output
    output = os.popen(cmd).read()
    # Jsonify the output from the curl command
    json_output = json.loads(output)

    # Check if a valid symbol was used
    try:
        stock_data = json_output['quoteResponse']['result'][0]
    except IndexError:
        alert('Error', 'Invalid stock symbol: ' + symbol)
        sys.exit()

    return stock_data

# Check a given stock symbol against the price limit list
def check_price_limits(symbol_to_be_checked, current_price, price_limit_list, data_file):
    for limit_entry in price_limit_list:
        if symbol_to_be_checked in limit_entry:
            # Get the limit price, limits are saved in the format: TYPE SYMBOL PRICE
            limit_price = float(limit_entry.split()[2])
            notification_text = 'Current price is: ' + str(current_price)
            notification_title = 'Price Alarm'

            # Notify user if current price is lower than the BUY limit, then remove the limit from list
            if 'BUY' in limit_entry and current_price < limit_price:
                notification_subtitle = 'BUY Limit: ' + str(limit_price)
                notify(notification_text, notification_title, notification_subtitle)
                remove_line_from_data_file(data_file, limit_entry)

            # Notify user if current price is higher than the SELL limit, then remove the limit from list
            if 'SELL' in limit_entry and current_price > limit_price:
                notification_subtitle = 'SELL Limit: ' + str(limit_price)
                notify(notification_text, notification_title, notification_subtitle)
                remove_line_from_data_file(data_file, limit_entry)

# Print the indices information in the menu bar
def print_index(index1, name1, index2, name2, index3, name3, index4, name4, index5, name5):
    market_state1 = index1['marketState']
    current_price1 = index1['regularMarketPrice']
    change1 = index1['regularMarketChangePercent']

    # Setting color1 and emojis depending on the market state and the market change1
    if market_state1 != 'REGULAR':
        # Set color1 for positive and negative values
        color1 = ''
        if change1 > 0:
            color1 = GREEN
        if change1 < 0:
            color1 = RED
        # Set change1 with a moon emoji for closed markets
        colored_change1 = color1 + '{:.2f} '.format(current_price1) + RESET
    if market_state1 == 'REGULAR':
        # Set color1 for positive and negative values
        color1 = ''
        if change1 > 0:
            color1 = GREEN
        if change1 < 0:
            color1 = RED
        # Format change1 to decimal with a precision of two and reset ansi color1 at the end
        colored_change1 = color1 + '{:.2f} '.format(current_price1) + RESET

    market_state2 = index2['marketState']
    current_price2 = index2['regularMarketPrice']
    change2 = index2['regularMarketChangePercent']

    # Setting color2 and emojis depending on the market state and the market change2
    if market_state2 != 'REGULAR':
        # Set color2 for positive and negative values
        color2 = ''
        if change2 > 0:
            color2 = GREEN
        if change2 < 0:
            color2 = RED
        # Set change2 with a moon emoji for closed markets
        colored_change2 = color2 + '({:.2f}'.format(change2) + '%)' + RESET
    if market_state2 == 'REGULAR':
        # Set color2 for positive and negative values
        color2 = ''
        if change2 > 0:
            color2 = GREEN
        if change2 < 0:
            color2 = RED
        # Format change2 to decimal with a precision of two and reset ansi color2 at the end
        colored_change2 = color2 + '({:.2f}'.format(change2) + '%)' + RESET

    market_state3 = index3['marketState']
    current_price3 = index3['regularMarketPrice']
    change3 = index3['regularMarketChangePercent']

    # Setting color3 and emojis depending on the market state and the market change3
    if market_state3 != 'REGULAR':
        # Set color3 for positive and negative values
        color3 = ''
        if change3 > 0:
            color3 = GREEN
        if change3 < 0:
            color3 = RED
        # Set change3 with a moon emoji for closed markets
        colored_change3 = color3 + '({:.2f}'.format(change3) + '%)' + RESET
    if market_state3 == 'REGULAR':
        # Set color3 for positive and negative values
        color3 = ''
        if change3 > 0:
            color3 = GREEN
        if change3 < 0:
            color3 = RED
        # Format change3 to decimal with a precision of two and reset ansi color3 at the end
        colored_change3 = color3 + '({:.2f}'.format(change3) + '%)' + RESET

    market_state4 = index4['marketState']
    current_price4 = index4['regularMarketPrice']
    change4 = index4['regularMarketChangePercent']

    # Setting color4 and emojis depending on the market state and the market change4
    if market_state4 != 'REGULAR':
        # Set color4 for positive and negative values
        color4 = ''
        if change4 > 0:
            color4 = GREEN
        if change4 < 0:
            color4 = RED
        # Set change4 with a moon emoji for closed markets
        colored_change4 = color4 + '({:.2f}'.format(change4) + '%)' + RESET
    if market_state4 == 'REGULAR':
        # Set color4 for positive and negative values
        color4 = ''
        if change4 > 0:
            color4 = GREEN
        if change4 < 0:
            color4 = RED
        # Format change4 to decimal with a precision of two and reset ansi color4 at the end
        colored_change4 = color4 + '({:.2f}'.format(change4) + '%)' + RESET

    market_state5 = index5['marketState']
    current_price5 = index5['regularMarketPrice']
    change5 = index5['regularMarketChangePercent']

    # Setting color4 and emojis depending on the market state and the market change4
    if market_state5 != 'REGULAR':
        # Set color4 for positive and negative values
        color5 = ''
        if change5 > 0:
            color5 = GREEN
        if change5 < 0:
            color5 = RED
        # Set change4 with a moon emoji for closed markets
        colored_change5 = color5 + '({:.2f}'.format(change5) + '%)' + RESET
    if market_state5 == 'REGULAR':
        # Set color4 for positive and negative values
        color5 = ''
        if change5 > 0:
            color5 = GREEN
        if change5 < 0:
            color5 = RED
        # Format change to decimal with a precision of two and reset ansi color4 at the end
        colored_change5 = color5 + '({:.2f}'.format(change5) + '%)' + RESET

    # Set color3FP for positive and negative values
    color3FP = ''
    if (change2*WTSWRD + change3*WTWSML + change4*WTEIMI) > 0:
        color3FP = GREEN
    if (change2*WTSWRD + change3*WTWSML + change4*WTEIMI) < 0:
        color3FP = RED

    threeFundPortfolioName = '3FP'
    threeFundPortfolioChange = color3FP + '({:.2f}'.format((change2*WTSWRD + change3*WTWSML + change4*WTEIMI)) + '%)' + RESET
    # Print the index info only to the menu bar
    TREEFPPRICE = "$" + str(round((index2['regularMarketPrice'])/SWRDSTART*WTSWRD*100 + index3['regularMarketPrice']/WSMLSTART*WTWSML*100 + index4['regularMarketPrice']/EIMISTART*WTEIMI*100,2))
    # THREEFPAUM = "$" + str(round((index2['totalAssets'] + index3['totalAssets'] + index4['totalAssets'])/1000000000,1)) + "B"
    IMIDPRICE = "$" + str(round((index5['regularMarketPrice'])/IMIDSTART*100,2))
    # IMIDAUM = "$" + str(round((index5['totalAssets'])/1000000,1)) + "M"
    toprint = name1 + " " + colored_change1 + " " + threeFundPortfolioName + " " + TREEFPPRICE + " " + threeFundPortfolioChange + " " + THREEFPHLD + " " + THREEFPHLDPct + " " + THREEFPAUM + " " + THREEFPAUMPct
    otherthingtoprint = name5 + " " + IMIDPRICE + " " + colored_change5 + " " + IMIDHLD + " " + IMIDHLDPct + " " + IMIDAUM + " " + IMIDAUMPct
    print("{} {}| dropdown=false".format(toprint,otherthingtoprint), sep=' ')

# Print the stock info in the dropdown menu with additional info in the submenu
def print_stock(s):
    if s['symbol'] == symbols[0]:
        print("Symbol      Price       %Δ        Nav         %Δ")
    market_state = s['marketState']
    change = s['regularMarketChangePercent']

    # Setting color and emojis depending on the market state and the market change
    if market_state != 'REGULAR':
        # Set color for positive and negative values
        color = ''
        if change > 0:
            color = GREEN
        if change < 0:
            color = RED
        market = 'CLOSED'
        # Set change with a moon emoji for closed markets
        colored_change = color + '{:.2f}'.format(change) + '%' + RESET
    if market_state == 'REGULAR':
        # Set color for positive and negative values
        color = ''
        market = 'OPEN'
        if change > 0:
            color = GREEN
        if change < 0:
            color = RED
        # Format change to decimal with a precision of two and reset ansi color at the end
        change_in_percent = color + '{:.2f}'.format(change) + '%' + RESET
        colored_change = change_in_percent

    # Remove appending stock exchange symbol for foreign exchanges, e.g. Apple stock symbol in Frankfurt: APC.F -> APC
    symbol = s['symbol'].split('.')[0]
    # Convert epoch to human readable time HH:MM:SS
    time = datetime.fromtimestamp(s['regularMarketTime']).strftime('%X')
    # Convert float values to decimals with a precision of two
    fifty_day = '{:.2f}'.format(s['fiftyDayAverage'])
    two_hundred_day = '{:.2f}'.format(s['twoHundredDayAverage'])
    fifty_day_change = '(' + '{:.2f}'.format(s['fiftyDayAverageChangePercent'] * 100) + '%)'
    two_hundred_day_change = '(' + '{:.2f}'.format(s['twoHundredDayAverageChangePercent'] * 100) + '%)'

    # Print the stock info seen in the dropdown menu
    if   s['symbol'] == "SWRD.L":
        stock_info = '{:<8} {:<6.2f} {:<15} {:<5.2f} {:<10}' + FONT
        NormSWRD = round(s['regularMarketPrice']/(SWRDSTART/100),2)
        NavSWRD = round(float(s['navPrice'])/SWRDNAVSTART*100,2)
        NavSWRDPct = round((NavSWRD-SWRDNAVLAST)/SWRDNAVLAST*100,2)
        if NavSWRDPct < 0:
            color = RED 
        if NavSWRDPct > 0:
            color = GREEN
        NavSWRDPct = color + "(" + '{:.2f}'.format(NavSWRDPct) + "%)" + RESET
        print(stock_info.format(symbol, (NormSWRD), colored_change, (NavSWRD), (NavSWRDPct)))
    elif s['symbol'] == "WSML.L":
        stock_info = '{:<8} {:<6.2f} {:<15} {:<5.2f} {:<10}' + FONT
        NormWSML = round(s['regularMarketPrice']/(WSMLSTART/100),2)
        NavWSML = round(float(s['navPrice'])/WSMLNAVSTART*100,2)
        NavWSMLPct = round((NavWSML-WSMLNAVLAST)/WSMLNAVLAST*100,2)
        if NavWSMLPct < 0:
            color = RED 
        if NavWSMLPct > 0:
            color = GREEN
        NavWSMLPct = color + "(" + '{:.2f}'.format(NavWSMLPct) + "%)" + RESET
        print(stock_info.format(symbol, (NormWSML), colored_change, (NavWSML), (NavWSMLPct)))
    elif s['symbol'] == "EIMI.L":
        stock_info = '{:<8} {:<6.2f} {:<15} {:<5.2f} {:<10}' + FONT
        NormEIMI = round(s['regularMarketPrice']/(EIMISTART/100),2)
        NavEIMI = round(float(s['navPrice'])/EIMINAVSTART*100,2)
        NavEIMIPct = round((NavEIMI-EIMINAVLAST)/EIMINAVLAST*100,2)
        if NavEIMIPct < 0:
            color = RED 
        if NavEIMIPct > 0:
            color = GREEN
        NavEIMIPct = color + "(" + '{:.2f}'.format(NavEIMIPct) + "%)" + RESET
        print(stock_info.format(symbol, (NormEIMI), colored_change, (NavEIMI), (NavEIMIPct)))
    elif s['symbol'] == "IMID.L":
        stock_info = '{:<8} {:<6.2f} {:<15} {:<5.2f} {:<10}' + FONT
        NormIMID = round(s['regularMarketPrice']/(IMIDSTART/100),2)
        NavIMID = round(float(s['navPrice'])/IMIDNAVSTART*100,2)
        NavIMIDPct = round((NavIMID-IMIDNAVLAST)/IMIDNAVLAST*100,2)
        if NavIMIDPct < 0:
            color = RED 
        if NavIMIDPct > 0:
            color = GREEN
        NavIMIDPct = color + "(" + '{:.2f}'.format(NavIMIDPct) + "%)" + RESET
        print(stock_info.format(symbol, NormIMID, colored_change, NavIMID, NavIMIDPct))
    else:
        stock_info = '{:<5} {:<6.2f} {:<15}' + FONT
        print(stock_info.format(symbol, float(s['regularMarketPrice']), colored_change))

    # Print additional stock info in the submenu
    stock_submenu = '{:<17} {:<17}' + FONT
    print('--' + s['shortName'] + FONT)
    print('--' + s['fullExchangeName'] + ' - Currency in ' + s['currency'] + FONT)
    print('--' + time + ' - Market is ' + market + FONT)
    print('-----')
    print(stock_submenu.format('--Previous Close:', s['regularMarketPreviousClose']))
    print(stock_submenu.format('--Open:', s['regularMarketOpen']))
    print(stock_submenu.format('--Bid:', s['bid']))
    print(stock_submenu.format('--Ask:', s['ask']))
    print(stock_submenu.format("--Day's Range:", s['regularMarketDayRange']))
    print(stock_submenu.format('--52 Week Range:', s['fiftyTwoWeekRange']))
    print(stock_submenu.format('--50 MA:', fifty_day + ' ' + fifty_day_change))
    print(stock_submenu.format('--200 MA:', two_hundred_day + ' ' + two_hundred_day_change))

# Print the price limits in the dropdown menu
def print_price_limits(price_limit_list):
    PARAMETERS = FONT + " refresh=true terminal='false' bash='" + __file__ + "'"

    print('---')
    print('Price Limits' + FONT)
    # Print available price limits in the submenu
    for limit_entry in price_limit_list:
        # Split the limit entry, limits are saved in the format: TYPE SYMBOL PRICE
        limit_type = limit_entry.split()[0]
        symbol = limit_entry.split()[1]
        limit_price = limit_entry.split()[2]
        price_limit_submenu = '{:<6} {:<4} {:<10}'
        # Print the price limit data into the submenu
        # onClick will rerun this script with parameters 'remove' and the {limit_entry} to remove clicked the limit
        print(price_limit_submenu.format('--' + limit_type, symbol, limit_price + PARAMETERS + " param1='remove' param2='" + limit_entry + "'"))
    print('-----')
    print('--To remove a limit, click on it.' + FONT)
    # Print the clickable fields to set new limits or clear all price limits
    # onClick will rerun this script with parameters 'set' to set a new limit
    print('Set new Price Limit...' + PARAMETERS + " param1='set'")
    # onClick will rerun this script with parameters 'clear' to clear the hidden .db file
    print('Clear all Price Limits...' + PARAMETERS + " param1='clear'")

if __name__ == '__main__':
    data_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '.' + os.path.basename(__file__) + '.db')

    # Normal execution by BitBar without any parameters
    if len(sys.argv) == 1:
        stocks = []

        # Check if hidden .db file exists
        try:
            price_limit_list = read_data_file(data_file)
        except FileNotFoundError:
            price_limit_list = []

        # Print the menu bar information
        indexsymbol = list(indices_dict.keys())
        indexnames = list(indices_dict.values())
        print_index(
            get_stock_data(indexsymbol[0]), indexnames[0], 
            get_stock_data(indexsymbol[1]), indexnames[1], 
            get_stock_data(indexsymbol[2]), indexnames[2],
            get_stock_data(indexsymbol[3]), indexnames[3],
            get_stock_data(indexsymbol[4]), indexnames[4])
        # for symbol, name in indices_dict.items():
        #     # For each symbol: curl the data, then print it
        #     index = get_stock_data(symbol)
        #     print_index(index, name)

        # For each symbol: curl the data, check against the .db file for limits
        for symbol in symbols:
            stock = get_stock_data(symbol)
            stocks.append(stock)
            check_price_limits(symbol, stock['regularMarketPrice'], price_limit_list, data_file)

        # Set order of stocks
        if sort_by == 'name':
            stocks = sorted(stocks, key=lambda k: k['shortName'])
        if sort_by == 'market_change_winners':
            stocks = sorted(stocks, key=lambda k: k['regularMarketChangePercent'], reverse=True)
        if sort_by == 'market_change_losers':
            stocks = sorted(stocks, key=lambda k: k['regularMarketChangePercent'])
        if sort_by == 'market_change_volatility':
            stocks = sorted(stocks, key=lambda k: abs(k['regularMarketChangePercent']), reverse=True)

        # Print the stock information inside the dropdown menu
        print('---')
        for stock in stocks:
            print_stock(stock)

        # Print the price limit section inside the dropdown
        print_price_limits(price_limit_list)

    # Script execution with parameter 'set' to set new price limits
    if len(sys.argv) == 2 and sys.argv[1] == 'set':
        # Run this until user does not want to continue
        while True:
            # Get the user selection of whether he wants to set 'BUY' or 'SELL'
            limit_type_prompt = 'Select the type of your limit: BUY (SELL) limits are triggered, when the price is lower (higher) than the limit.'
            limit_type_choices = '["BUY", "SELL"]'
            limit_type = prompt_selection(limit_type_prompt, limit_type_choices)

            # Get the user selection of all tracked symbols
            symbol = prompt_selection('Select stock symbol:', symbols)

            # Get the user input for a price limit, info message includes the current market price
            price = prompt('Current price of ' + symbol + ' is ' + str(get_stock_data(symbol)['regularMarketPrice']) + '. Enter a value for your price limit.')
            # Check if the user input are decimals with a precision of two
            if not re.match(r'^\d+(\.\d{1,2})?$', price):
                # Alert the user on invalid value and stop the script
                alert('Error', 'You entered an invalid value: ' + price + ' - valid values are decimals with a precision of 2, e.g 25.70!')
                sys.exit()

            # Write the limit to the hidden .db file
            write_data_file(data_file, limit_type, symbol, price)

            # Ask user if he wants to add another limit
            add_another_limit = alert('Question', 'Do you want to add another price limit?', ['No', 'Yes'])
            # If the user clicked the 'No' button, stop the script
            if add_another_limit is None:
                sys.exit()

    # Script execution with parameter 'clear' to clear the .db file
    if len(sys.argv) == 2 and sys.argv[1] == 'clear':
        # Ask for user confirmation
        warning = alert('Warning', 'This will clear your price limits! Do you want to continue?')
        if warning is None:
            sys.exit()

        # Clear the file
        open(data_file, 'w').close()

    # Script execution with the parameters 'remove' and the line to be removed
    if len(sys.argv) == 3 and sys.argv[1] == 'remove':
        limit_to_be_removed = sys.argv[2]
        remove_line_from_data_file(data_file, limit_to_be_removed)