diff --git a/README.md b/README.md index 143cd02..ca0dfb8 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ I thought this fits quite well to the cryptotrading world and that's why i chose # How to use/install Linux knowledge required! +Needed a running Docker install. Traefik suggested, see +https://gitea.ds9.dedyn.io/olli/debian.ansible.docker +https://gitea.ds9.dedyn.io/olli/debian.ansible.traefik.server + Download: ``` git clone https://gitea.ds9.dedyn.io/olli/dabo.git @@ -55,13 +59,78 @@ Build container: docker -l warn compose --ansi never build --progress=plain --pull --no-cache --force-rm ``` -Run: +Set Rights (UID 10000 for non-root-User in running container): +``` +chown -R 10000:10000 dabo data home +``` + +Edit docker-compose.yml or create docker-compose.override.yml to fit yout needs e.g. domain and network settings or basic auth, e.g. for traefik and letsencrypt: +``` +echo ' +services: + + dabo-bot: + networks: + - YOURNETWORK + + dabo-web: + labels: + # DOMAIN + - traefik.http.routers.dabo-YOURINSTANCENAME.rule=Host(`YOURDOMAIN`) + - traefik.http.routers.dabo-YOURINSTANCENAME.entrypoints=https + - traefik.http.routers.dabo-YOURINSTANCENAME.tls=true + # Proxy to service-port + - traefik.http.services.dabo-YOURINSTANCENAME.loadbalancer.server.port=80 + - traefik.http.routers.dabo-YOURINSTANCENAME.service=dabo-YOURINSTANCENAME + # cert via letsencrypt + - traefik.http.routers.dabo-YOURINSTANCENAME.tls.certresolver=letsencrypt + # activate secHeaders@file & basic auth + - traefik.http.routers.dabo-YOURINSTANCENAME.middlewares=secHeaders@file,basicauth + # Generate crypted password string with: echo $(htpasswd -nB YOURUSER) | sed -e s/\\$/\\$\\$/g + - traefik.http.middlewares.dabo-YOURINSTANCENAME.basicauth.users=YOURUSER:YOUR-GENERATED-CRYPTED-PASSWORD-STRING + # Traefik network + - traefik.docker.network=traefik + networks: + - traefik + +networks: + YOURNETWORK: + driver: bridge + driver_opts: + com.docker.network.bridge.name: YOURBRIDGE + traefik: + external: true + +' >docker-compose.override.yml +``` + +Create Secrets file for your API Key(s) +``` +# for Bitpanda +echo 'local API_TOKEN=YOUR_VERY_LOOOOOOONNNNGGGG_API_TOKEN_FROM_BITPANDA' >dabo/.bitpanda-secrets + +# or for Binance +echo 'local API_SECRET="YOUR_LONG_API_SECRET_FROM_BINANCE" +local API_KEY="YOUR_LONG_API_KEY_FROM_BINANCE" '>dabo/.binance-secrets +``` + +Edit your Config +Especially change URL, STOCK_EXCHANGE, FEE, CURRENCY,... to fit your needs. +``` +vim dabo/dabo-bot.conf +``` + +Prepare/Create a stretegy +IMPORTANT!!! THE DEFAULT STRATEGY MAY NOT FIT YOUR NEEDS OR WORK FPR YOU PROPERLY. SO YOU CAN LOOSE ALL YOUR MONEY!!! USE ON YOUR OWN RISK!!! + + +Run/Restart: ``` docker-compose down # if an old instance is running docker-compose up -d ``` -Logs: +Logs/Output: ``` docker-compose logs -f ``` diff --git a/dabo/dabo-bot.conf b/dabo/dabo-bot.conf index b199915..987b5bc 100755 --- a/dabo/dabo-bot.conf +++ b/dabo/dabo-bot.conf @@ -1,4 +1,7 @@ +# Webpage URL +URL="dabo.ds9.dedyn.io" + # The exchange we use for using the correct API (BINANCE or BITPANDA) STOCK_EXCHANGE="BITPANDA" @@ -32,7 +35,7 @@ INVEST="5" GOOD_MARKET_PERFORMANCE_INDEX="-1" # Stop all trading and sell everything if the complete balance shrinks under this value in ${CURRENCY} -EMERGENCY_STOP="190" +EMERGENCY_STOP="90" diff --git a/dabo/dabo-bot.sh b/dabo/dabo-bot.sh index b9be5b2..e62dec2 100755 --- a/dabo/dabo-bot.sh +++ b/dabo/dabo-bot.sh @@ -59,6 +59,7 @@ do TRADE_CMD='binance-api-call POST /api/v3/order "&symbol=TOKEN"eOrderQty=QUANTITY&side=ACTION&type=MARKET"' elif [ ${STOCK_EXCHANGE} = "BITPANDA" ] then + TOKEN_INFO_CMD="bitpanda_get_token_info" TRADE_CMD='bitpanda-api-call POST public/v1/account/orders "--header \"Content-Type: application/json\" --data \"{\\\"instrument_code\\\":\\\"TOKEN\\\",\\\"side\\\":\\\"ACTION\\\",\\\"type\\\":\\\"MARKET\\\",\\\"amount\\\":\\\"QUANTITY\\\"}\""' fi diff --git a/dabo/functions/binance_convert.sh b/dabo/functions/binance_convert.sh index aee5893..d50af79 100644 --- a/dabo/functions/binance_convert.sh +++ b/dabo/functions/binance_convert.sh @@ -68,7 +68,7 @@ cat ${g_tmp}/API_CMD_OUT >${f_CMDFILE}_OUT local f_trade_info_msg="CONVERT/TRADE - ${f_ACTION} ${f_ASSET}${f_CURRENCY} ${f_link} -Complete Overview: https://bot.ds9.dedyn.io/ +Complete Overview: https://${URL}/ Comment: ${f_COMMENT}" diff --git a/dabo/functions/bitpanda_get_token_info.sh b/dabo/functions/bitpanda_get_token_info.sh new file mode 100644 index 0000000..33d5391 --- /dev/null +++ b/dabo/functions/bitpanda_get_token_info.sh @@ -0,0 +1,32 @@ +function bitpanda_get_token_info { + + g_echo_note "RUNNING FUNCTION ${FUNCNAME} $@" + + local f_ASSET=$1 + local f_CURRENCY=$2 + f_QUANTITY=$3 + + # cleanup cache + [ -s BITPANDA_TOKEN_INFO_CMD_OUT ] && find BITPANDA_TOKEN_INFO_CMD_OUT -mmin +60 -or -empty -delete + if ! [ -s BITPANDA_TOKEN_INFO_CMD_OUT ] + then + bitpanda-api-call GET public/v1/instruments >BITPANDA_TOKEN_INFO_CMD_OUT + cat ${g_tmp}/API_CMD_OUT >BITPANDA_TOKEN_INFO_CMD_OUT + fi + + local f_ASSET_PRECISION=$(cat BITPANDA_TOKEN_INFO_CMD_OUT | jq -r ".[] | select(.state==\"ACTIVE\") | select(.quote.code==\"${f_CURRENCY}\") | select(.base.code==\"${f_ASSET}\") | .amount_precision") + local f_CURRENCY_PRECISION=$(cat BITPANDA_TOKEN_INFO_CMD_OUT | jq -r ".[] | select(.state==\"ACTIVE\") | select(.quote.code==\"${f_CURRENCY}\") | select(.base.code==\"${f_ASSET}\") | .quote.precision") + local f_ASSET_MINSIZE=$(cat BITPANDA_TOKEN_INFO_CMD_OUT | jq -r ".[] | select(.state==\"ACTIVE\") | select(.quote.code==\"${f_CURRENCY}\") | select(.base.code==\"${f_ASSET}\") | .min_size") + local f_ASSET_PRICE=$(tail -n1 "asset-histories/${f_ASSET}${f_CURRENCY}.history.csv" | cut -d"," -f2) + + if [ -n "$f_QUANTITY" ] && [ -n "$f_ASSET_PRICE" ] + then + if [ $(echo "${f_ASSET_MINSIZE} < ${f_QUANTITY}" | bc -l) -eq 0 ] + then + f_QUANTITY=$(echo "scale=${f_CURRENCY_PRECISION}; ${f_ASSET_MINSIZE}+1" | bc -l) + fi + f_ASSET_QUANTITY=$(echo "scale=${f_ASSET_PRECISION}; ${f_QUANTITY}/${f_ASSET_PRICE}" | bc -l | sed 's/^\./0./;') + fi + +} + diff --git a/dabo/functions/do_trade.sh b/dabo/functions/do_trade.sh index 2674d95..75b042a 100644 --- a/dabo/functions/do_trade.sh +++ b/dabo/functions/do_trade.sh @@ -1,7 +1,7 @@ function do_trade { # Info for log g_echo_note "RUNNING FUNCTION ${FUNCNAME} $@" - + # needed vars local f_ASSET=$1 local f_CURRENCY=$2 @@ -14,6 +14,8 @@ function do_trade { local f_link="https://www.coingecko.com/de/munze/$(egrep -i ^${f_ASSET}, COINGECKO_IDS | cut -d, -f2)" + # get stock exchange specific infos for trade (e.g. f_QUANTITY_LOT_CUT; f_MIN_NOTIONAL, precision and minsize) + ${TOKEN_INFO_CMD} ${f_ASSET} ${f_CURRENCY} ${f_QUANTITY} if [ ${STOCK_EXCHANGE} = "BINANCE" ] then @@ -49,36 +51,41 @@ ${FUNCNAME} $@" fi fi + + # prepare trading command - local f_CMDFILE="trade-histories/${f_DATE}-${f_CURRENCY}-${f_MIN_NOTIONAL}-TRADE_CMD" + local f_CMDFILE="trade-histories/${f_DATE}-${f_CURRENCY}-TRADE_CMD" [ ${STOCK_EXCHANGE} = "BINANCE" ] && echo "${TRADE_CMD}" | perl -pe "s/ACTION/${f_ACTION}/; s/TOKEN/${f_ASSET}${f_CURRENCY}/; s/QUANTITY/${f_QUANTITY}/" >${f_CMDFILE} - [ ${STOCK_EXCHANGE} = "BITPANDA" ] && echo "${TRADE_CMD}" | perl -pe "s/ACTION/${f_ACTION}/; s/TOKEN/${f_ASSET}_${f_CURRENCY}/; s/QUANTITY/${f_QUANTITY}/" >${f_CMDFILE} + [ ${STOCK_EXCHANGE} = "BITPANDA" ] && echo "${TRADE_CMD}" | perl -pe "s/ACTION/${f_ACTION}/; s/TOKEN/${f_ASSET}_${f_CURRENCY}/; s/QUANTITY/${f_ASSET_QUANTITY}/" >${f_CMDFILE} # trade g_echo_note "Command: $(cat ${f_CMDFILE})" + cat ${f_CMDFILE} #g_runcmd g_retrycmd sh ${f_CMDFILE} >${f_CMDFILE}_OUT . ${f_CMDFILE} cat ${g_tmp}/API_CMD_OUT >${f_CMDFILE}_OUT - g_echo_note "Command Output: $(cat ${f_CMDFILE}_OUT)" - + g_echo_note "Command Output: $(cat ${f_CMDFILE}_OUT)" + # workaround for "insufficient balance" error. lower quantity in 0.1 steps and try again (for values loosing while selling) - if [ "${f_ACTION}" = "sell" ] && grep -q "Account has insufficient balance for requested action." ${f_CMDFILE}_OUT + if [ ${STOCK_EXCHANGE} = "BINANCE" ] then - g_echo_note "workaround for \"insufficient balance\" error." - local f_tries=10 - local f_try=1 - until (grep -q "FILLED" ${f_CMDFILE}_OUT) - do - sleep 1 - f_QUANTITY=$(echo "$f_QUANTITY-0.1" | bc -l | sed 's/^\./0./;') - g_echo_note "lower $f_QUANTITY by -0.1" - [ ${STOCK_EXCHANGE} = "BINANCE" ] && echo "${TRADE_CMD}" | perl -pe "s/ACTION/${f_ACTION}/; s/TOKEN/${f_ASSET}${f_CURRENCY}/; s/QUANTITY/${f_QUANTITY}/" >${f_CMDFILE} - [ ${STOCK_EXCHANGE} = "BITPANDA" ] && echo "${TRADE_CMD}" | perl -pe "s/ACTION/${f_ACTION}/; s/TOKEN/${f_ASSET}_${f_CURRENCY}/; s/QUANTITY/${f_QUANTITY}/" >${f_CMDFILE} - #g_runcmd g_retrycmd sh ${f_CMDFILE} >${f_CMDFILE}_OUT - . ${f_CMDFILE} - cat ${g_tmp}/API_CMD_OUT >${f_CMDFILE}_OUT - [ ${f_try} -eq ${f_tries} ] && break - ((f_try=f_try+1)) - done + if [ "${f_ACTION}" = "sell" ] && grep -q "Account has insufficient balance for requested action." ${f_CMDFILE}_OUT + then + g_echo_note "workaround for \"insufficient balance\" error." + local f_tries=10 + local f_try=1 + until (grep -q "FILLED" ${f_CMDFILE}_OUT) + do + sleep 1 + f_QUANTITY=$(echo "$f_QUANTITY-0.1" | bc -l | sed 's/^\./0./;') + g_echo_note "lower $f_QUANTITY by -0.1" + echo "${TRADE_CMD}" | perl -pe "s/ACTION/${f_ACTION}/; s/TOKEN/${f_ASSET}${f_CURRENCY}/; s/QUANTITY/${f_QUANTITY}/" >${f_CMDFILE} + #g_runcmd g_retrycmd sh ${f_CMDFILE} >${f_CMDFILE}_OUT + . ${f_CMDFILE} + cat ${g_tmp}/API_CMD_OUT >${f_CMDFILE}_OUT + [ ${f_try} -eq ${f_tries} ] && break + ((f_try=f_try+1)) + done + fi fi @@ -89,7 +96,7 @@ ${FUNCNAME} $@" local f_trade_info_msg="TRADE - ${f_ACTION} ${f_ASSET}${f_CURRENCY} ${f_link} -Complete Overview: https://bot.ds9.dedyn.io/ +Complete Overview: https://${URL}/ Comment: ${f_COMMENT}" @@ -100,9 +107,15 @@ Comment: ${f_COMMENT}" [ ${STOCK_EXCHANGE} = "BINANCE" ] && local f_COMMISSION=$(cat ${f_CMDFILE}_OUT | grep '^{' | jq -r .fills[].commission) | head -n1 [ ${STOCK_EXCHANGE} = "BINANCE" ] && local f_COMMISSIONASSET=$(cat ${f_CMDFILE}_OUT | grep '^{' | jq -r .fills[].commissionAsset | head -n1) - [ ${STOCK_EXCHANGE} = "BITPANDA" ] && local f_PRICE=$(cat ${f_CMDFILE}_OUT | grep '^{' | jq -r .price | head -n1) - [ ${STOCK_EXCHANGE} = "BITPANDA" ] && local f_COMMISSION=$(echo "scale=2; $f_PRICE/100*${FEE}" | bc -l) - [ ${STOCK_EXCHANGE} = "BITPANDA" ] && local f_COMMISSIONASSET="EUR" + if [ ${STOCK_EXCHANGE} = "BITPANDA" ] + then + sleep 10 + bitpanda-api-call GET public/v1/account/trades + cat ${g_tmp}/API_CMD_OUT >${f_CMDFILE}_OUT_BITPANDA_TRADE + local f_PRICE=$(cat ${f_CMDFILE}_OUT_BITPANDA_TRADE | jq -r ".trade_history | .[] | select(.trade.order_id==\"${f_STATUS}\") | .trade.price") + local f_COMMISSION=$(cat ${f_CMDFILE}_OUT_BITPANDA_TRADE | jq -r ".trade_history | .[] | select(.trade.order_id==\"${f_STATUS}\") | .fee.fee_amount") + local f_COMMISSIONASSET=$(cat ${f_CMDFILE}_OUT_BITPANDA_TRADE | jq -r ".trade_history | .[] | select(.trade.order_id==\"${f_STATUS}\") | .fee.fee_currency") + fi echo "${f_DATE},${f_ACTION},${f_CMDFILE}_OUT,${f_QUANTITY} ${f_CURRENCY},${f_PRICE},${f_COMMISSION} ${f_COMMISSIONASSET},${f_COMMENT}" | head -n1 >>trade-histories/${f_ASSET}${f_CURRENCY}.history.csv if [ "${f_ACTION}" = "buy" ] diff --git a/dabo/functions/get_assets.sh b/dabo/functions/get_assets.sh index b92ad83..455b6dc 100644 --- a/dabo/functions/get_assets.sh +++ b/dabo/functions/get_assets.sh @@ -95,7 +95,7 @@ function get_assets { . /tmp/parallel # cleanup trashlines (possibly generated by kill further of this progress) - #sed -i "/[0-9]$(date +%Y)-/d" asset-histories/* + sed -i "/[0-9]$(date +%Y)-/d" asset-histories/* # get MSCI World Index for analysis echo "wget -q -O - https://www.boerse.de/realtime-kurse/MSCI-World/XC0009692739 | egrep 'itemprop=\"price\" content=\"[0-9]+\.[0-9]+\"' | cut -d\\\" -f6" >MSCI_WORLD_CMD diff --git a/dabo/home/.keep b/dabo/home/.keep new file mode 100644 index 0000000..eb853fc --- /dev/null +++ b/dabo/home/.keep @@ -0,0 +1 @@ +needed-dir diff --git a/dabo/htdocs/.keep b/dabo/htdocs/.keep new file mode 100644 index 0000000..eb853fc --- /dev/null +++ b/dabo/htdocs/.keep @@ -0,0 +1 @@ +needed-dir diff --git a/docker-compose.yml b/docker-compose.yml index e9e48db..9143c0a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,9 +13,6 @@ services: - ./home:/dabo/home:rw - /usr/local/bin/notify.sh:/usr/local/bin/notify.sh:ro - /etc/localtime:/etc/localtime:ro - networks: - - dabo--network - dabo-web: image: nginx:latest @@ -23,31 +20,4 @@ services: volumes: - ./data:/usr/share/nginx/html:ro - /etc/localtime:/etc/localtime:ro - networks: - - traefik - labels: - - traefik.enable=true - # HTTPS - - traefik.http.routers.dabo-web.rule=Host(`dabo.ds9.dedyn.io`) - - traefik.http.routers.dabo-web.entrypoints=https - - traefik.http.routers.dabo-web.tls=true - # Proxy to service-port - - traefik.http.services.dabo-web.loadbalancer.server.port=80 - - traefik.http.routers.dabo-web.service=dabo-web - # cert via letsencrypt - - traefik.http.routers.dabo-web.tls.certresolver=letsencrypt - # activate secHeaders@file - - traefik.http.routers.dabo-web.middlewares=secHeaders@file - # Traefik network - - traefik.docker.network=traefik - - -networks: - dabo--network: - driver: bridge - driver_opts: - com.docker.network.bridge.name: br-dabo - traefik: - external: true -