From 8fa59c984632b893e2b69ad2ce3dae03ead616cd Mon Sep 17 00:00:00 2001 From: Bruno21 Date: Mon, 13 Jan 2025 20:35:04 +0100 Subject: [PATCH] New stocks.sh forked from https://github.com/appatalks/ticker.sh --- stocks.sh | 476 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ ticker.sh | 94 ----------- 2 files changed, 476 insertions(+), 94 deletions(-) create mode 100755 stocks.sh delete mode 100755 ticker.sh diff --git a/stocks.sh b/stocks.sh new file mode 100755 index 0000000..38ae87a --- /dev/null +++ b/stocks.sh @@ -0,0 +1,476 @@ +#!/usr/bin/env bash +set -e + +# MIT License +# forked from https://github.com/appatalks/ticker.sh +# original script https://github.com/pstadler/ticker.sh + +VERSION="v1.0" + +### TODO +# Verif stocks in $@ +# Locale compliant +# Localization + + +### API +: ${TMPDIR:=/tmp} +SESSION_DIR="${TMPDIR%/}/ticker.sh-$(whoami)" +COOKIE_FILE="${SESSION_DIR}/cookies.txt" +API_ENDPOINT="https://query1.finance.yahoo.com/v8/finance/chart/" +API_SUFFIX="?interval=1d" + +### Variables +ScriptArgs=( "$@" ) +ScriptPath="$(readlink -f "$0")" # /Users/user/Documents/Scripts/stocks/crypto.sh +ScriptWorkDir="$(dirname "$ScriptPath")" # /Users/user/Documents/Scripts/stocks + + +####### Configurations ###### + +#NO_COLOR=1 + +LANG=C +LC_NUMERIC=C + +# Check if NO_COLOR is set to disable colorization +if [ -z "$NO_COLOR" ]; then + : "${COLOR_GREEN:=$'\e[32m'}" + : "${COLOR_GREEN_BOLD:=$'\e[1;32m'}" + : "${COLOR_RED:=$'\e[31m'}" + : "${COLOR_RED_BOLD:=$'\e[1;31m'}" + : "${COLOR_YELLOW=$'\e[33m'}" + : "${COLOR_YELLOW_BOLD:=$'\e[1;33m'}" + : "${COLOR_SILVER=$'\e[37m'}" + : "${COLOR_LIGHT_GREY=$'\e[249m'}" + : "${COLOR_BRIGHT_PURPLE=$'\e[35;1m'}" + : "${BOLD:=$'\e[1m'}" + : "${ITALIC:=$'\e[3m'}" + : "${COLOR_RESET:=$'\e[00m'}" +else + : "${BOLD:=$'\e[1m'}" + : "${COLOR_RESET:=$'\e[00m'}" + : "${ITALIC:=$'\e[3m'}" +fi + + +# Help +Help() { + clear + echo -e "\n${COLOR_GREEN_BOLD}Bash Stocks $VERSION${COLOR_RESET}\n" + echo "Syntax: stocks.sh []" + echo + echo "Examples: stocks.sh AAPL ORA.PA AUB.PA" + echo " stocks.sh (need ~/.stocks.yaml file)" + echo + echo "Options:" + echo "-h Print this Help." + echo + echo "Requires jq and yq installed" + echo " -https://stedolan.github.io/jq/" + echo " -https://github.com/kislyuk/yq" +} + +### Parse options + +while getopts "h" opt; do + case ${opt} in + h|*) Help + Help + exit 2 + ;; + esac +done +shift $((OPTIND -1)) + + +### Config file +stocks_list=$HOME/.stocks.yaml +#json_file="/tmp/purchased.json" +json_file="$ScriptWorkDir/purchased.json" +#json_file="" + +####### /Configurations ###### + + +### Requierements +if ! $(type jq >/dev/null 2>&1); then + echo -e "${COLOR_RED}'jq' is not in the PATH. (See: https://stedolan.github.io/jq/)${COLOR_RESET}" + exit 1 +fi +if ! $(type yq >/dev/null 2>&1); then + echo -e "${COLOR_RED}'yq' is not in the PATH. (See: https://github.com/kislyuk/yq)${COLOR_RESET}" + exit 1 +fi + +cat < /dev/null > /dev/tcp/1.1.1.1/53 +if [[ $? -ne 0 ]]; then + echo -e "\n${COLOR_RED}No Internet connection !${COLOR_RESET}" + echo -e "Exit !" + exit 1 +fi + + +####### Functions ###### + +symbol_currency() { +case $currency in + EUR) + echo -n "€" + ;; + USD) + echo -n "$" + ;; + GBP) + echo -n "£" + ;; + JPY) + echo -n "¥" + ;; +esac +} + +# Check if number is positive (or 0) or negative +CheckSign() { + local n="$1" # Capture the number passed as a parameter. + + if [ $(echo "$n < 0" | bc -l) -eq 1 ] ; then + #printf "%+17.13f : %s\n" "$n" "negative" + p="-" + else + #printf "%+17.13f : %s\n" "$n" "positive or zero" + p="+" + fi + echo "$p" +} + +[ ! -d "$SESSION_DIR" ] && mkdir -m 700 "$SESSION_DIR" + + +preflight () { + curl --silent --output /dev/null --cookie-jar "$COOKIE_FILE" "https://finance.yahoo.com" \ + -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8" +} + +fetch_chart () { + local symbol=$1 + local url="${API_ENDPOINT}${symbol}${API_SUFFIX}" + curl --silent -b "$COOKIE_FILE" "$url" +} + +[ ! -f "$COOKIE_FILE" ] && preflight + + +####### Start Display ###### + +echo -e "${COLOR_GREEN_BOLD}Bash Stocks${COLOR_RESET} $VERSION" +echo -e "Using Yahoo Finance API ($API_ENDPOINT)\n" + + +### Read quotes from arguments or from~/.stocks.yaml config file +SYMBOLS=() + +# from arguments +if [ -n "$ScriptArgs" ]; then + + REGEXP='^[a-zA-Z0-9.,_ ]+$' + q="$@" + if [[ "$q" =~ $REGEXP ]]; then + q=${q//,/\ } + q=${q//_/\ } + + SYMBOLS+=($q) + else + Help + exit 1 + fi + +: <<'END_COMMENT' + + # Check if quotes passed as argument really exist + + for val in ${!SYMBOLS[@]} + do + k="${SYMBOLS[$val]}" + + #echo $val # index + #echo $k # value + + if [[ ! "$list_coins" == *"$k"* ]]; then + echo -e "${COLOR_RED}$k is not a crypto. Remove it !${COLOR_RESET}" + unset SYMBOLS[$val] + fi + done + + # Recreate the array, because the gaps have to disappear + SYMBOLS=("${SYMBOLS[@]}") +END_COMMENT + + echo -e "${COLOR_GREEN}Found ${#SYMBOLS[@]} stocks to display: ${SYMBOLS[@]} ...${COLOR_RESET}" + +# from ~/.stocks.yaml config file +else + + if [ -f "$stocks_list" ]; then + sl=$(cat $stocks_list) + [ $? -eq 0 ] && echo -e "${COLOR_GREEN}Reading $stocks_list ...${COLOR_RESET}" || echo -e "${COLOR_RED}Error while reading $stocks_list ...${COLOR_RESET}" + + a=$(yq '.watchlist[]' <<< $sl) + SYMBOLS+=(${a}) + [ $? -eq 0 ] && echo -e "${COLOR_GREEN}Found ${#SYMBOLS[@]} stocks to display: ${SYMBOLS[@]} ...${COLOR_RESET}" || echo -e "${COLOR_RED}No coins to display...${COLOR_RESET}" + + b=$(yq '.lot' <<< $sl) + u=$((yq '.lots[] | select(.quantity != 0) | .symbol' | tr '\n' ' ') <<< $sl) + purchased=(${u}) + [ $? -eq 0 ] && echo -e "${COLOR_GREEN}Found ${#purchased[@]} quotes purchased: ${purchased[@]} ...${COLOR_RESET}" || echo -e "${COLOR_RED}Fail to load quotes list purchased...${COLOR_COLOR_RESET}" + + # Check that purchased's stocks are in display list + for val in ${!purchased[@]} + do + x="${purchased[$val]}" + if [[ " ${SYMBOLS[*]} " != *"$x"* ]]; then + SYMBOLS+=(${x}) + echo -e "${COLOR_RED}$x is not in stocks's list to display. We add it !${COLOR_GREEN}" + fi + done + [ $? -eq 0 ] && echo -e "${COLOR_GREEN}Found ${#SYMBOLS[@]} stocks to display: ${SYMBOLS[@]} ...${COLOR_RESET}" || echo -e "${COLOR_RED}No coins to display...${COLOR_RESET}" + + else + echo -e "${COLOR_RED}$stocks_list not present !${COLOR_RESET}" + echo "" + fi + + #echo "${SYMBOLS[@]}" + #echo "${purchased[@]}" + +: <<'END_COMMENT' + while IFS= read -r obj; do + #echo "$obj" + stock=${obj:2} + #echo "$stock" + SYMBOLS+=("$stock") + done < <(echo "$a") + [ $status -eq 0 ] && echo -e "${COLOR_GREEN}Found ${#SYMBOLS[@]} stocks to display: ${SYMBOLS[@]} ...${COLOR_RESET}" || echo -e "${COLOR_RED}No coins to display...${COLOR_RESET}" +END_COMMENT + if [ -z "$SYMBOLS" ]; then + echo "Usage: ./crypto.sh" + echo " - add quotes to ~/.stocks.yaml file" + exit 1 + fi + +fi + +: <<'END_COMMENT' +### Parse options + +while getopts "g" opt; do + case ${opt} in + h ) + echo "Usage: ./ticker.sh [-g] AAPL GOOG ORA.PA" + exit 1 + ;; + esac +done +shift $((OPTIND -1)) + + + +END_COMMENT + + +# volume %11s +echo +printf "%s%-22s %-8s %9s %8s %8s %9s %9s %11s %9s %9s %18s %18s %s \n" \ + "${BOLD}" "ShortName" "Symbol" \ + "Current" "Change" "Percent" \ + "Day High" "Day Low" "Volume" \ + "52w +" "52w -" "Last Date" "Timezone" \ + "${COLOR_RESET}" + +# Sort arrays (lists of quotes / purchased) + +IFS=$'\n' +SYMBOLS=($(sort <<<"${SYMBOLS[*]}")) +purchased=($(sort <<<"${purchased[*]}")) +unset IFS + + +### Create JSON purchased quotes + +# Create an initial block +jq -n '{"quotes":[]}' > "${json_file}" +ii=1 + + +# Initialize an array to hold background process IDs (avec les PID activés => affichage dans le désordre (au fil de l'eau)) +pids=() + +echo "quotes: ${#SYMBOLS[@]} - ${SYMBOLS[@]}" +echo "purchased: ${#purchased[@]} - ${purchased[@]}" + + +for symbol in "${SYMBOLS[@]}"; do +( + # Running in subshell + + results=$(fetch_chart "$symbol") + + regularMarketTime=$(echo "$results" | jq -r '.chart.result[0].meta.regularMarketTime') + #rdate -d @$regularMarketTime +"%c" + rmt=$(LC_ALL=fr_FR.UTF-8 date -d @$regularMarketTime +"%d/%m/%y %H:%M:%S" 2>/dev/null || LC_ALL=fr_FR.UTF-8 date -r $regularMarketTime +"%d/%m/%y %H:%M:%S") + + exchangeTimezoneName=$(echo "$results" | jq -r '.chart.result[0].meta.exchangeTimezoneName') + fiftyTwoWeekHigh=$(echo "$results" | jq -r '.chart.result[0].meta.fiftyTwoWeekHigh') + fiftyTwoWeekLow=$(echo "$results" | jq -r '.chart.result[0].meta.fiftyTwoWeekLow') + regularMarketDayHigh=$(echo "$results" | jq -r '.chart.result[0].meta.regularMarketDayHigh') + regularMarketDayLow=$(echo "$results" | jq -r '.chart.result[0].meta.regularMarketDayLow') + regularMarketVolume=$(echo "$results" | jq -r '.chart.result[0].meta.regularMarketVolume') + longName=$(echo "$results" | jq -r '.chart.result[0].meta.longName') + shortName=$(echo "$results" | jq -r '.chart.result[0].meta.shortName') + + currentPrice=$(echo "$results" | jq -r '.chart.result[0].meta.regularMarketPrice') + previousClose=$(echo "$results" | jq -r '.chart.result[0].meta.chartPreviousClose') + currency=$(echo "$results" | jq -r '.chart.result[0].meta.currency') + symb=$(symbol_currency $currency) + symbol=$(echo "$results" | jq -r '.chart.result[0].meta.symbol') + + [ "$previousClose" = "null" ] && previousClose="1.0" + + priceChange=$(awk -v currentPrice="$currentPrice" -v previousClose="$previousClose" 'BEGIN {printf "%.2f", currentPrice - previousClose}') + percentChange=$(awk -v currentPrice="$currentPrice" -v previousClose="$previousClose" 'BEGIN {printf "%.2f", ((currentPrice - previousClose) / previousClose) * 100}') + + open=$(echo "$results" | jq -r '.chart.result[0].indicators.quote' | jq -r '.[].open[0]') + + #firstTradeDate=$(echo "$results" | jq -r '.chart.result[0].meta.firstTradeDate') + #ftd==$(LC_ALL=fr_FR.UTF-8 date -d @$firstTradeDate +"%c" 2>/dev/null || LC_ALL=fr_FR.UTF-8 date -r $ts +"%c") + #volume=$(echo "$results" | jq -r '.chart.result[0].indicators.quote' | jq -r '.[].volume[0]') + #high=$(echo "$results" | jq -r '.chart.result[0].indicators.quote' | jq -r '.[].high[0]') + #close=$(echo "$results" | jq -r '.chart.result[0].indicators.quote' | jq -r '.[].close[0]') + + if [[ " ${purchased[*]} " == *"$symbol"* ]]; then + + + # Create a temporary JSON file for purchased quotes because requests arrive as they come in (not in order) + # https://www.codegix.com/use-jq-to-create-a-dynamic-json-data-in-bash/ + json_data=$(jq \ + --arg symbol "$symbol" \ + --arg price "$currentPrice" \ + --arg volume "$regularMarketVolume" \ + --arg last "$regularMarketTime" \ + --arg symb "$symb" \ + --arg percent "$percentChange" \ + --argjson value "$ii" '.quotes += [{ "symbol": $symbol, "price": $price, "volume": $volume, "last": $last, "symb": $symb, "percent": $percent }]' "${json_file}") + + echo "${json_data}" > "${json_file}" + + ((ii++)) + +: <<'END_COMMENT' + + # Check quotes we have (from .stocks.yaml) + z=$(echo "$sl" | yq '.lots[] | select(.symbol == "'${symbol}'")') + q=$(echo "$z" | yq '.quantity') + uc=$(echo "$z" | yq '.unit_cost') + + purchase_cost=$(echo "$q * $uc" | bc -l) + valuations=$(echo "$q * $currentPrice" | bc -l) + profit=$(echo "$valuations - $purchase_cost" | bc -l) + performance=$(echo "($valuations / $purchase_cost - 1) * 100" | bc -l) + #performance=$(round $performance 2) + + echo "$q - $uc - $purchase_cost - $valuations - $profit - $performance" +END_COMMENT + + + fi + + + COLOR_pri=$([ $(CheckSign "$priceChange") == "-" ] && echo ${COLOR_RED} || echo ${COLOR_GREEN}) + COLOR_per=$([ $(CheckSign "$percentChange") == "-" ] && echo ${COLOR_RED} || echo ${COLOR_GREEN}) + + if [ -z "$NO_COLOR" ]; then + printf "%s%-22s %-8s %8.2f%s%s %s%7.2f%s%s %s%7.2f%%%s %s%8.2f%s %8.2f%s %11s %8.2f%s %8.2f%s %18s %18s %s\n" \ + "${COLOR_YELLOW_BOLD}" "$shortName" "$symbol" \ + "$currentPrice" "$symb" "${COLOR_RESET}" "${COLOR_pri}" "$priceChange" "$symb" "${COLOR_RESET}" "${COLOR_per}" "$percentChange" "${COLOR_RESET}" \ + "${COLOR_YELLOW_BOLD}" "$regularMarketDayHigh" "$symb" "$regularMarketDayLow" "$symb" "$regularMarketVolume" \ + "$fiftyTwoWeekHigh" "$symb" "$fiftyTwoWeekLow" "$symb" "$rmt" "$exchangeTimezoneName" "${COLOR_RESET}"\ + + else + printf "%-22s %-8s %8.2f%s %7.2f%s %7.2f%% %8.2f%s %8.2f%s %11s %8.2f%s %8.2f%s %18s %18s\n" \ + "$shortName" "$symbol" \ + "$currentPrice" "$symb" "$priceChange" "$symb" "$percentChange" \ + "$regularMarketDayHigh" "$symb" "$regularMarketDayLow" "$symb" "$regularMarketVolume" \ + "$fiftyTwoWeekHigh" "$symb" "$fiftyTwoWeekLow" "$symb" "$rmt" "$exchangeTimezoneName" + fi + + ) #& + + # Stack PIDs + pids+=($!) + +done + +# Wait for all background processes to finish +for pid in "${pids[@]}"; do + wait "$pid" +done + +sleep 1 + +echo "quotes: ${#SYMBOLS[@]} - ${SYMBOLS[@]}" +echo "purchased: ${#purchased[@]} - ${purchased[@]}" +#cat $json_file + +if [ ${#purchased[@]} -gt 0 ]; then + + echo + echo -e "${COLOR_GREEN}Displaying purchased quotes ...${COLOR_RESET}" + echo + + quotes="$(cat $json_file)" + counter=0 + + printf "%s%-8s %13s %10s %9s %11s %16s %13s %13s %13s %11s %18s %s\n" \ + "${BOLD}" "Symbol" "Percent" "Current" "Quantity" "Unit cost" "Purchase cost" "Valuations" "Profit" "Performance" "Volume" "Last quote" "${COLOR_RESET}" + + while [ "$counter" -lt "${#purchased[@]}" ]; do + + purchase="${purchased[$counter]}" + + s=$(echo "$quotes" | jq -r '.quotes | .[] | select(.symbol == "'${purchase}'") | (.symbol)') # Symbol + p=$(echo "$quotes" | jq -r '.quotes | .[] | select(.symbol == "'${purchase}'") | (.price)') # Price + v=$(echo "$quotes" | jq -r '.quotes | .[] | select(.symbol == "'${purchase}'") | (.volume)') # Volume + l=$(echo "$quotes" | jq -r '.quotes | .[] | select(.symbol == "'${purchase}'") | (.last)') # Last quote + rmt=$(LC_ALL=fr_FR.UTF-8 date -d @$l +"%d/%m/%y %H:%M:%S" 2>/dev/null || LC_ALL=fr_FR.UTF-8 date -r $l +"%d/%m/%y %H:%M:%S") + #echo $l # 1736526916 + #rmt=$(LC_ALL=fr_FR.UTF-8 date -d "@$l" +"%c" 2>/dev/null || LC_ALL=fr_FR.UTF-8 date -r "$l" +"%c") + + sy=$(echo "$quotes" | jq -r '.quotes | .[] | select(.symbol == "'${purchase}'") | (.symb)') # Symb (€/$/£) + pc=$(echo "$quotes" | jq -r '.quotes | .[] | select(.symbol == "'${purchase}'") | (.percent)') # Percent change + + z=$(echo "$sl" | yq '.lots[] | select(.symbol == "'${purchase}'")') + q=$(echo "$z" | yq '.quantity') + uc=$(echo "$z" | yq '.unit_cost') + + purchase_cost=$(echo "$q * $uc" | bc -l) + valuations=$(echo "$q * $p" | bc -l) + profit=$(echo "$valuations - $purchase_cost" | bc -l) + performance=$(echo "($valuations / $purchase_cost - 1) * 100" | bc -l) + #performance=$(round $performance 2) + + COLOR_performance=$([ $(CheckSign "$performance") == "-" ] && echo ${COLOR_RED} || echo ${COLOR_GREEN}) + + #echo "$s - $p - $v - $rmt - $q - $uc - $purchase_cost - $valuations - $profit - $performance" + #echo "$purchase_cost - $valuations - $profit - $performance" + + # symbol(s) percent (pc) price(p) quantity(q) unit_cost(uc) purchase_cost valuations profit performance volume(v) last_quote(l) + + printf "%s%-8s %12.2f%% %9.2f%s %9d %10.2f%s %15.2f%s %12.2f%s %12.2f%s %12.2f%% %11d %18s %s\n" \ + "${COLOR_performance}" "$s" "$pc" "$p" "$sy" "$q" "$uc" "$sy" "$purchase_cost" "$sy" "$valuations" "$sy" "$profit" "$sy" "$performance" "$v" "$rmt" "${COLOR_RESET}" + + + ((++counter)) + done +fi + +echo -e "\n${ITALIC}Script with ${#SYMBOLS[@]} requests to Yahoo Finance API finished in $SECONDS seconds.${COLOR_RESET}" diff --git a/ticker.sh b/ticker.sh deleted file mode 100755 index e54124a..0000000 --- a/ticker.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env bash -set -e - -LANG=C -LC_NUMERIC=C - -SYMBOLS=("$@") - -if ! $(type jq > /dev/null 2>&1); then - echo "'jq' is not in the PATH. (See: https://stedolan.github.io/jq/)" - exit 1 -fi - -if [ -z "$SYMBOLS" ]; then - echo "Usage: ./ticker.sh AAPL MSFT GOOG BTC-USD" - exit -fi - -FIELDS=(symbol marketState regularMarketPrice regularMarketChange regularMarketChangePercent regularMarketVolume regularMarketPreviousClose \ - preMarketPrice preMarketChange preMarketChangePercent postMarketPrice postMarketChange postMarketChangePercent \ - shortName longName fullExchangeName) -#API_ENDPOINT="https://query1.finance.yahoo.com/v7/finance/quote?lang=en-US®ion=US&corsDomain=finance.yahoo.com" -API_ENDPOINT="https://query1.finance.yahoo.com/v7/finance/quote?lang=fr-FR®ion=FR&corsDomain=finance.yahoo.com" - -if [ -z "$NO_COLOR" ]; then - : "${COLOR_BOLD:=\e[1;37m}" - : "${COLOR_GREEN:=\e[32m}" - : "${COLOR_RED:=\e[31m}" - : "${COLOR_RESET:=\e[00m}" -fi - -symbols=$(IFS=,; echo "${SYMBOLS[*]}") -echo "$symbols" - -fields=$(IFS=,; echo "${FIELDS[*]}") - -results=$(curl --silent "$API_ENDPOINT&fields=$fields&symbols=$symbols" | jq '.quoteResponse .result') -#echo "$results" - -query () { - echo $results | jq -r ".[] | select(.symbol == \"$1\") | .$2" -} - -for symbol in $(IFS=' '; echo "${SYMBOLS[*]}" | tr '[:lower:]' '[:upper:]'); do - marketState="$(query $symbol 'marketState')" - - if [ -z $marketState ]; then - printf 'No results for symbol "%s"\n' $symbol - continue - fi - - preMarketChange="$(query $symbol 'preMarketChange')" - postMarketChange="$(query $symbol 'postMarketChange')" - - longName="$(query $symbol 'longName')" - #regularMarketPreviousClose="$(query $symbol 'regularMarketPreviousClose')" - - if [ $marketState == "PRE" ] \ - && [ $preMarketChange != "0" ] \ - && [ $preMarketChange != "null" ]; then - nonRegularMarketSign='*' - price=$(query $symbol 'preMarketPrice') - diff=$preMarketChange - percent=$(query $symbol 'preMarketChangePercent') - elif [ $marketState != "REGULAR" ] \ - && [ $postMarketChange != "0" ] \ - && [ $postMarketChange != "null" ]; then - nonRegularMarketSign='*' - price=$(query $symbol 'postMarketPrice') - diff=$postMarketChange - percent=$(query $symbol 'postMarketChangePercent') - else - nonRegularMarketSign='' - price=$(query $symbol 'regularMarketPrice') - diff=$(query $symbol 'regularMarketChange') - percent=$(query $symbol 'regularMarketChangePercent') - previous=$(query $symbol 'regularMarketPreviousClose') - fi - - if [ "$diff" == "0" ] || [ "$diff" == "0.0" ]; then - color= - elif ( echo "$diff" | grep -q ^- ); then - color=$COLOR_RED - else - color=$COLOR_GREEN - fi - - if [ "$price" != "null" ]; then - #printf "%-10s$COLOR_BOLD%8.2f$COLOR_RESET" $symbol $price - printf "%-10s%-35s$COLOR_BOLD%8.2f$COLOR_RESET" $symbol "$longName" $price - printf "$color%10.2f%12s$COLOR_RESET%+10s" $diff $(printf "(%.2f%%)" $percent) $previous - printf " %s\n" "$nonRegularMarketSign" - fi -done