#!/usr/bin/env bash #set -e ### https://coinmarketcap.com/api/documentation/v1/#operation/getV2CryptocurrencyQuotesLatest VERSION="v1.0" ### TODO # Locale compliant # Localization redbold="\033[1;31m" red="\033[0;31m" greenbold="\033[1;32m" green="\033[0;32m" yellowbold="\033[1;33m" yellow="\033[0;33m" bgd="\033[1;4;31m" bold="\033[1m" #bold_under="\033[1;4m" italic="\033[3m" ita_under="\033[3;4m" underline="\033[4m" reset="\033[0m" ### Variables ScriptArgs=( "$@" ) ScriptPath="$(readlink -f "$0")" # /Users/user/Documents/Scripts/stocks/crypto.sh ScriptWorkDir="$(dirname "$ScriptPath")" # /Users/user/Documents/Scripts/stocks ####### Configurations ###### ### Credentials ### Put the CMC_API_KEY= in .env file dotenv () { set -a # shellcheck disable=SC1091 [ -f "$ScriptWorkDir/.env" ] && . "$ScriptWorkDir/.env" || echo -e "${red}\nNo .env file found ! CMC_API_KEY needed !!${reset}" set +a } dotenv ### or in the script #CMC_API_KEY= if [ -z $CMC_API_KEY ]; then echo -e "\n${red}No CMC_API_KEY key found !${reset}" help exit fi ### Config file cryptos_list=$HOME/.cryptos.yaml # Free version does not support more than 1 currency. # So, 2 requests for Euro and Dollar exchange rates # Default currency (MUST BE THE SAME as unit_cost in ~/.cryptos.yaml) currency="EUR" symb_currency="€" # Secondary currency (optionnal) currency2="USD" symb_currency2="$" color_display=true # (true/false) ####### /Configurations ###### ### Requierements if ! $(type jq >/dev/null 2>&1); then echo -e "${red}'jq' is not in the PATH. (See: https://stedolan.github.io/jq/)${reset}" exit 1 fi if ! $(type yq >/dev/null 2>&1); then echo -e "${red}'yq' is not in the PATH. (See: https://github.com/kislyuk/yq)${reset}" exit 1 fi cat < /dev/null > /dev/tcp/1.1.1.1/53 if [[ $? -ne 0 ]]; then echo -e "\n${red}No Internet connection !${reset}" echo -e "Exit !" exit 1 fi ####### Functions ###### # Help Help() { echo -e "${bold}cryptos $VERSION${reset}\n" echo "Syntax: cryptos.sh []" echo echo "Examples: cryptos.sh BTC ETH SOLE" echo " cryptos.sh (need ~/.cryptos.yaml file)" echo echo "Options:" echo "-h Print this Help." echo echo "Requires a CoinMarketCap API key" echo "https://coinmarketcap.com/api/" echo "Requires jq and yq installed" echo echo "\$currency variable (default)(line 39) must be the same as unit_cost in ~/.cryptos.yaml" } # 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" } LANG=C LC_NUMERIC=C ### API URLPREFIX="https://" URLBASE="pro-api.coinmarketcap.com/" URLPOSTFIX="v1/cryptocurrency/" DATAURL=${URLPREFIX}${URLBASE}${URLPOSTFIX} ### Load (or download) a list of existing cryptos #list_coins=$(curl --silent -H "X-CMC_PRO_API_KEY: $CMC_API_KEY" -H "Accept: application/json" -d "start=1&limit=5&convert=USD" -G "${DATAURL}listings"/latest) #echo "$list_coins" | jq -r '.data[].name, .data[].symbol' #echo "$list_coins" | jq -r '.data[] | {name: .name, symbol: .symbol}' #list_coins=$(curl --silent -H "X-CMC_PRO_API_KEY: $CMC_API_KEY" -H "Accept: application/json" -d "start=1&limit=5&convert=USD" -G "${DATAURL}listings"/latest | jq -r '.data[] | .symbol') get_list_coins (){ START_TIME=`echo $(($(date +%s%N)/1000000))` curl --silent -H "X-CMC_PRO_API_KEY: $CMC_API_KEY" -H "Accept: application/json" -d "start=1&limit=5000&convert=USD" -G "${DATAURL}listings"/latest | jq -r '.data[] | .symbol' > "$ScriptWorkDir/list_coins.txt" status=$? END_TIME=`echo $(($(date +%s%N)/1000000))` ELAPSED_TIME=$(($END_TIME - $START_TIME)) [ $status -eq 0 ] && echo -e "${green}Request to CMC API listings $currency successfull in $ELAPSED_TIME ms !${reset}" || echo -e "${red}Request to CMC API listing $currency failed !${reset}" } echo -e "${greenbold}Bash Crypto${reset} $VERSION" echo -e "Using CoinMarketCap API ($DATAURL)\n" if [ -f "$ScriptWorkDir/list_coins.txt" ]; then timestamp_current=$(date +"%s") timestamp_file=$(date -r list_coins.txt +"%s") since=$(($((timestamp_current - timestamp_file))/(60*60*24))) # Re-Download coins list every 15 days ((since > 15)) && { echo -e "${green}Download coins list...${reset}"; get_list_coins; } list_coins=$(cat "$ScriptWorkDir/list_coins.txt") [ $? -eq 0 ] && echo -e "${green}Load coins list...${reset}" || echo -e "${red}Fail to load coins list...${reset}" else get_list_coins list_coins=$(cat "$ScriptWorkDir/list_coins.txt") [ $? -eq 0 ] && echo -e "${green}Load coins list...${reset}" || echo -e "${red}Fail to load coins list...${reset}" fi ### Read cryptos from ~/.cryptos.yaml config file cryptos=() # from arguments if [ -n "$ScriptArgs" ]; then REGEXP='^[a-zA-Z0-9,_ ]+$' q="$@" if [[ "$q" =~ $REGEXP ]]; then q=${q//,/\ } q=${q//_/\ } cryptos+=($q) else Help exit 1 fi # Check if crypto passed as argument really exist for val in ${!cryptos[@]} do k="${cryptos[$val]}" #echo $val # index #echo $k # value if [[ ! "$list_coins" == *"$k"* ]]; then echo -e "${red}$k is not a crypto. Remove it !${reset}" unset cryptos[$val] fi done # Recreate the array, because the gaps have to disappear cryptos=("${cryptos[@]}") echo -e "${green}Found ${#cryptos[@]} coins to display: ${cryptos[@]} ...${reset}" # from ~/.cryptos.yaml config file else if [ -f "$cryptos_list" ]; then cl=$(cat $cryptos_list) a=$(yq '.watchlist' <<< $cl) #a=$(cat $cryptos_list | yq '.watchlist') # Cryptos to display status=$? #[ $? -eq 0 ] && echo -e "${green}Load coins list to display...${reset}" || echo -e "${red}Fail to load coins list to display...${reset}" b=$(yq '.lot' <<< $cl) #b=$(cat $cryptos_list | yq '.lots') # Cryptos i've purchased c=$((yq '.lots[] | select(.quantity != 0) | .symbol' | tr '\n' ' ') <<< $cl) #c=$(cat $cryptos_list | yq '.lots[] | select(.quantity != 0) | .symbol' | tr '\n' ' ') u=$(echo "$c" | wc -w) [ $? -eq 0 ] && echo -e "${green}Found $u coins purchased: $c ...${reset}" || echo -e "${red}Fail to load coins list purchased...${reset}" else echo -e "${red}$cryptos_list not present !${reset}" echo "" fi while IFS= read -r obj; do stock=${obj:2} cryptos+=("$stock") done < <(echo "$a") [ $status -eq 0 ] && echo -e "${green}Found ${#cryptos[@]} coins to display: ${cryptos[@]} ...${reset}" || echo -e "${red}No coins to display...${reset}" if [ -z "$cryptos" ]; then echo "Usage: ./crypto.sh" echo " - add coins to ~/.cryptos.yaml file" exit 1 fi fi nb_cryptos=${#cryptos[@]} symbols=$(echo ${cryptos[@]} | tr [:space:] ',' | awk '{ print substr( $0, 1, length($0)-1 ) }') # Free version does not support more than 1 currency. # So, 2 requests for Euro and Dollar exchange rates if [ -n "$currency2" ]; then # 2 currency START_TIME=`echo $(($(date +%s%N)/1000000))` request=$(curl --silent -H "X-CMC_PRO_API_KEY: $CMC_API_KEY" -H "Accept: application/json" -d "convert=$currency" -G "${DATAURL}quotes"/latest?symbol=$symbols) status=$? END_TIME=`echo $(($(date +%s%N)/1000000))` ELAPSED_TIME=$(($END_TIME - $START_TIME)) [ $status -eq 0 ] && echo -e "${green}Request to CMC API with currency $currency successfull in $ELAPSED_TIME ms !${reset}" || echo -e "${red}Request to CMC API with currency $currency failed !${reset}" START_TIME=`echo $(($(date +%s%N)/1000000))` request2=$(curl --silent -H "X-CMC_PRO_API_KEY: $CMC_API_KEY" -H "Accept: application/json" -d "convert=$currency2" -G "${DATAURL}quotes"/latest?symbol=$symbols) status2=$? END_TIME=`echo $(($(date +%s%N)/1000000))` ELAPSED_TIME=$(($END_TIME - $START_TIME)) [ $status2 -eq 0 ] && echo -e "${green}Request to CMC API with currency $currency2 successfull in $ELAPSED_TIME ms !${reset}" || echo -e "${red}Request to CMC API with currency $currency2 failed !${reset}" echo -e "${green}Displaying quotes for ${cryptos[@]} ...${reset}\n" printf "${bold}| %-15s | %-4s | %-9s | %-9s | %-14s | %-7s | %-7s | %-7s | %-7s | %-7s | %-25s ${reset}\n" "Nom" "Symb" " Prix ($symb_currency)" " Prix ($symb_currency2)" " Volume" " % 24h" " % 7j" " % 30j" " % 60j" " % 90j" "Dernière mise-à-jour" printf "${bold}%141s${reset}\n" " " | tr ' ' '-' else # 1 currency START_TIME=`echo $(($(date +%s%N)/1000000))` request=$(curl --silent -H "X-CMC_PRO_API_KEY: $CMC_API_KEY" -H "Accept: application/json" -d "convert=$currency" -G "${DATAURL}quotes"/latest?symbol=$symbols) status=$? END_TIME=`echo $(($(date +%s%N)/1000000))` ELAPSED_TIME=$(($END_TIME - $START_TIME)) [ $status -eq 0 ] && echo -e "${green}Request to CMC API with currency $currency successfull in $ELAPSED_TIME ms !${reset}" || echo -e "${red}Request to CMC API with currency $currency failed !${reset}" echo -e "${green}Displaying quotes for ${cryptos[@]} ...${reset}\n" printf "${bold}| %-15s | %-4s | %-9s | %-14s | %-7s | %-7s | %-7s | %-7s | %-7s | %-25s ${reset}\n" "Nom" "Symb" " Prix ($symb_currency)" " Volume" " % 24h" " % 7j" " % 30j" " % 60j" " % 90j" "Dernière mise-à-jour" printf "${bold}%141s${reset}\n" " " | tr ' ' '-' fi # Check if NO_COLOR is set to disable colorization if [ -z "$NO_COLOR" ]; then : "${COLOR_GREEN:=$'\e[32m'}" : "${COLOR_RED:=$'\e[31m'}" : "${COLOR_RESET:=$'\e[00m'}" fi #b=$(cat $cryptos_list | yq '.lots') #echo "$b" #d=$(cat $cryptos_list | yq '.lots[]') #echo "$d" #c=$(cat $cryptos_list | yq '.lots[] | select(.quantity != 0) | .symbol') counter=0 while [ $counter -lt $nb_cryptos ]; do c=$(echo "${cryptos[counter]}") filter=".$c" #echo "value (crypto): $c" # devise 1 (EUR) coin=$(echo "$request" | jq -r '.data' | jq -r $filter) if [ "$coin" == null ]; then echo -e "${red}$c n'est pas une crypto !${reset}" ((counter++)) continue fi name=$(echo "$coin" | jq -r '.name') symbol=$(echo "$coin" | jq -r '.symbol') slug=$(echo "$coin" | jq -r '.slug') max_supply=$(echo "$coin" | jq -r '.max_supply') # Volume (Mrd) ($2,07 Mrd) circulating_supply=$(echo "$coin" | jq -r '.circulating_supply') # Supply en circulation (Mio BNB) (144,01 Mio BNB) total_supply=$(echo "$coin" | jq -r '.total_supply') # Montant total fourni (Mio BNB) (144,01 Mio BNB) cmc_rank=$(echo "$coin" | jq -r '.cmc_rank') # Rang (N°5) filter2=".$currency" quote=$(echo "$coin" | jq -r '.quote'| jq -r $filter2) price=$(echo "$quote" | jq -r '.price') volume_24h=$(echo "$quote" | jq -r '.volume_24h') percent_change_24h=$(echo "$quote" | jq -r '.percent_change_24h') percent_change_7d=$(echo "$quote" | jq -r '.percent_change_7d') percent_change_30d=$(echo "$quote" | jq -r '.percent_change_30d') percent_change_60d=$(echo "$quote" | jq -r '.percent_change_60d') percent_change_90d=$(echo "$quote" | jq -r '.percent_change_90d') last_updated=$(echo "$quote" | jq -r '.last_updated') # 2025-01-07T16:35:00.000Z UTC (ZULU) last_updated_fr=$(date --date="$last_updated" +"%d/%m/%Y %H:%M:%S %Z") market_cap=$(echo "$quote" | jq -r '.market_cap') # Capitalisation (Mrd) ($103,47 Mrd ~= €95,81 Mrd) market_cap_dominance=$(echo "$quote" | jq -r '.market_cap_dominance') # Dominance sur le marché (%) (2,94%) fully_diluted_market_cap=$(echo "$quote" | jq -r '.fully_diluted_market_cap') # Cap. du marché totlalement diluée (Mrd) ($103,47 Mrd ~= €95,81 Mrd) # devise 2 (USD) if [ -n "$currency2" ]; then coin2=$(echo "$request2" | jq -r '.data' | jq -r $filter) filter3=".$currency2" quote2=$(echo "$coin2" | jq -r '.quote' | jq -r $filter3) price2=$(echo "$quote2" | jq -r '.price') fi : <<'END_COMMENT' blabla END_COMMENT # Check cryptos we have (from .cryptos.yaml) z=$(cat $cryptos_list | yq '.lots[] | select(.symbol == "'${c}'")') ### OK ### #z=$(yq '.[] | select(.symbol == "'${c}'")' <<< "$b") #echo "z: $z" if [ ! -z "$z" ]; then quantity=$(yq '.quantity' <<< $z) else quantity=0 fi ### /OK ### # Côté variables #COLOR_24h=$([ $(CheckSign "$percent_change_24h") == "+" ] && echo ${COLOR_GREEN} || echo ${COLOR_RED}) #COLOR_7d=$([ $(CheckSign "$percent_change_7d") == "+" ] && echo ${COLOR_GREEN} || echo ${COLOR_RED}) #COLOR_30d=$([ $(CheckSign "$percent_change_30d") == "+" ] && echo ${COLOR_GREEN} || echo ${COLOR_RED}) #COLOR_60d=$([ $(CheckSign "$percent_change_60d") == "+" ] && echo ${COLOR_GREEN} || echo ${COLOR_RED}) #COLOR_90d=$([ $(CheckSign "$percent_change_90d") == "+" ] && echo ${COLOR_GREEN} || echo ${COLOR_RED}) # Côté mise-en-page COLOR_24h=$([ $(CheckSign "$percent_change_24h") == "+" ] && echo ${green} || echo ${red}) COLOR_7d=$([ $(CheckSign "$percent_change_7d") == "+" ] && echo ${green} || echo ${red}) COLOR_30d=$([ $(CheckSign "$percent_change_30d") == "+" ] && echo ${green} || echo ${red}) COLOR_60d=$([ $(CheckSign "$percent_change_60d") == "+" ] && echo ${green} || echo ${red}) COLOR_90d=$([ $(CheckSign "$percent_change_90d") == "+" ] && echo ${green} || echo ${red}) if [ -n "$currency2" ]; then # 2 currency if [ "$color_display" == true ];then # With % colors printf "${yellowbold}| %-15s | %-4s | %'9.2f | %'9.2f | %'14d |${reset}${COLOR_24h} %+6.1f%% ${COLOR_RESET}${yellowbold}|${COLOR_RESET}${COLOR_7d} %+6.1f%% ${COLOR_RESET}${yellowbold}|${COLOR_RESET}${COLOR_30d} %+6.1f%% ${COLOR_RESET}${yellowbold}|${COLOR_RESET}${COLOR_60d} %+6.1f%% ${COLOR_RESET}${yellowbold}|${COLOR_RESET}${COLOR_90d} %+6.1f%% ${COLOR_RESET}${yellowbold}| %-25s ${reset}\n" \ "$name" "$symbol" "$price" "$price2" "$volume_24h" "$percent_change_24h" "$percent_change_7d" "$percent_change_30d" "$percent_change_60d" "$percent_change_90d" "$last_updated_fr" 2>/dev/null else # Without colors printf "| %-15s | %-4s | %'9.2f | %'9.2f | %'14d | %+6.1f%% | %+6.1f%% | %+6.1f%% | %+6.1f%% | %+6.1f%% | %-25s \n" "$name" "$symbol" "$price" "$price2" "$volume_24h" "$percent_change_24h" "$percent_change_7d" "$percent_change_30d" "$percent_change_60d" "$percent_change_90d" "$last_updated_fr" 2>/dev/null fi else # 1 currency if [ "$color_display" == true ];then # With % colors printf "${yellowbold}| %-15s | %-4s | %'9.2f | %'14d |${reset}${COLOR_24h} %+6.1f%% ${COLOR_RESET}${yellowbold}|${COLOR_RESET}${COLOR_7d} %+6.1f%% ${COLOR_RESET}${yellowbold}|${COLOR_RESET}${COLOR_30d} %+6.1f%% ${COLOR_RESET}${yellowbold}|${COLOR_RESET}${COLOR_60d} %+6.1f%% ${COLOR_RESET}${yellowbold}|${COLOR_RESET}${COLOR_90d} %+6.1f%% ${COLOR_RESET}${yellowbold}| %-25s ${reset}\n" \ "$name" "$symbol" "$price" "$volume_24h" "$percent_change_24h" "$percent_change_7d" "$percent_change_30d" "$percent_change_60d" "$percent_change_90d" "$last_updated_fr" 2>/dev/null else # Without colors printf "| %-15s | %-4s | %'9.2f | %'14d | %+6.1f%% | %+6.1f%% | %+6.1f%% | %+6.1f%% | %+6.1f%% | %-25s \n" "$name" "$symbol" "$price" "$volume_24h" "$percent_change_24h" "$percent_change_7d" "$percent_change_30d" "$percent_change_60d" "$percent_change_90d" "$last_updated_fr" 2>/dev/null fi fi : <<'END_COMMENT2' END_COMMENT2 if (( $(bc <<< "$quantity > 0") )); then quantity=$(yq '.quantity' <<<$z) unit_cost=$(yq '.unit_cost' <<<$z) # EUR purchased=$(echo "$quantity * $unit_cost" | bc -l) valuations=$(echo "$quantity * $price" | bc -l) profit=$(echo "$valuations - $purchased" | bc -l) performance=$(echo "($valuations / $purchased - 1) * 100" | bc -l) # quantity -> Disponible # unit_cost -> Coût moyen # purchased -> Acheté # valuations -> Valoris. # profit -> +/- value # performance -> Perf. printf "| %-11s | %-9s | %-10s | %-9s | %-9s | %-8s \n" "Disponible" "Coût moyen" "Acheté" "Valoris." "+/- value" "Perf." 2>/dev/null if (( $(bc <<< "$performance > 0") )); then printf "${green}| %11s | %10.2f | %9.2f | %9.2f | %9.2f | %8.2f%% ${reset}\n\n" "$quantity" "$unit_cost" "$purchased" "$valuations" "$profit" "$performance" 2>/dev/null else printf "${red}| %11s | %10.2f | %9.2f | %9.2f | %9.2f | %8.2f%% ${reset}\n\n" "$quantity" "$unit_cost" "$purchased" "$valuations" "$profit" "$performance" 2>/dev/null fi else quantity=0 unit_cost=0 purchased=0 valuations=0 profit=0 fi ((counter++)) done echo -e "\n${italic}Script finished in $SECONDS seconds.${reset}"