diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..ba6518c Binary files /dev/null and b/.DS_Store differ diff --git a/Dockerfile b/Dockerfile index 5dbd75f..0227128 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,24 @@ -FROM ubuntu:latest -RUN apt-get update -RUN apt-get install -y \ - python3 \ - python3-pip +FROM python:3.9.18 +ENV PYTHON_TA_LIB_PACKAGE_NAME "TA-lib" +ENV PYTHON_TA_LIB_VERSION "0.4.28" + +RUN apt-get -y update RUN python3 -m pip install --upgrade pip -WORKDIR /bot -COPY ta-lib-0.4.0-src.tar.gz /bot/ -RUN tar -xzf ta-lib-0.4.0-src.tar.gz -WORKDIR /bot/ta-lib -RUN ./configure -RUN make -RUN make install -RUN python3 -m pip install \ - ta-lib -RUN python3 -m pip install \ - colorama -WORKDIR /bot -RUN python3 -m pip install \ - requests -RUN python3 -m pip install \ - binance-futures-connector -RUN apt-get install -y iputils-ping \ No newline at end of file +RUN pip3 install numpy +RUN pip3 install requests +RUN pip3 install setuptools +RUN cd /tmp +RUN curl -L -O http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz \ + && tar -xzf ta-lib-0.4.0-src.tar.gz \ + && cd ta-lib/ \ + && sed -i 's/^#define TA_IS_ZERO(v).*$/#define TA_IS_ZERO(v) (((-0.000000000000000001)", - "secret": "", - "base_url": "https://fapi.binance.com" - }, - "LINE":{ - "token":"", - "enable": false - }, - "pairs":[ - { - "enable": false, - "crypto": "BTC", - "asset": "BUSD", - "strategy": "MACDez", - "interval_seconds": 180, - "color":"LIGHTYELLOW_EX" - },{ - "enable": true, - "crypto": "ADA", - "asset": "BUSD", - "strategy": "test", - "interval_seconds": 10, - "color":"LIGHTMAGENTA_EX" - } - ] -} \ No newline at end of file diff --git a/config/config.dist.json b/config/config.dist.json new file mode 100644 index 0000000..696a539 --- /dev/null +++ b/config/config.dist.json @@ -0,0 +1,66 @@ +{ + "disclaimer":"This JSON file support BinanceSloth version 1.0 and above.", + "customWD":{ + "enable":false, + "linux":"/home/pi/Bots", + "windows":"C:\\Playground\\official\\jakt-bot" + }, + "BinanceAPI":{ + "name":"", + "apiName": "", + "key":"", + "secret":"", + "base_url":"https://fapi.binance.com/api/v3/" + }, + "LINE":{ + "enable":false, + "enableErrorNotify":true, + "token":"", + "api":"https://notify-api.line.me/api/notify" + }, + "static":{ + "colors":[ + "LIGHTBLACK_EX", + "LIGHTBLUE_EX", + "LIGHTCYAN_EX", + "LIGHTGREEN_EX", + "LIGHTMAGENTA_EX", + "LIGHTRED_EX", + "LIGHTWHITE_EX", + "LIGHTYELLOW_EX" + ] + }, + "reporter":{ + "interval":900 + }, + "loggingLevel":"INFO", + "timezone":"Asia/Bangkok", + "pairs":[ + { + "enable":true, + "crypto":"SOL", + "asset":"USDT", + "strategy":"K90_v1", + "used_asset_percent":25, + "interval_when_opened":3600, + "interval_when_closed":3600, + "priority":1, + "lineAlert":0, + "color":"LIGHTMAGENTA_EX", + "testOrder":false + }, + { + "enable":false, + "crypto":"BTC", + "asset":"USDT", + "strategy":"K90_v1", + "used_asset_percent":50, + "interval_when_opened":300, + "interval_when_closed":600, + "priority":1, + "lineAlert":0, + "color":"LIGHTGREEN_EX", + "testOrder":true + } + ] +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index d8313fe..703190b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,15 @@ -version: '3' +version: '3.2' services: - bot: + binancesloth: build: dockerfile: "Dockerfile" context: . + container_name: "binancesloth" volumes: - - .:/bot - working_dir: /bot - command: ["python3", "jakt_v0.18.1.py"] \ No newline at end of file + - type: bind + source: ./ + target: /app/binancesloth + working_dir: /app/binancesloth + stdin_open: true # docker run -i + tty: true # docker run -t + command: ["python3", "main-v0.28.1.py", "-c", "/app/binancesloth/config/config.json"] \ No newline at end of file diff --git a/jakt_v0.18.1.py b/jakt_v0.18.1.py deleted file mode 100644 index a74563a..0000000 --- a/jakt_v0.18.1.py +++ /dev/null @@ -1,851 +0,0 @@ -from marshal import dumps -#from binhex import Error -from sys import exit -from os import system, path, chdir -from collections import defaultdict -import sched, time -#from cv2 import CALIB_NINTRINSIC -from numpy.core.fromnumeric import resize -import requests -import numpy as np -from requests import models -import talib as ta -#from binance.client import Client -from binance.um_futures import UMFutures as Futures -from binance.error import ClientError -import json -import logging -import random -import urllib3 -import subprocess -import platform -import pathlib -urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) -from colorama import Fore, Style, init as colorama_init - - -class Bot: - def __init__(self, name, color, lineToken, lineNotify=False): - self.name = name - self.lineToken = lineToken - self.lineNotify = lineNotify - #self.color = random.choice(["LIGHTMAGENTA_EX", "LIGHTYELLOW_EX", "LIGHTCYAN_EX", "LIGHTGREEN_EX", "LIGHTRED_EX"]) - self.color = color - - def pprint(self, msg): - print(json.dumps(msg, indent=4, sort_keys=False)) - - def print(self, msg, type="info"): - if True: - #print(msg) - if type == "info": - if self.color == "LIGHTMAGENTA_EX": - LOGGER.info(Fore.LIGHTMAGENTA_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg)) - elif self.color == "LIGHTYELLOW_EX": - LOGGER.info(Fore.LIGHTYELLOW_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg)) - elif self.color == "LIGHTCYAN_EX": - LOGGER.info(Fore.LIGHTCYAN_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg)) - elif self.color == "LIGHTGREEN_EX": - LOGGER.info(Fore.LIGHTGREEN_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg)) - elif self.color == "LIGHTRED_EX": - LOGGER.info(Fore.LIGHTRED_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg)) - - elif type == "debug": - LOGGER.debug("{} {}".format(self.name, msg)) - elif type == "warning": - LOGGER.warning("{} {}".format(self.name, msg)) - if self.lineNotify: - try: - self.LINE_notify(msg) - #print("Line notices!") - except: - print("{} LINE notification error".format(self.name)) - - def LINE_notify(self, msg): - url = 'https://notify-api.line.me/api/notify' - headers = {'Authorization':'Bearer '+self.lineToken} - return requests.post(url, headers=headers , data = {'message':msg}, files=None) - - - def getTemp_CPU(self): - response = subprocess.run(['cat', '/sys/class/thermal/thermal_zone0/temp'], stdout=subprocess.PIPE) - return ''.join(e for e in str(response.stdout) if e.isnumeric()) - - def ping(self, hostname="www.google.com", count=1): - response = None - if platform.system() == "Linux": - response = system("ping -c " + str(count) + " " + hostname + " > /dev/null 2>&1") - elif platform.system() == "Windows": - response = system("ping -n " + str(count) + " " + hostname + " > $null ") - - if response == 0: - return 1 - else: - return 0 - - def waitForInternet(self, retry_sec_min=1, increment_sec=10, retry_sec_max=60*3): - seconds = retry_sec_min - while not self.ping(): - self.print( - "internet connection "+Fore.RED+"failed"+Style.RESET_ALL - +", retry in "+Fore.GREEN+"{}".format(seconds)+Style.RESET_ALL+" second(s)" - ) - time.sleep(seconds) - if seconds < retry_sec_max: - seconds += increment_sec - self.print(Fore.GREEN + "OK" + Style.RESET_ALL) - - def sleep(self, min=1, max=-1): - if max == -1: - time.sleep(min) - else: - time.sleep(random.randint(min, max)) - - def connectBinanceAPI(self, retry_sec_min=5, increment_sec=10, retry_sec_max=60*3): - seconds = retry_sec_min - connectionPassed = 0 - connectionTotal = 1 - - while connectionPassed < connectionTotal: - try: - response = FUTURES.account() - connectionPassed += 1 - except ClientError as e: - self.print(e.error_message) - self.print( - "Binance API connection "+Fore.RED+"failed"+Style.RESET_ALL - +", attempt to fix the problem..." - ) - self.print("time syncing function is under being developed!") - #### - # fixing goes here... - # under construction! - #### - self.print( - "attempted, connect again in "+Fore.GREEN+"{}".format(seconds)+Style.RESET_ALL+" second(s)" - ) - time.sleep(seconds) - if seconds < retry_sec_max: - seconds += increment_sec - - if connectionPassed >= connectionTotal: - self.print(Fore.GREEN + "connected" + Style.RESET_ALL) - -class JAKT(Bot): - def __init__( - self, - symbol=None, - strategy=None, - color=None, - lineToken=None, - lineNotify=False, - priority=1, - interval_sec=5, - action=False - ): - Bot.__init__(self, symbol[0]+symbol[1], color, lineToken, lineNotify) - self.symbol = symbol[0]+symbol[1] - self.cryptoName = symbol[0] - self.assetName = symbol[1] - self.strategy = strategy - self.interval_sec = interval_sec - self.priority = priority - self.availableBalance = None - self.positionAmt = 0 - self.positionState = None # longed, shorted, or closed - #self.orderAmt = 0 - self.orderAmtMin = 0.001 # for BTC only, not sure for the others - self.leverage = 0 - #self.flag_ema_vector = None - self.action = action - self.initialize() - - # initialization - def initialize(self): - - self.print("initialization started...") - if True: - self.print("checking internet connection...") - self.waitForInternet() - self.sleep(1, 2) - - if True: - self.print("connecting to Binance API...") - self.connectBinanceAPI() - self.sleep(1, 2) - - # for debugging only - if False: - self.pprint(self.fetchLeverageBracket()) - exit() - - if True: - self.print("syncing position...") - self.syncPosition() - self.print( - "current position: " - +Fore.GREEN - +"{} ".format(self.positionState) - +Style.RESET_ALL - +", amt:" - +Fore.GREEN - +" {} {}".format(self.positionAmt, self.cryptoName) - +Style.RESET_ALL - ) - self.sleep(1, 2) - - # close all opened position if needed; default: False - if False: - self.print("closing all positions if exist...") - self.print(self.closeAllPositions()) - self.sleep(2, 3) - - if True: - self.syncAvailableBalance() - self.print("available balance: "+Fore.GREEN+"{} {}".format(self.availableBalance, self.assetName)+Style.RESET_ALL) - self.sleep(1, 2) - - #self.print("syncing leverage...") - self.syncLeverage() - #self.pprint(self.fetchLeverageBracket()) - self.print("synced leverage: "+Fore.GREEN+"{}x".format(self.leverage)+Style.RESET_ALL) - self.sleep(1, 2) - - if False: - #self.print("updating order amount...") - self.updateOrderAmt() - self.print("max amount per order: "+Fore.GREEN+"{} {}".format(self.orderAmt, self.cryptoName)+Style.RESET_ALL) - self.sleep(1, 2) - - self.print( - "set by "+ - Fore.GREEN+ - "{}".format(self.strategy)+ - Style.RESET_ALL+ - " strategy, and scheduled with "+ - Fore.GREEN+"{} seconds".format(self.interval_sec)+ - Style.RESET_ALL+ - " interval" - ) - self.sleep(1, 2) - - self.print("initialization complete.") - self.print("--------------------------------") - self.sleep(1, 2) - - def API_connect(self): - self.print(FUTURES.ping()) - - - # taking a short a position with desire quantity, or self.orderAmt - def short(self, quantity=-1): - if quantity < 0: - quantity = self.positionAmt - response = "self.action is set to False" - if self.action: - params = { - 'symbol': self.symbol, - 'side': 'SELL', - 'type': 'MARKET', - 'quantity': abs(quantity) - } - try: - response = FUTURES.new_order(**params) - #self.positionState = 'shorted' - time.sleep(1) - self.syncPosition() - self.print(Fore.GREEN+"Shorted: {} {}".format(self.positionAmt, self.cryptoName)+Style.RESET_ALL) - except ClientError as e: - self.print("Short position with {} qty failed. {}".format(quantity, e.error_message)) - response = e.error_message - return response - - - # taking a long position with desire quantity, or self.orderAmt - def long(self, quantity=-1): - if quantity < 0: - quantity = self.positionAmt - response = "self.action is set to False" - if self.action: - params = { - 'symbol': self.symbol, - 'side': 'BUY', - 'type': 'MARKET', - 'quantity': abs(quantity) - } - try: - response = FUTURES.new_order(**params) - #self.positionState = 'longed' - time.sleep(1) - self.syncPosition() - self.print(Fore.GREEN+"Longed: {} {}".format(self.positionAmt, self.cryptoName)+Style.RESET_ALL) - except ClientError as e: - self.print("Long position with {} {} failed. {}".format(quantity, self.cryptoName, e.error_message)) - response = e.error_message - return response - - - def close(self): - response = "self.action is set to False" - if self.action: - self.syncPosition() - if self.positionAmt > 0: - self.short(self.positionAmt) - self.positionState = "closed" - response = "longed positions are closed" - elif self.positionAmt < 0: - self.long(self.positionAmt) - self.positionState = "closed" - response = "shorted positions are closed" - else: - response = "no opened position to be closed" - time.sleep(1) - self.syncPosition() - return response - - def closeAllPositions(self): - response = "self.action is set to False" - if self.action: - self.syncPosition() - if self.positionAmt > 0: - self.short(self.positionAmt) - response = "longed positions are closed" - elif self.positionAmt < 0: - self.long(self.positionAmt) - response = "shorted positions are closed" - else: - response = "no opened position to be closed" - time.sleep(1) - self.syncPosition() - self.positionState = "closed" - return response - - - def syncPosition(self): - self.positionAmt = float(self.fetchPositionAmt()) - if self.positionAmt > 0: - self.positionState = "longed" - elif self.positionAmt < 0: - self.positionState = "shorted" - elif self.positionAmt == 0: - self.positionState = "closed" - else: - self.print("syncPosition error") - - - # fetch klines - def fetch(self, tf, samples=50): - return_data = [] - for each in requests.get( - "https://fapi.binance.com/fapi/v1/klines?symbol={}&interval={}&limit={}".format( - #"https://api.binance.com/api/v3/klines?symbol={}&interval={}&limit={}".format( - self.symbol, tf, samples - ) - ).json(): - return_data.append(float(each[4])) # we need only closed price - return np.array(return_data) - - - def fetchPositionAmt(self): - return float(self.fetchOpenPosition()["positionAmt"]) - - - def fetchAccount(self): - try: - response = FUTURES.account(ecvWindow=2000) - except ClientError as e: - response = e - return response - - - def fetchOpenOrders(self): - try: - response = FUTURES.get_open_orders(symbol=self.symbol, recvWindow=2000) - except ClientError as e: - response = e - return response - - - def fetchBalance(self): - balance = None - try: - response = FUTURES.balance(recvWindow=2000) - for asset in response: - if asset["asset"] == self.assetName: - return asset["availableBalance"] - except ClientError as e: - return e - - - def fetchLeverageBracket(self): - leverages = FUTURES.leverage_brackets() - for leverage in leverages: - if leverage["symbol"] == self.symbol: - for bracket in leverage["brackets"]: - #if bracket["initialLeverage"] == self.leverage: - return bracket - - - def fetchLeverage(self): - for position in self.fetchAccount()["positions"]: - if position["symbol"] == self.symbol: - return position["leverage"] - - - # not tested yet, but might works - def fetchUnPNL(self): - account = self.fetchAccount() - return account["totalCrossUnPnl"] - - - def fetchOpenPosition(self): - try: - risks = FUTURES.get_position_risk(recvWindow=6000) - for risk in risks: - if risk["symbol"] == self.symbol: - return risk - except ClientError as e: - return e - - - def syncLeverage(self): - self.leverage = int(self.fetchLeverage()) - return self.leverage - - - def syncAvailableBalance(self): - self.availableBalance = float(self.fetchBalance()) - - - # going to be deleted, not neccessary! - def updateOrderAmt(self): - leverage = self.fetchLeverageBracket() - self.orderAmt = leverage["maintMarginRatio"] - - - def calOpenLoss(self): - # = Number of contract * abs val {min[0, direction of order * (markprice - orderprice)]} - #return self.orderAmt*abs(min([0, 1*markprice-orderprice])) - pass - - - def calInitMargin(self, price): - return price*self.orderAmtMin/self.leverage - - - def calMaxQTY(self, price, percent=100): - # under implementation! This could return the float, not int!? - return int((percent/100*self.availableBalance)/self.calInitMargin(price)) - - - def calQTY(self, price, percent=100): - # under implementation! This could return the float, not int!? - return self.calMaxQTY(price, percent) * self.orderAmtMin - - - def getEMA(self, data, ema=5): - return round(ta.EMA(data, ema)[-2], 2) - - - def getEMA_comp_cross(self, data, ema_small=5, ema_large=10): - # - # p and n stand for positive and negative respectively - # - emas_small = ta.EMA(data, ema_small) - emas_large = ta.EMA(data, ema_large) - - if ((emas_small[-4]-emas_large[-4]) > 0) and ((emas_small[-3]-emas_large[-3]) > 0) and ((emas_small[-2]-emas_large[-2]) > 0): - return"ppp" - elif ((emas_small[-4]-emas_large[-4]) > 0) and ((emas_small[-3]-emas_large[-3]) > 0) and ((emas_small[-2]-emas_large[-2]) <= 0): - return "ppn" - elif ((emas_small[-4]-emas_large[-4]) > 0) and ((emas_small[-3]-emas_large[-3]) <= 0) and ((emas_small[-2]-emas_large[-2]) > 0): - return "pnp" - elif ((emas_small[-4]-emas_large[-4]) > 0) and ((emas_small[-3]-emas_large[-3]) <= 0) and ((emas_small[-2]-emas_large[-2]) <= 0): - return "pnn" - elif ((emas_small[-4]-emas_large[-4]) <= 0) and ((emas_small[-3]-emas_large[-3]) > 0) and ((emas_small[-2]-emas_large[-2]) > 0): - return "npp" - elif ((emas_small[-4]-emas_large[-4]) <= 0) and ((emas_small[-3]-emas_large[-3]) > 0) and ((emas_small[-2]-emas_large[-2]) <= 0): - return "npn" - elif ((emas_small[-4]-emas_large[-4]) <= 0) and ((emas_small[-3]-emas_large[-3]) <= 0) and ((emas_small[-2]-emas_large[-2]) > 0): - return "nnp" - elif ((emas_small[-4]-emas_large[-4]) <= 0) and ((emas_small[-3]-emas_large[-3]) <= 0) and ((emas_small[-2]-emas_large[-2]) <= 0): - return "nnn" - - - def getEMA_vector(self, data, ema=5, depth=3, step=1): - # - # u and d stand for up and down respectively - # - emas = ta.EMA(data, ema) - if depth == 2: - if emas[-(step+3)] > emas[-(step+2)] > emas[-(step+1)]: - return "dd" - elif emas[-(step+3)] > emas[-(step+2)] <= emas[-(step+1)]: - return "du" - elif emas[-(step+3)] <= emas[-(step+2)] > emas[-(step+1)]: - return "ud" - elif emas[-(step+3)] <= emas[-(step+2)] <= emas[-(step+1)]: - return "uu" - elif depth == 3: - if emas[-(step+4)] > emas[-(step+3)] > emas[-(step+2)] > emas[-(step+1)]: - return "ddd" - elif emas[-(step+4)] > emas[-(step+3)] > emas[-(step+2)] <= emas[-(step+1)]: - return "ddu" - elif emas[-(step+4)] > emas[-(step+3)] <= emas[-(step+2)] > emas[-(step+1)]: - return "dud" - elif emas[-(step+4)] > emas[-(step+3)] <= emas[-(step+2)] <= emas[-(step+1)]: - return "duu" - elif emas[-(step+4)] <= emas[-(step+3)] > emas[-(step+2)] > emas[-(step+1)]: - return "udd" - elif emas[-(step+4)] <= emas[-(step+3)] > emas[-(step+2)] <= emas[-(step+1)]: - return "udu" - elif emas[-(step+4)] <= emas[-(step+3)] <= emas[-(step+2)] > emas[-(step+1)]: - return "uud" - elif emas[-(step+4)] <= emas[-(step+3)] <= emas[-(step+2)] <= emas[-(step+1)]: - return "uuu" - - - def getDeltaEMA(self, data, ema_small, ema_large): - return round(ta.EMA(data, ema_small)[-2]-ta.EMA(data, ema_large)[-2], 2) - - - def getMACD(self, data, fastperiod=12, slowperiod=26, signalperiod=9): - _, _, macdhist = ta.MACD(data, fastperiod, slowperiod, signalperiod) - return round(macdhist[-2], 2) - - - def getMACD_zcross(self, data, fastperiod=12, slowperiod=26, signalperiod=9): - # - # p and n stand for positive and negative respectively - # - _, _, macdhist = ta.MACD(data, fastperiod, slowperiod, signalperiod) - if macdhist[-4] > 0 and macdhist[-3] > 0 and macdhist[-2] > 0: - return"ppp" - elif macdhist[-4] > 0 and macdhist[-3] > 0 and macdhist[-2] <= 0: - return "ppn" - elif macdhist[-4] > 0 and macdhist[-3] <= 0 and macdhist[-2] > 0: - return "pnp" - elif macdhist[-4] > 0 and macdhist[-3] <= 0 and macdhist[-2] <= 0: - return "pnn" - elif macdhist[-4] <= 0 and macdhist[-3] > 0 and macdhist[-2] > 0: - return "npp" - elif macdhist[-4] <= 0 and macdhist[-3] > 0 and macdhist[-2] <= 0: - return "npn" - elif macdhist[-4] <= 0 and macdhist[-3] <= 0 and macdhist[-2] > 0: - return "nnp" - elif macdhist[-4] <= 0 and macdhist[-3] <= 0 and macdhist[-2] <= 0: - return "nnn" - - - def getMACD_vector(self, data, fastperiod=12, slowperiod=26, signalperiod=9, step=1): - # - # u and d stand for up and down respectively - # - if len(data)*2 <= slowperiod: - return "not enough data." - else: - _, _, macdhist = ta.MACD(data, fastperiod, slowperiod, signalperiod) - if macdhist[-(step+4)] > macdhist[-(step+3)] > macdhist[-(step+2)] > macdhist[-(step+1)]: - return "ddd" - elif macdhist[-(step+4)] > macdhist[-(step+3)] > macdhist[-(step+2)] <= macdhist[-(step+1)]: - return "ddu" - elif macdhist[-(step+4)] > macdhist[-(step+3)] <= macdhist[-(step+2)] > macdhist[-(step+1)]: - return "dud" - elif macdhist[-(step+4)] > macdhist[-(step+3)] <= macdhist[-(step+2)] <= macdhist[-(step+1)]: - return "duu" - elif macdhist[-(step+4)] <= macdhist[-(step+3)] > macdhist[-(step+2)] > macdhist[-(step+1)]: - return "udd" - elif macdhist[-(step+4)] <= macdhist[-(step+3)] > macdhist[-(step+2)] <= macdhist[-(step+1)]: - return "udu" - elif macdhist[-(step+4)] <= macdhist[-(step+3)] <= macdhist[-(step+2)] > macdhist[-(step+1)]: - return "uud" - elif macdhist[-(step+4)] <= macdhist[-(step+3)] <= macdhist[-(step+2)] <= macdhist[-(step+1)]: - return "uuu" - - - def getRSI(self, data): - return round(ta.RSI(data, 6)[-2], 2) - - - def getSAR(self, data, timeperiod=30): - return ta.SAR(data.high, data.low, acceleration=0, maximum=0) - - - def run(self): - predict = 'ready' - action = 'watch' - indy = defaultdict(dict) - - if self.strategy == "basic": - tf="1m" - closing_data = self.fetch(tf, 50) - indy["pair"] = self.symbol - indy[tf]["ema_dif"] = self.getDeltaEMA(closing_data, 5, 10) - indy[tf]["macd"] = self.getMACD(closing_data) - indy[tf]["rsi"] = self.getRSI(closing_data) - - if indy[tf]["ema_dif"] >= 0: - if indy[tf]["macd"] >= 0: # this TF says should take LONG - predict = 'long' - else: - if indy[tf]["macd"] < 0: - predict = 'short' - - if self.strategy == "simple33": - tf="3m" - closing_data = self.fetch(tf, 200) - indy["pair"] = self.symbol - indy[tf]["ema_19_vector"] = self.getEMA_vector(closing_data, ema=19, depth=3) - indy[tf]["ema_19"] = self.getEMA(data=closing_data, ema=19) - indy[tf]["ema_33"] = self.getEMA(data=closing_data, ema=33) - - if self.positionState == "closed": - # this condition is to consider when should taking long or short - if (indy[tf]["ema_19_vector"] == "uuu") and (indy[tf]["ema_19"] <= indy[tf]["ema_33"]): - # taking long position - self.long() - action="open longed order" - elif (indy[tf]["ema_19_vector"] == "ddd") and (indy[tf]["ema_19"] > indy[tf]["ema_33"]): - # taking short position - self.short() - action="open shorted order" - else: - action="waiting for the top and the dip" - - elif self.positionState == "longed": - if indy[tf]["ema_19_vector"] == "ddd": - # taking close position - self.close() - action="close a longed position" - else: - action="let longed profit run" - - elif self.positionState == "shorted": - if indy[tf]["ema_19_vector"] == "uuu": - # taking close position - self.close() - action="close a shorted position" - else: - action="let shorted profit run" - else: - self.print("self.positionState invalid: {}".format(self.positionState)) - - self.print("action: {}, position state: {}".format(action, self.positionState)) - - - elif self.strategy == "onlymacd30m": - tf="30m" - closing_data = self.fetch(tf, 50) - indy["pair"] = self.symbol - indy[tf]["ema_dif"] = self.getDeltaEMA(closing_data, ema_small=5, ema_large=10) - indy[tf]["macd"] = self.getMACD(closing_data, fastperiod=12, slowperiod=26, signalperiod=9) - indy[tf]["rsi"] = self.getRSI(closing_data) - - if indy[tf]["ema_dif"] >= 0: - if indy[tf]["macd"] >= 0: # this TF says should take LONG - predict = 'long' - else: - if indy[tf]["macd"] < 0: - predict = 'short' - - elif self.strategy == "testindy": - tf="1m" - closing_data = self.fetch(tf, 50) - indy["pair"] = self.symbol - indy[tf]["closed"] = closing_data[-2] - indy[tf]["ema_5"] = self.getEMA(closing_data, 5) - indy[tf]["ema_10"] = self.getEMA(closing_data, 10) - indy[tf]["macd"] = self.getMACD(closing_data, fastperiod=12, slowperiod=26, signalperiod=9) - indy[tf]["rsi"] = self.getRSI(closing_data) - self.pprint(indy) - - elif self.strategy == "MaoNoi1h": - tf="1h" - closing_data = self.fetch(tf, 200) - indy["pair"] = self.symbol - indy[tf]["ema_dif"] = self.getDeltaEMA(closing_data, 5, 10) - indy[tf]["macd"] = self.getMACD(closing_data, fastperiod=12, slowperiod=26, signalperiod=9) - #indy[tf]["rsi"] = self.getRSI(closing_data) - - if indy[tf]["macd"] > 0: - pass - else: - pass - elif self.strategy == "MaoJayNoi15m": - pass - elif self.strategy == "MaoKayNoi": - pass - elif self.strategy == "MaoToneNoi": - pass - elif self.strategy == "test": - tf="3m" - closing_data = self.fetch(tf, 330) - #response = FUTURES.change_leverage(symbol=self.symbol, leverage=1, recvWindow=6000) - #self.print("Available balance: {} {}".format(self.fetchBalance(), self.assetName)) - #self.pprint(self.getLeverage()) - #print(self.getTemp_CPU()) - #self.short() - #time.sleep(1) - #self.pprint(FUTURES.get_all_orders(self.symbol)[-1]) - #self.pprint(self.close_test()) - #self.pprint(FUTURES.get_all_orders(self.symbol, recvWindow=2000)) - #print(self.close(self.getRecentOrders()["orderId"])) - #display("Orders: {}".format(self.fetchOpenOrders())) - #self.print(closing_data[0]) - #self.print(self.calQTY(closing_data[0], 100)) - #self.pprint(self.fetchLeverageBracket()) - if True: - self.updateOrderAmt() - self.print("max amount per order: "+Fore.GREEN+"{} {}".format(self.orderAmt, self.cryptoName)+Style.RESET_ALL) - #self.print("EMA_vector: {}".format(self.getEMA_vector(closing_data, ema=5, depth=2))) - #self.print("MACD_vector: {}".format(self.getMACD_vector(closing_data, fastperiod=12, slowperiod=26, signalperiod=9))) - - self.print(self.getMACD_vector(closing_data, fastperiod=99, slowperiod=150, signalperiod=99, step=1)) - self.print("end test strategy") - #time.sleep(30) - - elif self.strategy == "testOpenOrder": - tf="15m" - closing_data = self.fetch(tf, 1) - - self.print("testing long...") - self.long(self.calQTY(closing_data[0], 100)) - time.sleep(5) - self.print("testing close...") - self.close() - time.sleep(5) - self.print("testing short...") - self.short(self.calQTY(closing_data[0], 100)) - time.sleep(5) - self.print("testing close...") - self.close() - - self.print("end test strategy") - #time.sleep(30) - - elif self.strategy == "MACD99-150-99": - tf="3m" - closing_data = self.fetch(tf, 300) - - self.print("{} operated".format(self.strategy)) - #time.sleep(30) - - elif self.strategy == "MACDez": - action="doing nothing" - tf="3m" - - closing_data = self.fetch(tf, 200) - indy["QTY_percent"] = 50 - indy["QTY"] = self.calQTY(closing_data[0], percent=indy["QTY_percent"]) - indy[tf]["MACD_zcross"] = self.getMACD_zcross(closing_data, fastperiod=66, slowperiod=100, signalperiod=33) - indy[tf]["MACD_vector"] = self.getMACD_vector(closing_data, fastperiod=66, slowperiod=100, signalperiod=33, step=1) - self.pprint(indy) - - if self.positionState == "closed": - # this condition is to consider when should taking long or short - if (indy[tf]["MACD_zcross"] == "nnp"): - # taking long position - self.long(indy["QTY"]) - action="open longed order" - elif (indy[tf]["MACD_zcross"] == "ppn"): - # taking short position - self.short(indy["QTY"]) - action="open shorted order" - else: - action="waiting for the top and the dip" - - elif self.positionState == "longed": - if indy[tf]["MACD_vector"] == "udd" or indy[tf]["MACD_vector"] == "ddd": - # taking close position - self.close(indy["QTY"]) - action="close a longed position" - else: - action="let longed profit run" - - elif self.positionState == "shorted": - if indy[tf]["MACD_vector"] == "duu" or indy[tf]["MACD_vector"] == "uuu": - # taking close position - self.close(indy["QTY"]) - action="close a shorted position" - else: - action="let shorted profit run" - else: - self.print("self.positionState invalid: {}".format(self.positionState)) - - self.print("{} just {}, current position state: {}".format(self.strategy, action, self.positionState)) - #sleep(10) - - else: - self.print("no strategy is set!") - - #self.print("suggest: {}, act: {}, has {}".format(predict, action, self.positionState)) - SCHEDULER.enter(self.interval_sec, self.priority, self.run) - - -################################################ -# MAIN SECTION -################################################ -LOGGER = logging.getLogger() -LOGGER.setLevel(logging.INFO) -stream_handler = logging.StreamHandler() -stream_handler.setFormatter(logging.Formatter('[%(asctime)s] %(message)s', datefmt='%Y/%m/%d %H:%M:%S')) -LOGGER.addHandler(stream_handler) -colorama_init(autoreset=True) -configFile = "config.json" - -while True: - if path.exists(configFile): - try: - f = open(configFile, encoding = 'utf-8') - config = json.load(f) - f.close() - print("{} loaded".format(configFile)) - except KeyError as e: - print(e) - exit() - - if config["customWD"]["enable"]: - if platform.system() == "Linux": - chdir(config["customWD"]["linux"]) - elif platform.system() == "Windows": - chdir(config["customWD"]["windows"]) - else: - print("Operating system unknown. Invalid WD might cause an error.") - - SCHEDULER = sched.scheduler(time.time, time.sleep) - FUTURES = Futures( - key=config["BinanceAPI"]["key"], - secret=config["BinanceAPI"]["secret"], - base_url=config["BinanceAPI"]["base_url"] - ) - - pairsCount = 0 - for pair in config["pairs"]: - if not pair["enable"]: - continue - A = JAKT( - symbol=[pair["crypto"], pair["asset"]], - strategy=pair["strategy"], - interval_sec=pair["interval_seconds"], - color=pair["color"], - lineToken=config["LINE"]["token"], - lineNotify=config["LINE"]["enable"], - action=config["action"] - ) - SCHEDULER.enter(A.interval_sec, A.priority, A.run) - pairsCount += 1 - - if pairsCount > 0: - time.sleep(random.randint(2, 3)) - try: - LOGGER.info("getting started...") - SCHEDULER.run() - except NameError as e: - LOGGER.info(e) - else: - LOGGER.info("no pair to run.") - else: - LOGGER.info("could not load the configuration file!") - - LOGGER.info("retry in 30 seconds...") - time.sleep(30) \ No newline at end of file diff --git a/main-v0.28.1.py b/main-v0.28.1.py new file mode 100644 index 0000000..8b07b98 --- /dev/null +++ b/main-v0.28.1.py @@ -0,0 +1,1044 @@ +import argparse +import talib as ta +from sys import exit +from os import system, path, chdir, getcwd, environ +from collections import defaultdict +import sched +import time +import logging as logginglib +import numpy as np +import requests +from binance.um_futures import UMFutures +from binance.error import ClientError +import json +import random +import urllib3 +import subprocess +import platform +from colorama import Fore, Style, init as colorama_init +from tabulate import tabulate +import math + + +################################################ +# BinanceSloth Classes +################################################ +class SlothAnalysis: + def map(self, x, in_min, in_max, out_min, out_max): + ''' + long map(long x, long in_min, long in_max, long out_min, long out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; + } + ''' + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + + def ta_consistency(self, tfs, mode="D"): + ''' + Default mode "D" means direction, return -1, 0, 1 stand for + consistence neg, not consistence, and consistence pos consequently. + "N" mode returns score -100 to 100 + ''' + consist = 0 + tf_count = len(tfs) + for _, tf_v in tfs.items(): + if tf_v > 0: + consist += 1 + else: + consist -= 1 + consist = consist/tf_count + + if mode == "N": + return consist + elif mode == "D": + if consist == 100: + return 1 + elif consist == -100: + return -1 + else: + return 0 + else: + return consist + + def ta_ema(self, data, ta_ema=5): + return round(ta.EMA(data, ta_ema)[-2], 2) + + def ta_ma(self, data, ta_ma=5): + return round(ta.MA(data, ta_ma)[-2], 4) + + def ta_ema_comp_cross(self, data, ema_small=5, ema_large=10): + # + # p and n stand for positive and negative respectively + # + emas_small = ta.EMA(data, ema_small) + emas_large = ta.EMA(data, ema_large) + + if ((emas_small[-4]-emas_large[-4]) > 0) and ((emas_small[-3]-emas_large[-3]) > 0) and ((emas_small[-2]-emas_large[-2]) > 0): + return"ppp" + elif ((emas_small[-4]-emas_large[-4]) > 0) and ((emas_small[-3]-emas_large[-3]) > 0) and ((emas_small[-2]-emas_large[-2]) <= 0): + return "ppn" + elif ((emas_small[-4]-emas_large[-4]) > 0) and ((emas_small[-3]-emas_large[-3]) <= 0) and ((emas_small[-2]-emas_large[-2]) > 0): + return "pnp" + elif ((emas_small[-4]-emas_large[-4]) > 0) and ((emas_small[-3]-emas_large[-3]) <= 0) and ((emas_small[-2]-emas_large[-2]) <= 0): + return "pnn" + elif ((emas_small[-4]-emas_large[-4]) <= 0) and ((emas_small[-3]-emas_large[-3]) > 0) and ((emas_small[-2]-emas_large[-2]) > 0): + return "npp" + elif ((emas_small[-4]-emas_large[-4]) <= 0) and ((emas_small[-3]-emas_large[-3]) > 0) and ((emas_small[-2]-emas_large[-2]) <= 0): + return "npn" + elif ((emas_small[-4]-emas_large[-4]) <= 0) and ((emas_small[-3]-emas_large[-3]) <= 0) and ((emas_small[-2]-emas_large[-2]) > 0): + return "nnp" + elif ((emas_small[-4]-emas_large[-4]) <= 0) and ((emas_small[-3]-emas_large[-3]) <= 0) and ((emas_small[-2]-emas_large[-2]) <= 0): + return "nnn" + + def ta_ema_vector(self, data, ta_ema=5, depth=3, step=1): + # + # u and d stand for up and down respectively + # + emas = ta.EMA(data, ta_ema) + if depth == 2: + if emas[-(step+3)] > emas[-(step+2)] > emas[-(step+1)]: + return "dd" + elif emas[-(step+3)] > emas[-(step+2)] <= emas[-(step+1)]: + return "du" + elif emas[-(step+3)] <= emas[-(step+2)] > emas[-(step+1)]: + return "ud" + elif emas[-(step+3)] <= emas[-(step+2)] <= emas[-(step+1)]: + return "uu" + elif depth == 3: + if emas[-(step+4)] > emas[-(step+3)] > emas[-(step+2)] > emas[-(step+1)]: + return "ddd" + elif emas[-(step+4)] > emas[-(step+3)] > emas[-(step+2)] <= emas[-(step+1)]: + return "ddu" + elif emas[-(step+4)] > emas[-(step+3)] <= emas[-(step+2)] > emas[-(step+1)]: + return "dud" + elif emas[-(step+4)] > emas[-(step+3)] <= emas[-(step+2)] <= emas[-(step+1)]: + return "duu" + elif emas[-(step+4)] <= emas[-(step+3)] > emas[-(step+2)] > emas[-(step+1)]: + return "udd" + elif emas[-(step+4)] <= emas[-(step+3)] > emas[-(step+2)] <= emas[-(step+1)]: + return "udu" + elif emas[-(step+4)] <= emas[-(step+3)] <= emas[-(step+2)] > emas[-(step+1)]: + return "uud" + elif emas[-(step+4)] <= emas[-(step+3)] <= emas[-(step+2)] <= emas[-(step+1)]: + return "uuu" + + def ta_macd_delta(self, data, ema_small, ema_large): + return round(ta.EMA(data, ema_small)[-2]-ta.EMA(data, ema_large)[-2], 2) + + def ta_macd(self, data, fastperiod=12, slowperiod=26, signalperiod=9): + _, _, macdhist = ta.MACD(data, fastperiod, slowperiod, signalperiod) + return round(macdhist[-2], 2) + + def ta_macd_vector(self, data, fastperiod=12, slowperiod=26, signalperiod=9, step=1): + # + # u and d stand for up and down respectively + # + if len(data)*2 <= slowperiod: + return "not enough data." + else: + _, _, macdhist = ta.MACD(data, fastperiod, slowperiod, signalperiod) + if macdhist[-(step+4)] > macdhist[-(step+3)] > macdhist[-(step+2)] > macdhist[-(step+1)]: + return "ddd" + elif macdhist[-(step+4)] > macdhist[-(step+3)] > macdhist[-(step+2)] <= macdhist[-(step+1)]: + return "ddu" + elif macdhist[-(step+4)] > macdhist[-(step+3)] <= macdhist[-(step+2)] > macdhist[-(step+1)]: + return "dud" + elif macdhist[-(step+4)] > macdhist[-(step+3)] <= macdhist[-(step+2)] <= macdhist[-(step+1)]: + return "duu" + elif macdhist[-(step+4)] <= macdhist[-(step+3)] > macdhist[-(step+2)] > macdhist[-(step+1)]: + return "udd" + elif macdhist[-(step+4)] <= macdhist[-(step+3)] > macdhist[-(step+2)] <= macdhist[-(step+1)]: + return "udu" + elif macdhist[-(step+4)] <= macdhist[-(step+3)] <= macdhist[-(step+2)] > macdhist[-(step+1)]: + return "uud" + elif macdhist[-(step+4)] <= macdhist[-(step+3)] <= macdhist[-(step+2)] <= macdhist[-(step+1)]: + return "uuu" + + def ta_macd_zcross(self, data, fastperiod=12, slowperiod=26, signalperiod=9): + # + # p and n stand for positive and negative respectively + # + _, _, macdhist = ta.MACD(data, fastperiod, slowperiod, signalperiod) + if macdhist[-4] > 0 and macdhist[-3] > 0 and macdhist[-2] > 0: + return"ppp" + elif macdhist[-4] > 0 and macdhist[-3] > 0 and macdhist[-2] <= 0: + return "ppn" + elif macdhist[-4] > 0 and macdhist[-3] <= 0 and macdhist[-2] > 0: + return "pnp" + elif macdhist[-4] > 0 and macdhist[-3] <= 0 and macdhist[-2] <= 0: + return "pnn" + elif macdhist[-4] <= 0 and macdhist[-3] > 0 and macdhist[-2] > 0: + return "npp" + elif macdhist[-4] <= 0 and macdhist[-3] > 0 and macdhist[-2] <= 0: + return "npn" + elif macdhist[-4] <= 0 and macdhist[-3] <= 0 and macdhist[-2] > 0: + return "nnp" + elif macdhist[-4] <= 0 and macdhist[-3] <= 0 and macdhist[-2] <= 0: + return "nnn" + + def ta_insideway(self, rsi, ovs=40, ovb=60): + ''' + input rsi 0 to 100, output -100 to 100. -100 means recommend to short, and vice versa. + ''' + score = 0 + if rsi <= ovb and rsi >= ovs: + return score + elif rsi > ovb: + return self.map(rsi, ovb, 100, 0, 100) + elif rsi < ovs: + return self.map(rsi, 0, ovs, -100, 0) + + def ta_rsi(self, data, value=6): + return round(ta.RSI(data, value)[-2], 2) + + def ta_rsi_vector(self, data, value=6, step=1): + rsis = ta.RSI(data, value) + if rsis[-(step+4)] > rsis[-(step+3)] > rsis[-(step+2)] > rsis[-(step+1)]: + return "ddd" + elif rsis[-(step+4)] > rsis[-(step+3)] > rsis[-(step+2)] <= rsis[-(step+1)]: + return "ddu" + elif rsis[-(step+4)] > rsis[-(step+3)] <= rsis[-(step+2)] > rsis[-(step+1)]: + return "dud" + elif rsis[-(step+4)] > rsis[-(step+3)] <= rsis[-(step+2)] <= rsis[-(step+1)]: + return "duu" + elif rsis[-(step+4)] <= rsis[-(step+3)] > rsis[-(step+2)] > rsis[-(step+1)]: + return "udd" + elif rsis[-(step+4)] <= rsis[-(step+3)] > rsis[-(step+2)] <= rsis[-(step+1)]: + return "udu" + elif rsis[-(step+4)] <= rsis[-(step+3)] <= rsis[-(step+2)] > rsis[-(step+1)]: + return "uud" + elif rsis[-(step+4)] <= rsis[-(step+3)] <= rsis[-(step+2)] <= rsis[-(step+1)]: + return "uuu" + return None + + def ta_sar(self, data, timeperiod=30): + return ta.SAR(data.high, data.low, acceleration=0, maximum=0) + + def ta_vector_fibo(self, vals): + score = 0 + if vals[0] == "n" or vals[0] == "d": + score -= 21 + else: + score += 21 + if vals[1] == "n" or vals[1] == "d": + score -= 34 + else: + score += 34 + if vals[2] == "n" or vals[2] == "d": + score -= 55 + else: + score += 55 + return self.map(score, -110, 110, -100, 100) + +class BinanceSloth(SlothAnalysis): + def __init__(self, pair): + #c configuration loading and LINE test + self.pair = pair + self.symbol = self.pair["crypto"]+self.pair["asset"] + self.positionStatus = None + self.prefix = LOGGER.tint(self.symbol, color=self.pair["color"]) + + # Test logging + #LOGGER.testLogging() + + # Binance Futures preparation + #self.connect() + self.fetch() + #exit() + #self.test() + + def getAsset(self, name="USDT"): + if self.fetch(): + assets = self.account["assets"] + for asset in assets: + if asset["asset"] == name: + return asset + return False + + def fetch(self): + if self.connect(): + self.position = FUTURES.get_position_risk(symbol=self.symbol)[0] + #LOGGER.pprint(self.position) + self.account = FUTURES.account() + if float(self.position["positionAmt"]) > 0: + self.positionStatus = "longed" + elif float(self.position["positionAmt"]) < 0: + self.positionStatus = "shorted" + else: + self.positionStatus = "closed" + return True + else: + return False + + def map(self, x, in_min, in_max, out_min, out_max): + ''' + long map(long x, long in_min, long in_max, long out_min, long out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; + } + ''' + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + + def round_decimals_down(number:float, decimals:int=2): + """ + Returns a value rounded down to a specific number of decimal places. + """ + if not isinstance(decimals, int): + raise TypeError("decimal places must be an integer") + elif decimals < 0: + raise ValueError("decimal places has to be 0 or more") + elif decimals == 0: + return math.floor(number) + + factor = 10 ** decimals + return math.floor(number * factor) / factor + + def round_decimals_up(number:float, decimals:int=2): + """ + Returns a value rounded up to a specific number of decimal places. + round_decimals_up(8.343) + # Returns: 8.35 + """ + if not isinstance(decimals, int): + raise TypeError("decimal places must be an integer") + elif decimals < 0: + raise ValueError("decimal places has to be 0 or more") + elif decimals == 0: + return math.ceil(number) + + factor = 10 ** decimals + return math.ceil(number * factor) / factor + + def maxQty(self, percent=None): + if percent is None: + percent = self.pair["used_asset_percent"] + ex_info = FUTURES.exchange_info() + filter = None + for symbol in ex_info["symbols"]: + if symbol["symbol"] == self.symbol: + for filt in symbol["filters"]: + if filt["filterType"] == "MARKET_LOT_SIZE": + filter = filt + assetUsed = (percent/100*float(self.account["availableBalance"])) + markPrice = float(FUTURES.mark_price(symbol=self.symbol)["markPrice"]) + minPriceBuyable = markPrice * float(filter["minQty"]) + leverage = self.get_leverage() + # I spent many hours for the following line! + maximumQty = int(assetUsed*leverage/minPriceBuyable) * float(filter["stepSize"]) + return maximumQty + + def cal_QTY2(self, percent=None): + if percent is None: + percent = self.pair["used_asset_percent"] + ex_info = FUTURES.exchange_info() + filter = None + for symbol in ex_info["symbols"]: + if symbol["symbol"] == self.symbol: + for filt in symbol["filters"]: + if filt["filterType"] == "MARKET_LOT_SIZE": + filter = filt + LOGGER.pprint(filt) + assetUsed = (percent/100*float(self.account["availableBalance"])) + markPrice = float(FUTURES.mark_price(symbol=self.symbol)["markPrice"]) + minPriceBuyable = markPrice * float(filter["minQty"]) + stepSize = float(filter["stepSize"]) + leverage = self.get_leverage() + if filter["minQty"].rfind(".") > -1: + _, deci = filter["minQty"].split(".") + quantityPrecision = len(deci) + else: + quantityPrecision = 0 + minimumQty = max( + float(filter["minQty"]), + self.round_decimals_up( + (float(filter["stepSize"]) / markPrice), + quantityPrecision + ) + ) + # I spent many hours for the following line! + maximumQty = int(assetUsed*leverage/minPriceBuyable) * float(filter["stepSize"]) + ''' + print("assetUsed:{} ".format(assetUsed)) + print("markPrice:{} ".format(markPrice)) + print("minPriceBuyable:{} ".format(minPriceBuyable)) + print("quantityPrecision:{} ".format(quantityPrecision)) + print("minimumQty:{} ".format(minimumQty)) + print("maximumQty: {}".format(maximumQty)) + print("MAX: {}".format(maximumQty*minPriceBuyable)) + ''' + return maximumQty + + ''' + def cal_QTY(self, price, percent=None): + # under implementation! This could return the float, not int!? + if percent is None: + percent = self.pair["used_asset_percent"] + qty = float(self.cal_QTY_max(float(price), percent) * self.minimum_order_amt) + qty = self.float_trim(qty, 3) + print("QTY: {}".format(qty)) + #qty=5.0 + return qty + + def cal_QTY_max(self, price, percent): + # under implementation! This could return the float, not int!? + return float((percent/100*float(self.account["availableBalance"]))/self.cal_initMargin(float(price))) + + def cal_initMargin(self, price): + ex_info = FUTURES.exchange_info() + for symbol in ex_info["symbols"]: + if symbol["symbol"] == self.symbol: + #LOGGER.pprint(symbol["filters"]) + for filter in symbol["filters"]: + if filter["filterType"] == "MARKET_LOT_SIZE": + LOGGER.pprint(filter) + self.minimum_order_amt = float(filter["minQty"]) + self.minimum_order_stepSize = float(filter["stepSize"]) + result = float(float(price)*self.minimum_order_amt/self.get_leverage()) + print("cal_initMargin:{} ".format(result)) + return result + ''' + + def get_leverage(self): + if self.fetch(): + for position in self.account["positions"]: + if position["symbol"] in CONFIG["symbols"]: + return int(position["leverage"]) + return False + + def float_trim(self, input, decimal): + x_str = str(input) + x = x_str.split(".") + result = str(x[0])+"."+str(x[1][:decimal]) + return(float(result)) + + def long(self, quantity=None): + if self.connect(): + if quantity is None: + quantity = self.maxQty() + params = { + 'symbol': self.symbol, + 'side': 'BUY', + 'type': 'MARKET', + 'quantity': abs(quantity) + } + try: + if self.pair["testOrder"]: + response = FUTURES.new_order_test(**params) + else: + response = FUTURES.new_order(**params) + time.sleep(1) + LOGGER.success("{} just open {} position.".format(self.prefix, LOGGER.tint("LONG", color="success"))) + except ClientError as e: + LOGGER.warning("{} LONG with {} {} failed. {}".format(self.prefix, quantity, self.pair["crypto"], e)) + response = e + #LOGGER.pprint(response.error_message) + LINE.notify("{}, LONG failed. {}".format(CONFIG["BinanceAPI"]["name"], response.error_message)) + else: + self.positionStatus = "longed" + + return response + + def short(self, quantity=None): + if self.connect(): + if quantity is None: + quantity = self.maxQty() + params = { + 'symbol': self.symbol, + 'side': 'SELL', + 'type': 'MARKET', + 'quantity': abs(quantity) + } + try: + if self.pair["testOrder"]: + response = FUTURES.new_order_test(**params) + else: + response = FUTURES.new_order(**params) + time.sleep(1) + LOGGER.success("{} just open {} position.".format(self.prefix, LOGGER.tint("SHORT", color="success"))) + except ClientError as e: + LOGGER.warning("{} SHORT with {} {} failed. {}".format(self.prefix, quantity, self.pair["crypto"], e)) + response = e + #LOGGER.pprint(response.error_message) + LINE.notify("{}, SHORT failed. {}".format(CONFIG["BinanceAPI"]["name"], response.error_message)) + else: + self.positionStatus = "shorted" + return response + + def fetch_klines(self, tf="15m", samples=50): + ''' + response (for SOLUSDT): + [ + [ + 1701621900000, // Open time + "62.8270", // Open + "63.0830", // High + "62.7870", // Low + "62.8900", // Close + "119247", // Volume + 1701622799999, // Close time + "7505644.4280", // Quote asset volume + 8325, // Number of trades + "66790", // Taker buy base asset volume + "4203732.1250", // Taker buy quote asset volume + "0" // Ignore. + ] + ] + ''' + klines = FUTURES.klines(symbol=self.symbol, interval=tf, limit=samples) + return klines + + def fetch_closes(self, tf="15m", samples=50): + return_data = [] + klines = self.fetch_klines(tf, samples) + for kline in klines: + return_data.append(float(kline[4])) + #LOGGER.pprint(return_data) + return np.array(return_data) + + def close(self): + if self.fetch(): + if self.positionStatus == "shorted": + self.long(float(self.position["positionAmt"])) + self.positionStatus = "closed" + LOGGER.general("{} SHORT position is just {}.".format(self.prefix, LOGGER.tint("closed", color="success"))) + elif self.positionStatus == "longed": + self.short(float(self.position["positionAmt"])) + self.positionStatus = "closed" + LOGGER.general("{} LONG position is just {}.".format(self.prefix, LOGGER.tint("closed", color="success"))) + else: + LOGGER.general("{} The position is already closed.".format(self.prefix)) + + def test(self): + if self.connect(): + LOGGER.pprint(FUTURES.get_orders()) + + + def connect(self, retrySecondMin=5, incrementSecond=10, timeout=60*1): + seconds = retrySecondMin + connectionPassed = 0 + connectionTotal = 1 #some situation might require more than 1 successful connection to make sure + while connectionPassed < connectionTotal: + try: + FUTURES.ping() + except: + LOGGER.warning("API connection failed, retry in {} seconds".format(seconds)) + time.sleep(seconds) + if seconds < timeout: + seconds += incrementSecond + else: + return False + else: + LOGGER.debug("API connected") + connectionPassed += 1 + return True + + def run(self): + if self.pair["strategy"] == "MACDrider": + # Open when MA25 crosses MA99, close when MA25 reverses direction + indy = defaultdict(dict) + data = defaultdict(dict) + tf = "1h" + data = self.fetch_closes(tf) + ma7 = self.ta_ma(data, 7) + ma25 = self.ta_ma(data, 25) + #LOGGER.pprint(ma7) + #LOGGER.pprint(ma25) + + if self.positionStatus == "closed": + if ma7 > ma25: + self.long() + else: + self.short() + elif self.positionStatus == "longed": + if ma7 < ma25: + self.close() + time.sleep(1) + self.short() + elif self.positionStatus == "shorted": + if ma7 > ma25: + self.close() + time.sleep(1) + self.long() + elif self.pair["strategy"] == "MA25xMA99": + indy = defaultdict(dict) + data = defaultdict(dict) + tf = "4h" + data = self.fetch_closes(tf, samples=200) + ma25 = self.ta_ma(data, 25) + ma99 = self.ta_ma(data, 99) + #LOGGER.pprint(ma25) + #LOGGER.pprint(ma99) + + if self.positionStatus == "closed": + if ma25 > ma99: + self.long() + else: + self.short() + elif self.positionStatus == "longed": + if ma25 < ma99: + self.close() + time.sleep(1) + self.short() + elif self.positionStatus == "shorted": + if ma25 > ma99: + self.close() + time.sleep(1) + self.long() + elif self.pair["strategy"] == "K90_v1": + indy = defaultdict(dict) + data = defaultdict(dict) + criterion = defaultdict(dict) + k = defaultdict(dict) + + criterion["rsi_oversold"] = 40 + criterion["rsi_overbought"] = 60 + criterion["long_if_over"] = 33 + criterion["close_longed_if_below"] = 30 + criterion["short_if_below"] = -33 + criterion["close_shorted_if_over"] = -30 + + #tfs = ["5m", "15m", "1h"] + tfs = ["1h", "4h"] + tfs_weight = [1, 2] + k_weights = [ + #[2, 3, 3, 2], #fibo_macd_v, macd_zcross, rsi_v, insideway + [1, 1, 1, 1], + [2, 3, 3, 2] + ] + + ''' + prepare indicator values to be used in analysis process + ''' + for tf in tfs: + data[tf] = self.fetch_closes(tf) + #indy[tf]["macd"] = self.ta_macd(data[tf]) + indy[tf]["macd_v"] = self.ta_macd_vector(data[tf]) + indy[tf]["macd_zcross"] = self.ta_macd_zcross(data[tf]) + indy[tf]["rsi"] = self.ta_rsi(data[tf]) + indy[tf]["rsi_v"] = self.ta_rsi_vector(data[tf]) + indy[tf]["insideway"] = self.ta_insideway( + indy[tf]["rsi"], + criterion["rsi_oversold"], + criterion["rsi_overbought"] + ) + #LOGGER.pprint(indy) + ''' + Calculate k_results for each timeframe, which is including fibo_macd_v, macd_zcross, rsi_v, and insideway consequently. + This segtion can be further designed more complex as desire. + ''' + k_results = defaultdict(dict) + weight_counter = 0 + for tf in tfs: + k[tf]["fibo_macd_v"] = k_weights[weight_counter][0] * self.ta_vector_fibo(indy[tf]["macd_v"]) + k[tf]["macd_zcross"] = k_weights[weight_counter][1] * self.ta_vector_fibo(indy[tf]["macd_zcross"]) + k[tf]["rsi_v"] = k_weights[weight_counter][2] * self.ta_vector_fibo(indy[tf]["rsi_v"]) + k[tf]["insideway"] = k_weights[weight_counter][3] * indy[tf]["insideway"] + k_result = ( + k[tf]["fibo_macd_v"] + + k[tf]["macd_zcross"] + + k[tf]["rsi_v"] + + k[tf]["insideway"] + ) / sum(k_weights[weight_counter]) + #self.pprint(k) + weight_counter += 1 + k_results[tf] = k_result + #LOGGER.pprint(k) + #LOGGER.pprint(k_results) + ''' + Final segtion which will calculate the K_score, + which will be used as the criteria in openning the long or close position. + ''' + weight_counter = 0 + K_score = 0 + for tf in tfs: + K_score += k_results[tf]*tfs_weight[weight_counter] + weight_counter += 1 + K_score = K_score / sum(tfs_weight) + LOGGER.info(str(round(K_score, 2))) + #LOGGER.debug(str(self.ta_consistency(k_results))) + ''' + After we've achived the K_score, the OUR INCREDIBLE FACTOR, then the actual trading is coming below... + ''' + #LOGGER.info(K_score) + if self.positionStatus == "closed": + if K_score >= criterion["long_if_over"]: + try: + self.long() + except KeyError as e: + LOGGER.error(e) + elif K_score <= criterion["short_if_below"]: + try: + self.short() + except KeyError as e: + LOGGER.error(e) + else: + LOGGER.info("{} K_score: {}, sit on hands and keep waiting.".format(self.symbol, round(K_score, 2))) + elif self.positionStatus == "longed": + if K_score < criterion["close_longed_if_below"]: + try: + self.close() + LOGGER.info("{} K_score: {}, longed position just closed".format(self.symbol, round(K_score, 2))) + except KeyError as e: + LOGGER.error(e) + else: + LOGGER.info("{} K_score: {}, let {} profit run".format(self.symbol, round(K_score), self.positionStatus)) + elif self.positionStatus == "shorted": + if K_score > criterion["close_shorted_if_over"]: + try: + self.close() + LOGGER.info("{} K_score: {}, shorted position just closed".format(self.symbol, round(K_score, 2))) + except KeyError as e: + LOGGER.error(e) + else: + LOGGER.info("{} K_score: {}, let {} profit run".format(self.symbol, round(K_score), self.positionStatus)) + else: + LOGGER.warning("{} self.positionStatus: {}".format(self.symbol, self.positionStatus)) + #LOGGER.pprint(indy) + + elif self.pair["strategy"] == "test": + time.sleep(5) + ''' + tf="3m" + data = self.fetch_closes(tf, 100) + self.pprint(self.ta_rsi_vector(data)) + ''' + elif self.pair["strategy"] == "testOpenOrder": + tf="15m" + data = self.fetch_closes(tf, 1) + LOGGER.info("testing long...") + self.long() + time.sleep(5) + LOGGER.info("testing close...") + self.close() + time.sleep(5) + LOGGER.info("testing short...") + self.short() + time.sleep(5) + LOGGER.info("testing close...") + self.close() + LOGGER.info("end test strategy") + elif self.pair["strategy"] == "MACDez": + pass + else: + LOGGER.warning("{} strategy not available!".format(self.prefix)) + + LOGGER.general("{} is {}".format(self.prefix, LOGGER.tint("running", color="success"))) + if(self.positionStatus == "closed"): + SCHEDULER.enter(self.pair["interval_when_closed"], self.pair["priority"], self.run) + else: + SCHEDULER.enter(self.pair["interval_when_opened"], self.pair["priority"], self.run) + +class Test: + def testTAlib(): + c = np.random.randn(100) + k, d = ta.STOCHRSI(c) + rsi = ta.RSI(c) + k, d = ta.STOCHF(rsi, rsi, rsi) + rsi = ta.RSI(c) + k, d = ta.STOCH(rsi, rsi, rsi) + print("TA-lib OK") + +class Line: + def __init__(self, lineApi, lineToken, lineEnable): + self.lineApi = lineApi + self.lineToken = lineToken + self.headers = {'Authorization':'Bearer '+self.lineToken} + self.lineEnable = lineEnable + + def notify(self, msg): + if self.lineEnable: + try: + return requests.post( + self.lineApi, + headers=self.headers, + data = {'message':msg}, + files=None + ) + except KeyError as e: + print(e) +class Logging: + def __init__(self, loggingLevel="INFO"): + self.logging = None + self.loggingLevel = loggingLevel + logger = logginglib.getLogger() + if self.loggingLevel.upper() == "DEBUG": + logger.setLevel(logginglib.DEBUG) + elif self.loggingLevel.upper() == "INFO": + logger.setLevel(logginglib.INFO) + elif self.loggingLevel.upper() == "WARNING": + logger.setLevel(logginglib.WARNING) + elif self.loggingLevel.upper() == "ERROR": + logger.setLevel(logginglib.ERROR) + elif self.loggingLevel.upper() == "CRITICAL": + logger.setLevel(logginglib.CRITICAL) + else: + print("loggingLevel invalid [{}]".format(self.loggingLevel)) + logger.setLevel(logginglib.DEBUG) + sh = logginglib.StreamHandler() + sh.setFormatter(logginglib.Formatter('[%(asctime)s] %(message)s', datefmt='%Y/%m/%d %H:%M:%S')) + logger.addHandler(sh) + self.logging = logger + + def debug(self, msg): + self.logging.debug(self.tint(msg, "LIGHTBLACK_EX")) + + def general(self, msg): + self.logging.info(msg) + + def info(self, msg): + self.logging.info(self.tint(msg, "LIGHTBLUE_EX")) + + def success(self, msg): + self.logging.info(self.tint(msg, "LIGHTGREEN_EX")) + + def warning(self, msg): + self.logging.warning(self.tint(msg, "LIGHTYELLOW_EX")) + + def error(self, msg): + self.logging.error(self.tint(msg, "LIGHTRED_EX")) + + def critical(self, msg): + self.logging.critical(self.tint(msg, "LIGHTMAGENTA_EX")) + + def pprint(self, msg): + print(json.dumps(msg, indent=4, sort_keys=False)) + + def pretty(self, msg): + return(json.dumps(msg, indent=4, sort_keys=False)) + + def tint(self, msg, color=None): + if color is None: + color = self.color + if color == "LIGHTBLACK_EX": + return Fore.LIGHTBLACK_EX+msg+Style.RESET_ALL + elif color == "LIGHTBLUE_EX" or color == "info": + return Fore.LIGHTBLUE_EX+msg+Style.RESET_ALL + elif color == "LIGHTCYAN_EX": + return Fore.LIGHTCYAN_EX+msg+Style.RESET_ALL + elif color == "LIGHTGREEN_EX" or color == "success": + return Fore.LIGHTGREEN_EX+msg+Style.RESET_ALL + elif color == "LIGHTMAGENTA_EX" or color == "critical": + return Fore.LIGHTMAGENTA_EX+msg+Style.RESET_ALL + elif color == "LIGHTRED_EX" or color == "danger": + return Fore.LIGHTRED_EX+msg+Style.RESET_ALL + elif color == "LIGHTWHITE_EX": + return Fore.LIGHTWHITE_EX+msg+Style.RESET_ALL + elif color == "LIGHTYELLOW_EX" or color == "warning": + return Fore.LIGHTYELLOW_EX+msg+Style.RESET_ALL + else: + return msg + + def printTint(self, msg, color=None): + print(self.tint(msg, color)) + + def testLogging(self): + self.debug("debug msg") + self.general("general msg") + self.info("info msg") + self.success("success msg") + self.warning("warning msg") + self.error("error msg") + self.critical("critical msg") + +class Utils: + def __init__(self): + pass + + def getTemp_CPU(self): + response = subprocess.run(['cat', '/sys/class/thermal/thermal_zone0/temp'], stdout=subprocess.PIPE) + return ''.join(e for e in str(response.stdout) if e.isnumeric()) + + def ping(self, hostname="www.google.com", count=1): + response = None + if platform.system() == "Linux": + response = system("ping -c " + str(count) + " " + hostname + " > /dev/null 2>&1") + elif platform.system() == "Windows": + response = system("ping -n " + str(count) + " " + hostname + " > $null ") + if response == 0: + return 1 + else: + return 0 + + def waitForInternet(self, retry_sec_min=1, increment_sec=10, retry_sec_max=60*3): + seconds = retry_sec_min + while not self.ping(): + LOGGER.warning( + "internet connection "+Fore.RED+"failed"+Style.RESET_ALL + +", retry in "+Fore.GREEN+"{}".format(seconds)+Style.RESET_ALL+" second(s)" + ) + time.sleep(seconds) + if seconds < retry_sec_max: + seconds += increment_sec + return True + + def sleep(self, min=1, max=-1): + if max == -1: + time.sleep(min) + else: + time.sleep(random.randint(min, max)) + + def float_trim(self, input, decimal): + x_str = str(input) + x = x_str.split(".") + result = str(x[0])+"."+str(x[1][:decimal]) + return(float(result)) + +class Reporter(): + def __init__(self, interval, priority): + self.interval=interval + self.priority=priority + + def connect(self, retrySecondMin=5, incrementSecond=10, timeout=60*1): + seconds = retrySecondMin + connectionPassed = 0 + connectionTotal = 1 #some situation might require more than 1 successful connection to make sure + while connectionPassed < connectionTotal: + try: + FUTURES.ping() + except: + LOGGER.error("API connection failed, retry in {} seconds".format(seconds)) + time.sleep(seconds) + if seconds < timeout: + seconds += incrementSecond + else: + return False + else: + connectionPassed += 1 + return True + + def report(self): + if self.connect(): + account = FUTURES.account() + acc_info = "totalWalletBalance: [{}], totalCrossUnPnl: [{}], ".format( + LOGGER.tint(str(round(float(account["totalWalletBalance"]), 4)), color="success"), + LOGGER.tint(str(round(float(account["totalCrossUnPnl"]), 4)), color="success") + ) + for asset in account["assets"]: + if asset["asset"] in CONFIG["assets"]: + acc_info += asset["asset"] + ": [" + LOGGER.tint(str(round(float(asset["availableBalance"]), 4)), color="success") + "] " + + headers_pairs = [ + "Symbol", + "Side", + "Amount", + "x", + "UnPnl", + "UseAsset", + "Strategy" + ] + row_pairs = [] + for position in account["positions"]: + if position["symbol"] in CONFIG["symbols"]: + row = [ + LOGGER.tint(position["symbol"], color=CONFIG_DICT["pairs"][position["symbol"]]["color"]), + position["positionSide"], + round(float(position["positionAmt"]), 4), + position["leverage"], + round(float(position["unrealizedProfit"]), 2), + str(CONFIG_DICT["pairs"][position["symbol"]]["used_asset_percent"])+"%", + CONFIG_DICT["pairs"][position["symbol"]]["strategy"] + ] + row_pairs.append(row) + global global_firstRun + if global_firstRun: + LINE.notify("{}, totalWalletBalance: [{}], totalCrossUnPnl: [{}]".format( + CONFIG["BinanceAPI"]["name"], + str(round(float(account["totalWalletBalance"]), 4)), + str(round(float(account["totalCrossUnPnl"]), 4)) + )) + global_firstRun = False + LOGGER.general(acc_info+"\n"+tabulate(row_pairs, headers_pairs, tablefmt="pretty")) + SCHEDULER.enter(self.interval, self.priority, self.report) + + +# ######## +# MAIN SECTION +# ######## +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +colorama_init(autoreset=True) +global_firstRun = True +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="GeekSloth's Binance bot") + parser.add_argument("-c", "--config", help="configuration JSON file", default="config.json") + args = vars(parser.parse_args()) + + while True: + CONFIG = None + CONFIG_DICT = defaultdict(dict) + if path.exists(args["config"]): + # ######## + # Configuration file loading. + # Additional keys for efficient processing. + # ######## + try: + f = open(args["config"], encoding = 'utf-8') + CONFIG = json.load(f) + f.close() + print("{} loaded".format(args["config"])) + except KeyError as e: + raise Exception(e) + if CONFIG["customWD"]["enable"]: + if platform.system() == "Linux": + chdir(CONFIG["customWD"]["linux"]) + elif platform.system() == "Windows": + chdir(CONFIG["customWD"]["windows"]) + else: + print("Operating system unknown. Invalid WD might cause an error.") + i = 0 + for pair in CONFIG["pairs"]: + if pair["enable"]: + # add symbol key to each pair + CONFIG["pairs"][i]["symbol"] = pair["crypto"]+pair["asset"] + # make dictionary for easier access + CONFIG_DICT["pairs"][str(pair["crypto"]+pair["asset"])] = pair + CONFIG["assets"] = [] + CONFIG["symbols"] = [] + for pair in CONFIG["pairs"]: + if pair["enable"]: + if pair["asset"] not in CONFIG["assets"]: + CONFIG["assets"].append(pair["asset"]) + if pair["crypto"]+pair["asset"] not in CONFIG["symbols"]: + CONFIG["symbols"].append(pair["crypto"]+pair["asset"]) + + # ######## + # Essential objects and functions construction + # ######## + environ['TZ'] = CONFIG["timezone"] + time.tzset() + SCHEDULER = sched.scheduler(time.time, time.sleep) + FUTURES = UMFutures( + key=CONFIG["BinanceAPI"]["key"], + secret=CONFIG["BinanceAPI"]["secret"] + ) + LINE = Line( + lineApi=CONFIG["LINE"]["api"], + lineToken=CONFIG["LINE"]["token"], + lineEnable=CONFIG["LINE"]["enable"] + ) + LOGGER = Logging(loggingLevel=CONFIG["loggingLevel"]) + REPORTER = Reporter(CONFIG["reporter"]["interval"], priority=2) + SCHEDULER.enter(1, REPORTER.priority, REPORTER.report) + + # ######## + # Run each pair simultaenously. + # ######## + pairsCount = 0 + for pair in CONFIG["pairs"]: + if not pair["enable"]: + continue + sloth = BinanceSloth(pair) + SCHEDULER.enter(1, pair["priority"], sloth.run) + pairsCount += 1 + + if pairsCount > 0: + time.sleep(random.randint(2, 3)) + try: + print("getting started...") + SCHEDULER.run() + except NameError as e: + print(e) + else: + print("no pair to run.") + else: + print("could not load the configuration file!") + + print("retry to load {} again in 30 seconds...".format(args["config"])) + time.sleep(30) \ No newline at end of file diff --git a/refs/Dockerfile.JAKT b/refs/Dockerfile.JAKT new file mode 100644 index 0000000..3972620 --- /dev/null +++ b/refs/Dockerfile.JAKT @@ -0,0 +1,43 @@ +FROM python:3.9.10-alpine +ENV PYTHON_TA_LIB_PACKAGE_NAME "TA-lib" +ENV PYTHON_TA_LIB_VERSION "0.4.28" + +#RUN apt-get update && apt install -y python3-numpy gcc make +#RUN pip install python-binance && \ +#pip install numpy && \ +#pip install binance-future && \ +#pip install binance-futures-connector + +USER root +WORKDIR /tmp + +RUN apk add --no-cache --virtual .build-deps \ + musl-dev \ + linux-headers \ + gcc \ + g++ \ + make \ + curl \ + libffi-dev + +#RUN ap install libffi-dev + +RUN pip3 install cffi +RUN cd /tmp \ + && curl -L -O http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz \ + && tar -zxf ta-lib-0.4.0-src.tar.gz \ + && cd ta-lib/ \ + && sed -i 's/^#define TA_IS_ZERO(v).*$/#define TA_IS_ZERO(v) (((-0.000000000000000001) 0: + time.sleep(random.randint(2, 3)) + try: + print("getting started...") + scheduler.run() + except NameError as e: + print(e) + else: + print("no pair to run.") + +class Test: + def testTAlib(): + c = np.random.randn(100) + k, d = talib.STOCHRSI(c) + rsi = talib.RSI(c) + k, d = talib.STOCHF(rsi, rsi, rsi) + rsi = talib.RSI(c) + k, d = talib.STOCH(rsi, rsi, rsi) + print("TA-lib OK") + +class LINE: + def __init__(self, lineApi, lineToken, lineEnable): + self.lineApi = lineApi + self.lineToken = lineToken + self.headers = {'Authorization':'Bearer '+self.lineToken} + self.lineEnable = lineEnable + + def notify(self, msg): + if self.lineEnable: + try: + return requests.post( + self.lineApi, + headers=self.headers, + data = {'message':msg}, + files=None + ) + except KeyError as e: + print(e) +class Logging: + def __init__(self, name, color="LIGHTBLACK_EX", loggingLevel="INFO"): + self.logging = None + self.name = name + self.color = color + self.loggingLevel = loggingLevel + + import logging as logginglib + logger = logginglib.getLogger() + if self.loggingLevel.upper() == "DEBUG": + logger.setLevel(logginglib.DEBUG) + elif self.loggingLevel.upper() == "INFO": + logger.setLevel(logginglib.INFO) + elif self.loggingLevel.upper() == "WARNING": + logger.setLevel(logginglib.WARNING) + elif self.loggingLevel.upper() == "ERROR": + logger.setLevel(logginglib.ERROR) + elif self.loggingLevel.upper() == "CRITICAL": + logger.setLevel(logginglib.CRITICAL) + else: + print("loggingLevel invalid [{}]".format(self.loggingLevel)) + logger.setLevel(logginglib.DEBUG) + sh = logginglib.StreamHandler() + sh.setFormatter(logginglib.Formatter('[%(asctime)s] %(message)s', datefmt='%Y/%m/%d %H:%M:%S')) + logger.addHandler(sh) + self.logging = logger + + def addPrefix(self, msg): + if self.color == "LIGHTBLACK_EX": + styledMsg = Fore.LIGHTBLACK_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTBLUE_EX": + styledMsg = Fore.LIGHTBLUE_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTCYAN_EX": + styledMsg = Fore.LIGHTCYAN_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTGREEN_EX": + styledMsg = Fore.LIGHTGREEN_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTMAGENTA_EX": + styledMsg = Fore.LIGHTMAGENTA_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTRED_EX": + styledMsg = Fore.LIGHTRED_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTWHITE_EX": + styledMsg = Fore.LIGHTWHITE_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTYELLOW_EX": + styledMsg = Fore.LIGHTYELLOW_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + return styledMsg + + def debug(self, msg): + self.logging.debug(self.addPrefix(self.tint(msg, "LIGHTBLACK_EX"))) + + def general(self, msg): + self.logging.info(self.addPrefix(msg)) + + def info(self, msg): + self.logging.info(self.addPrefix(self.tint(msg, "LIGHTBLUE_EX"))) + + def success(self, msg): + self.logging.info(self.addPrefix(self.tint(msg, "LIGHTGREEN_EX"))) + + def warning(self, msg): + self.logging.warning(self.addPrefix(self.tint(msg, "LIGHTYELLOW_EX"))) + + def error(self, msg): + self.logging.error(self.addPrefix(self.tint(msg, "LIGHTRED_EX"))) + + def critical(self, msg): + self.logging.critical(self.addPrefix(self.tint(msg, "LIGHTMAGENTA_EX"))) + + def pprint(self, msg): + print(json.dumps(msg, indent=4, sort_keys=False)) + + def pretty(self, msg): + return(json.dumps(msg, indent=4, sort_keys=False)) + + def print(self, msg): + if self.color == "LIGHTMAGENTA_EX": + styled_msg = Fore.LIGHTMAGENTA_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTYELLOW_EX": + styled_msg = Fore.LIGHTYELLOW_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTCYAN_EX": + styled_msg = Fore.LIGHTCYAN_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTGREEN_EX": + styled_msg = Fore.LIGHTGREEN_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTRED_EX": + styled_msg = Fore.LIGHTRED_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + print(styled_msg) + + def tint(self, msg, color=None): + if color is None: + color = self.color + if color == "LIGHTBLACK_EX": + return Fore.LIGHTBLACK_EX+msg+Style.RESET_ALL + elif color == "LIGHTBLUE_EX" or color == "info": + return Fore.LIGHTBLUE_EX+msg+Style.RESET_ALL + elif color == "LIGHTCYAN_EX": + return Fore.LIGHTCYAN_EX+msg+Style.RESET_ALL + elif color == "LIGHTGREEN_EX" or color == "success": + return Fore.LIGHTGREEN_EX+msg+Style.RESET_ALL + elif color == "LIGHTMAGENTA_EX" or color == "critical": + return Fore.LIGHTMAGENTA_EX+msg+Style.RESET_ALL + elif color == "LIGHTRED_EX" or color == "danger": + return Fore.LIGHTRED_EX+msg+Style.RESET_ALL + elif color == "LIGHTWHITE_EX": + return Fore.LIGHTWHITE_EX+msg+Style.RESET_ALL + elif color == "LIGHTYELLOW_EX" or color == "warning": + return Fore.LIGHTYELLOW_EX+msg+Style.RESET_ALL + else: + return msg + + def printTint(self, msg, color=None): + self.debug(self.tint(msg, color)) + +class Utils: + def __init__(self): + pass + + def getTemp_CPU(self): + response = subprocess.run(['cat', '/sys/class/thermal/thermal_zone0/temp'], stdout=subprocess.PIPE) + return ''.join(e for e in str(response.stdout) if e.isnumeric()) + + def ping(self, hostname="www.google.com", count=1): + response = None + if platform.system() == "Linux": + response = system("ping -c " + str(count) + " " + hostname + " > /dev/null 2>&1") + elif platform.system() == "Windows": + response = system("ping -n " + str(count) + " " + hostname + " > $null ") + + if response == 0: + return 1 + else: + return 0 + + def waitForInternet(self, retry_sec_min=1, increment_sec=10, retry_sec_max=60*3): + seconds = retry_sec_min + while not self.ping(): + self.log.warning( + "internet connection "+Fore.RED+"failed"+Style.RESET_ALL + +", retry in "+Fore.GREEN+"{}".format(seconds)+Style.RESET_ALL+" second(s)" + ) + time.sleep(seconds) + if seconds < retry_sec_max: + seconds += increment_sec + return True + + def sleep(self, min=1, max=-1): + if max == -1: + time.sleep(min) + else: + time.sleep(random.randint(min, max)) + + def float_trim(self, input, decimal): + x_str = str(input) + x = x_str.split(".") + result = str(x[0])+"."+str(x[1][:decimal]) + return(float(result)) \ No newline at end of file diff --git a/refs/jakt_v0.28.1.py b/refs/jakt_v0.28.1.py new file mode 100644 index 0000000..726dc6b --- /dev/null +++ b/refs/jakt_v0.28.1.py @@ -0,0 +1,978 @@ +from asyncio.log import logger +from sys import exit +from os import system, path, chdir +from collections import defaultdict +import sched, time +from numpy.core.fromnumeric import resize +import requests +import numpy as np +import talib as ta +#from binance.futures import Futures +from binance.cm_futures import CMFutures as Futures +from binance.error import ClientError +import json +import logging +import random +import urllib3 +import subprocess +import platform +from colorama import Fore, Style, init as colorama_init +import argparse +from tabulate import tabulate + + +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +colorama_init(autoreset=True) + + +class Bot: + def __init__(self, name, color, loggingLevel, LINE): + self.name = name + self.color = color + self.LINE = self.LINE(LINE["api"], LINE["token"], LINE["enable"]) + self.log = self.Log(name, color, loggingLevel, self.LINE, LINE["enableErrorNotify"]) + + class LINE(): + def __init__(self, lineApi, lineToken, lineEnable): + self.lineApi = lineApi + self.lineToken = lineToken + self.headers = {'Authorization':'Bearer '+self.lineToken} + self.lineEnable = lineEnable + + def notify(self, msg): + if self.lineEnable: + try: + return requests.post( + self.lineApi, + headers=self.headers, + data = {'message':msg}, + files=None + ) + except KeyError as e: + print(e) + + class Log: + def __init__(self, name, color, loggingLevel, LINE, enableErrorNotify): + self.logger = None + self.name = name + self.color = color + self.LINE=LINE + self.loggingLevel = loggingLevel + self.enableErrorNotify = enableErrorNotify + + logger = logging.getLogger() + if self.loggingLevel.upper() == "DEBUG": + logger.setLevel(logging.DEBUG) + elif self.loggingLevel.upper() == "INFO": + logger.setLevel(logging.INFO) + elif self.loggingLevel.upper() == "WARNING": + logger.setLevel(logging.WARNING) + elif self.loggingLevel.upper() == "ERROR": + logger.setLevel(logging.ERROR) + elif self.loggingLevel.upper() == "CRITICAL": + logger.setLevel(logging.CRITICAL) + else: + print("loggingLevel invalid [{}]".format(self.loggingLevel)) + logger.setLevel(logging.DEBUG) + sh = logging.StreamHandler() + sh.setFormatter(logging.Formatter('[%(asctime)s] %(message)s', datefmt='%Y/%m/%d %H:%M:%S')) + logger.addHandler(sh) + self.logger = logger + + def addPrefix(self, msg): + if self.color == "LIGHTMAGENTA_EX": + styledMsg = Fore.LIGHTMAGENTA_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTYELLOW_EX": + styledMsg = Fore.LIGHTYELLOW_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTCYAN_EX": + styledMsg = Fore.LIGHTCYAN_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTGREEN_EX": + styledMsg = Fore.LIGHTGREEN_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTRED_EX": + styledMsg = Fore.LIGHTRED_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + return styledMsg + + def debug(self, msg): + self.logger.debug(self.addPrefix(msg)) + + def info(self, msg): + self.logger.info(self.addPrefix(msg)) + + def warning(self, msg): + self.logger.warning(self.addPrefix(msg)) + + def error(self, msg): + self.logger.error(self.addPrefix(msg)) + if self.enableErrorNotify: + self.LINE.notify("{} error: {}".format(self.name, msg)) + + def critical(self, msg): + self.logger.critical(self.addPrefix(msg)) + if self.enableErrorNotify: + self.LINE.notify("critical: {}".format(self.name, msg)) + + def pprint(self, msg): + print(json.dumps(msg, indent=4, sort_keys=False)) + + def pretty(self, msg): + return(json.dumps(msg, indent=4, sort_keys=False)) + + def print(self, msg): + if self.color == "LIGHTMAGENTA_EX": + styled_msg = Fore.LIGHTMAGENTA_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTYELLOW_EX": + styled_msg = Fore.LIGHTYELLOW_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTCYAN_EX": + styled_msg = Fore.LIGHTCYAN_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTGREEN_EX": + styled_msg = Fore.LIGHTGREEN_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + elif self.color == "LIGHTRED_EX": + styled_msg = Fore.LIGHTRED_EX+"{} ".format(self.name)+Style.RESET_ALL+"{}".format(msg) + print(styled_msg) + + def getTemp_CPU(self): + response = subprocess.run(['cat', '/sys/class/thermal/thermal_zone0/temp'], stdout=subprocess.PIPE) + return ''.join(e for e in str(response.stdout) if e.isnumeric()) + + def ping(self, hostname="www.google.com", count=1): + response = None + if platform.system() == "Linux": + response = system("ping -c " + str(count) + " " + hostname + " > /dev/null 2>&1") + elif platform.system() == "Windows": + response = system("ping -n " + str(count) + " " + hostname + " > $null ") + + if response == 0: + return 1 + else: + return 0 + + def pingAPI(self): + response = None + return futures.time() + + def waitForInternet(self, retry_sec_min=1, increment_sec=10, retry_sec_max=60*3): + seconds = retry_sec_min + while not self.ping(): + self.log.warning( + "internet connection "+Fore.RED+"failed"+Style.RESET_ALL + +", retry in "+Fore.GREEN+"{}".format(seconds)+Style.RESET_ALL+" second(s)" + ) + time.sleep(seconds) + if seconds < retry_sec_max: + seconds += increment_sec + self.log.info(Fore.GREEN + "OK" + Style.RESET_ALL) + + def sleep(self, min=1, max=-1): + if max == -1: + time.sleep(min) + else: + time.sleep(random.randint(min, max)) + + def connectBinanceAPI(self, retry_sec_min=5, increment_sec=10, retry_sec_max=60*3): + seconds = retry_sec_min + connectionPassed = 0 + connectionTotal = 1 + + while connectionPassed < connectionTotal: + try: + response = futures.account() + connectionPassed += 1 + except ClientError as e: + self.log.error(e.error_message) + self.log.info( + "Binance API connection "+Fore.RED+"failed"+Style.RESET_ALL + +", attempt to fix the problem..." + ) + self.log.warning("time syncing function is under being developed!") + #### + # fixing goes here... + # under construction! + #### + self.log.warning( + "attempted, connect again in "+Fore.GREEN+"{}".format(seconds)+Style.RESET_ALL+" second(s)" + ) + time.sleep(seconds) + if seconds < retry_sec_max: + seconds += increment_sec + + if connectionPassed >= connectionTotal: + self.log.info(Fore.GREEN + "connected" + Style.RESET_ALL) + + def float_trim(self, input, decimal): + x_str = str(input) + x = x_str.split(".") + result = str(x[0])+"."+str(x[1][:decimal]) + return(float(result)) + +class JAKT(Bot): + def __init__(self, pair, color, loggingLevel, LINE): + self.symbol = pair["crypto"]+pair["asset"] + self.cryptoName = pair["crypto"] + self.assetName = pair["asset"] + self.strategy = pair["strategy"] + self.used_asset_percent = int(pair["used_asset_percent"]) + self.interval_when_closed = int(pair["interval_when_closed"]) + self.interval_when_opened = int(pair["interval_when_opened"]) + self.priority = int(pair["priority"]) + self.availableBalance = None + self.positionAmt = 0 + self.positionState = None # longed, shorted, or closed + self.orderAmtMin = float(pair["minimum_order_amt"]) # for BTC only, not sure for the others + self.leverage = 0 + self.cUnPnl = None + Bot.__init__(self, self.symbol, color, loggingLevel, LINE) + self.initialize() + + def connect_API(self): + self.log.info(futures.ping()) + + def cal_initMargin(self, price): + return float(float(price)*self.orderAmtMin/self.leverage) + + def cal_openLoss(self): + # = Number of contract * abs val {min[0, direction of order * (markprice - orderprice)]} + #return self.orderAmt*abs(min([0, 1*markprice-orderprice])) + pass + + def cal_QTY(self, price): + # under implementation! This could return the float, not int!? + qty = self.cal_QTY_max(float(price), self.used_asset_percent) * self.orderAmtMin + + #print(qty) + return self.float_trim(qty, 3) + + def cal_QTY_max(self, price, percent): + # under implementation! This could return the float, not int!? + return float((percent/100*self.availableBalance)/self.cal_initMargin(float(price))) + + def close(self): + response = None + self.sync_position() + if self.positionAmt > 0: + self.short() + self.positionState = "closed" + response = "longed positions are closed" + elif self.positionAmt < 0: + self.long() + self.positionState = "closed" + response = "shorted positions are closed" + else: + response = "no opened position to be closed" + time.sleep(1) + self.sync_position() + return response + + def close_all(self): + response = None + self.sync_position() + if self.positionAmt > 0: + self.short(self.positionAmt) + response = "longed positions are closed" + elif self.positionAmt < 0: + self.long(self.positionAmt) + response = "shorted positions are closed" + else: + response = "no opened position to be closed" + time.sleep(1) + self.sync_position() + self.positionState = "closed" + return response + + def fetch_closes(self, tf, samples=50): + return_data = [] + for each in requests.get( + "https://fapi.binance.com/fapi/v1/klines?symbol={}&interval={}&limit={}".format( + #"https://api.binance.com/api/v3/klines?symbol={}&interval={}&limit={}".format( + self.symbol, tf, samples + ) + ).json(): + return_data.append(float(each[4])) # we need only closed price + return np.array(return_data) + + def fetch_account(self): + try: + response = futures.account(ecvWindow=2000) + except ClientError as e: + response = e + return response + + def fetch_balance(self): + try: + response = futures.balance(recvWindow=2000) + for asset in response: + if asset["asset"] == self.assetName: + return asset["availableBalance"] + except ClientError as e: + return e + + def fetch_leverage(self): + for position in self.fetch_account()["positions"]: + if position["symbol"] == self.symbol: + return position["leverage"] + + def fetch_leverageBracket(self): + leverages = futures.leverage_brackets() + for leverage in leverages: + if leverage["symbol"] == self.symbol: + for bracket in leverage["brackets"]: + #if bracket["initialLeverage"] == self.leverage: + return bracket + + def fetch_openedOrders(self): + try: + response = futures.get_open_orders(symbol=self.symbol, recvWindow=2000) + except ClientError as e: + response = e + return response + + def fetch_position(self): + try: + risks = futures.get_positions(recvWindow=6000) + for risk in risks: + if risk["symbol"] == self.symbol: + return risk + except ClientError as e: + return e + + def fetch_position_amt(self): + val = self.fetch_position()["positionAmt"] + if val is None: + self.log.warning("NoneType returned from fetch_position()") + return float(0.0) + else: + return float(val) + + # not tested yet, but should work maybe + def fetch_UnPNL_cross(self): + account = self.fetch_account() + return account["totalCrossUnPnl"] + + def getReady(self): + try: + self.sync_position() + self.sync_stats() + return True + except NameError as e: + return False + + def initialize(self): + self.log.info("initialization started...") + if False: + #self.LINE.notify("Just notify") + self.log.debug("test debug") + self.log.info("test info") + self.log.warning("test warning") + self.log.error("test ERROR") + #self.log.critical("test CRITICAL!") + exit() + + if False: + #self.log.debug(self.used_asset_percent) + self.log.info(self.fetch_UnPNL_cross()) + #self.LINE("Hi, I'm {}->{}.".format(path.basename(__file__), self.symbol)) + self.log.info("end test.") + #exit() + + if True: + self.log.info("checking internet connection...") + self.waitForInternet() + self.sleep(1, 2) + if True: + self.log.info("connecting to Binance API...") + self.connectBinanceAPI() + self.sleep(1, 2) + if True: + #self.log.info("syncing position...") + self.sync_position() + self.log.info( + "current position: " + +Fore.GREEN + +"{} ".format(self.positionState) + +Style.RESET_ALL + +", amt:" + +Fore.GREEN + +" {} {}".format(self.positionAmt, self.cryptoName) + +Style.RESET_ALL + ) + self.sleep(1, 2) + # close all opened position if needed; default: False + if False: + self.log.warning("closing all positions if exist...") + self.log.info(self.close_all()) + self.sleep(2, 3) + if True: + self.sync_availableBlance() + self.log.info("available balance: "+Fore.GREEN+"{} {}".format(round(self.availableBalance, 4), self.assetName)+Style.RESET_ALL) + self.sleep(1, 2) + #self.log.info("syncing leverage...") + self.sync_leverage() + #self.pprint(self.fetch_leverageBracket()) + self.log.info("synced leverage: "+Fore.GREEN+"{}x".format(self.leverage)+Style.RESET_ALL) + self.sleep(1, 2) + self.log.info("used asset: "+Fore.GREEN+"{}%".format(self.used_asset_percent)+Style.RESET_ALL) + self.sleep(1, 2) + self.log.info( + "set by "+ + Fore.GREEN+ + "{}".format(self.strategy)+ + Style.RESET_ALL+ + " strategy" + ) + self.log.info( + "interval when closed: "+ + Fore.GREEN+"{} seconds".format(self.interval_when_closed)+ + Style.RESET_ALL + ) + self.log.info( + "interval when opened: "+ + Fore.GREEN+"{} seconds".format(self.interval_when_opened)+ + Style.RESET_ALL + ) + self.sleep(1, 2) + + self.log.info("initialization completed.") + #self.LINE("{} {} initialized".format(path.basename(__file__), self.symbol)) + self.log.info("-"*32) + self.sleep(1, 2) + + def long(self, quantity=None): + if quantity is None: + quantity = self.positionAmt + response = "self.action is set to False" + params = { + 'symbol': self.symbol, + 'side': 'BUY', + 'type': 'MARKET', + 'quantity': abs(quantity) + } + try: + response = futures.new_order(**params) + time.sleep(1) + self.sync_position() + self.log.info(Fore.GREEN+"Longed: {} {}".format(self.positionAmt, self.cryptoName)+Style.RESET_ALL) + except ClientError as e: + self.log.info("Long with {} {} failed. {}".format(quantity, self.cryptoName, e.error_message)) + response = e.error_message + return response + + def map(self, x, in_min, in_max, out_min, out_max): + ''' + long map(long x, long in_min, long in_max, long out_min, long out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; + } + ''' + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + + def printCurrentStats(self): + #self.log.info(self.positionState) + headers = [ + "strategy", + "avl[{}]".format(self.assetName), + "posStt", + "posAmt[{}]".format(self.cryptoName), + "lvrg", + "cUnPnl", + "asset%" + ] + row = [[ + self.strategy, + self.availableBalance, + self.positionState, + self.positionAmt, + self.leverage, + self.cUnPnl, + self.used_asset_percent + ]] + self.log.info("\n"+tabulate(row, headers, tablefmt="github")) + + def short(self, quantity=None): + if quantity is None: + quantity = self.positionAmt + response = "self.action is set to False" + params = { + 'symbol': self.symbol, + 'side': 'SELL', + 'type': 'MARKET', + 'quantity': abs(quantity) + } + try: + response = futures.new_order(**params) + #self.positionState = 'shorted' + time.sleep(1) + self.sync_position() + self.log.warning(Fore.GREEN+"Shorted: {} {}".format(self.positionAmt, self.cryptoName)+Style.RESET_ALL) + except ClientError as e: + self.log.error("Short position with {} qty failed. {}".format(quantity, e.error_message)) + response = e.error_message + return response + + def sync_availableBlance(self): + self.availableBalance = float(self.fetch_balance()) + + def sync_leverage(self): + self.leverage = int(self.fetch_leverage()) + return self.leverage + + def sync_position(self): + self.positionAmt = self.fetch_position_amt() + if self.positionAmt > 0: + self.positionState = "longed" + elif self.positionAmt < 0: + self.positionState = "shorted" + elif self.positionAmt == 0: + self.positionState = "closed" + else: + self.log.error("sync_position error") + + def sync_stats(self): + try: + account = self.fetch_account() + for position in account["positions"]: + if position["symbol"] == self.symbol: + self.leverage = int(position["leverage"]) + self.cUnPnl = float(account["totalCrossUnPnl"]) + except NameError as e: + self.log.error("sync_stats: "+e) + + def ta_consistency(self, tfs, mode="D"): + ''' + Default mode "D" means direction, return -1, 0, 1 stand for + consistence neg, not consistence, and consistence pos consequently. + "N" mode returns score -100 to 100 + ''' + consist = 0 + tf_count = len(tfs) + for _, tf_v in tfs.items(): + if tf_v > 0: + consist += 1 + else: + consist -= 1 + consist = consist/tf_count + + if mode == "N": + return consist + elif mode == "D": + if consist == 100: + return 1 + elif consist == -100: + return -1 + else: + return 0 + else: + return consist + + + def ta_ema(self, data, ta_ema=5): + return round(ta.EMA(data, ta_ema)[-2], 2) + + def ta_ema_comp_cross(self, data, ema_small=5, ema_large=10): + # + # p and n stand for positive and negative respectively + # + emas_small = ta.EMA(data, ema_small) + emas_large = ta.EMA(data, ema_large) + + if ((emas_small[-4]-emas_large[-4]) > 0) and ((emas_small[-3]-emas_large[-3]) > 0) and ((emas_small[-2]-emas_large[-2]) > 0): + return"ppp" + elif ((emas_small[-4]-emas_large[-4]) > 0) and ((emas_small[-3]-emas_large[-3]) > 0) and ((emas_small[-2]-emas_large[-2]) <= 0): + return "ppn" + elif ((emas_small[-4]-emas_large[-4]) > 0) and ((emas_small[-3]-emas_large[-3]) <= 0) and ((emas_small[-2]-emas_large[-2]) > 0): + return "pnp" + elif ((emas_small[-4]-emas_large[-4]) > 0) and ((emas_small[-3]-emas_large[-3]) <= 0) and ((emas_small[-2]-emas_large[-2]) <= 0): + return "pnn" + elif ((emas_small[-4]-emas_large[-4]) <= 0) and ((emas_small[-3]-emas_large[-3]) > 0) and ((emas_small[-2]-emas_large[-2]) > 0): + return "npp" + elif ((emas_small[-4]-emas_large[-4]) <= 0) and ((emas_small[-3]-emas_large[-3]) > 0) and ((emas_small[-2]-emas_large[-2]) <= 0): + return "npn" + elif ((emas_small[-4]-emas_large[-4]) <= 0) and ((emas_small[-3]-emas_large[-3]) <= 0) and ((emas_small[-2]-emas_large[-2]) > 0): + return "nnp" + elif ((emas_small[-4]-emas_large[-4]) <= 0) and ((emas_small[-3]-emas_large[-3]) <= 0) and ((emas_small[-2]-emas_large[-2]) <= 0): + return "nnn" + + def ta_ema_vector(self, data, ta_ema=5, depth=3, step=1): + # + # u and d stand for up and down respectively + # + emas = ta.EMA(data, ta_ema) + if depth == 2: + if emas[-(step+3)] > emas[-(step+2)] > emas[-(step+1)]: + return "dd" + elif emas[-(step+3)] > emas[-(step+2)] <= emas[-(step+1)]: + return "du" + elif emas[-(step+3)] <= emas[-(step+2)] > emas[-(step+1)]: + return "ud" + elif emas[-(step+3)] <= emas[-(step+2)] <= emas[-(step+1)]: + return "uu" + elif depth == 3: + if emas[-(step+4)] > emas[-(step+3)] > emas[-(step+2)] > emas[-(step+1)]: + return "ddd" + elif emas[-(step+4)] > emas[-(step+3)] > emas[-(step+2)] <= emas[-(step+1)]: + return "ddu" + elif emas[-(step+4)] > emas[-(step+3)] <= emas[-(step+2)] > emas[-(step+1)]: + return "dud" + elif emas[-(step+4)] > emas[-(step+3)] <= emas[-(step+2)] <= emas[-(step+1)]: + return "duu" + elif emas[-(step+4)] <= emas[-(step+3)] > emas[-(step+2)] > emas[-(step+1)]: + return "udd" + elif emas[-(step+4)] <= emas[-(step+3)] > emas[-(step+2)] <= emas[-(step+1)]: + return "udu" + elif emas[-(step+4)] <= emas[-(step+3)] <= emas[-(step+2)] > emas[-(step+1)]: + return "uud" + elif emas[-(step+4)] <= emas[-(step+3)] <= emas[-(step+2)] <= emas[-(step+1)]: + return "uuu" + + def ta_macd_delta(self, data, ema_small, ema_large): + return round(ta.EMA(data, ema_small)[-2]-ta.EMA(data, ema_large)[-2], 2) + + def ta_macd(self, data, fastperiod=12, slowperiod=26, signalperiod=9): + _, _, macdhist = ta.MACD(data, fastperiod, slowperiod, signalperiod) + return round(macdhist[-2], 2) + + def ta_macd_vector(self, data, fastperiod=12, slowperiod=26, signalperiod=9, step=1): + # + # u and d stand for up and down respectively + # + if len(data)*2 <= slowperiod: + return "not enough data." + else: + _, _, macdhist = ta.MACD(data, fastperiod, slowperiod, signalperiod) + if macdhist[-(step+4)] > macdhist[-(step+3)] > macdhist[-(step+2)] > macdhist[-(step+1)]: + return "ddd" + elif macdhist[-(step+4)] > macdhist[-(step+3)] > macdhist[-(step+2)] <= macdhist[-(step+1)]: + return "ddu" + elif macdhist[-(step+4)] > macdhist[-(step+3)] <= macdhist[-(step+2)] > macdhist[-(step+1)]: + return "dud" + elif macdhist[-(step+4)] > macdhist[-(step+3)] <= macdhist[-(step+2)] <= macdhist[-(step+1)]: + return "duu" + elif macdhist[-(step+4)] <= macdhist[-(step+3)] > macdhist[-(step+2)] > macdhist[-(step+1)]: + return "udd" + elif macdhist[-(step+4)] <= macdhist[-(step+3)] > macdhist[-(step+2)] <= macdhist[-(step+1)]: + return "udu" + elif macdhist[-(step+4)] <= macdhist[-(step+3)] <= macdhist[-(step+2)] > macdhist[-(step+1)]: + return "uud" + elif macdhist[-(step+4)] <= macdhist[-(step+3)] <= macdhist[-(step+2)] <= macdhist[-(step+1)]: + return "uuu" + + def ta_macd_zcross(self, data, fastperiod=12, slowperiod=26, signalperiod=9): + # + # p and n stand for positive and negative respectively + # + _, _, macdhist = ta.MACD(data, fastperiod, slowperiod, signalperiod) + if macdhist[-4] > 0 and macdhist[-3] > 0 and macdhist[-2] > 0: + return"ppp" + elif macdhist[-4] > 0 and macdhist[-3] > 0 and macdhist[-2] <= 0: + return "ppn" + elif macdhist[-4] > 0 and macdhist[-3] <= 0 and macdhist[-2] > 0: + return "pnp" + elif macdhist[-4] > 0 and macdhist[-3] <= 0 and macdhist[-2] <= 0: + return "pnn" + elif macdhist[-4] <= 0 and macdhist[-3] > 0 and macdhist[-2] > 0: + return "npp" + elif macdhist[-4] <= 0 and macdhist[-3] > 0 and macdhist[-2] <= 0: + return "npn" + elif macdhist[-4] <= 0 and macdhist[-3] <= 0 and macdhist[-2] > 0: + return "nnp" + elif macdhist[-4] <= 0 and macdhist[-3] <= 0 and macdhist[-2] <= 0: + return "nnn" + + def ta_insideway(self, rsi, ovs=40, ovb=60): + ''' + input rsi 0 to 100, output -100 to 100. -100 means recommend to short, and vice versa. + ''' + score = 0 + if rsi <= ovb and rsi >= ovs: + return score + elif rsi > ovb: + return self.map(rsi, ovb, 100, 0, 100) + elif rsi < ovs: + return self.map(rsi, 0, ovs, -100, 0) + + def ta_rsi(self, data, value=6): + return round(ta.RSI(data, value)[-2], 2) + + def ta_rsi_vector(self, data, value=6, step=1): + rsis = ta.RSI(data, value) + if rsis[-(step+4)] > rsis[-(step+3)] > rsis[-(step+2)] > rsis[-(step+1)]: + return "ddd" + elif rsis[-(step+4)] > rsis[-(step+3)] > rsis[-(step+2)] <= rsis[-(step+1)]: + return "ddu" + elif rsis[-(step+4)] > rsis[-(step+3)] <= rsis[-(step+2)] > rsis[-(step+1)]: + return "dud" + elif rsis[-(step+4)] > rsis[-(step+3)] <= rsis[-(step+2)] <= rsis[-(step+1)]: + return "duu" + elif rsis[-(step+4)] <= rsis[-(step+3)] > rsis[-(step+2)] > rsis[-(step+1)]: + return "udd" + elif rsis[-(step+4)] <= rsis[-(step+3)] > rsis[-(step+2)] <= rsis[-(step+1)]: + return "udu" + elif rsis[-(step+4)] <= rsis[-(step+3)] <= rsis[-(step+2)] > rsis[-(step+1)]: + return "uud" + elif rsis[-(step+4)] <= rsis[-(step+3)] <= rsis[-(step+2)] <= rsis[-(step+1)]: + return "uuu" + return None + + def ta_sar(self, data, timeperiod=30): + return ta.SAR(data.high, data.low, acceleration=0, maximum=0) + + def ta_vector_fibo(self, vals): + score = 0 + if vals[0] == "n" or vals[0] == "d": + score -= 21 + else: + score += 21 + if vals[1] == "n" or vals[1] == "d": + score -= 34 + else: + score += 34 + if vals[2] == "n" or vals[2] == "d": + score -= 55 + else: + score += 55 + return self.map(score, -110, 110, -100, 100) + + def run(self): + if(self.getReady()): + if self.strategy == "MACDRider": + indy = defaultdict(dict) + data = defaultdict(dict) + tf = "15m" + data = self.fetch_closes(tf) + if self.positionState == "closed": + pass + elif self.positionState == "longed": + pass + elif self.positionState == "shorted": + pass + else: + self.log.info("self.positionState: {}".format(self.positionState)) + #self.pprint(indy) + if self.strategy == "MA99surfer": + indy = defaultdict(dict) + data = defaultdict(dict) + tf = "1h" + data = self.fetch_closes(tf) + + if self.positionState == "closed": + pass + elif self.positionState == "longed": + pass + elif self.positionState == "shorted": + pass + else: + self.log.info("self.positionState: {}".format(self.positionState)) + + elif self.strategy == "K90_v1": + indy = defaultdict(dict) + data = defaultdict(dict) + criterion = defaultdict(dict) + k = defaultdict(dict) + + criterion["rsi_oversold"] = 40 + criterion["rsi_overbought"] = 60 + criterion["long_if_over"] = 20 + criterion["short_if_below"] = -20 + criterion["close_shorted_if_over"] = -10 + criterion["close_longed_if_below"] = 10 + + #tfs = ["15m", "1h", "4h"] + tfs = ["1h", "4h"] + tfs_weight = [1, 3] + k_weights = [ + #[2, 3, 3, 2], #fibo_macd_v, macd_zcross, rsi_v, insideway + [1, 1, 1, 1], + [2, 3, 3, 2]] + + ''' + prepare indicator values to be used in analysis process + ''' + for tf in tfs: + data[tf] = self.fetch_closes(tf) + #indy[tf]["macd"] = self.ta_macd(data[tf]) + indy[tf]["macd_v"] = self.ta_macd_vector(data[tf]) + indy[tf]["macd_zcross"] = self.ta_macd_zcross(data[tf]) + indy[tf]["rsi"] = self.ta_rsi(data[tf]) + indy[tf]["rsi_v"] = self.ta_rsi_vector(data[tf]) + indy[tf]["insideway"] = self.ta_insideway( + indy[tf]["rsi"], + criterion["rsi_oversold"], + criterion["rsi_overbought"] + ) + #self.pprint(indy) + + ''' + Calculate k_results for each timeframe, which is including fibo_macd_v, macd_zcross, rsi_v, and insideway consequently. + This segtion can be further designed more complex as desire. + ''' + k_results = defaultdict(dict) + weight_counter = 0 + for tf in tfs: + k[tf]["fibo_macd_v"] = k_weights[weight_counter][0] * self.ta_vector_fibo(indy[tf]["macd_v"]) + k[tf]["macd_zcross"] = k_weights[weight_counter][1] * self.ta_vector_fibo(indy[tf]["macd_zcross"]) + k[tf]["rsi_v"] = k_weights[weight_counter][2] * self.ta_vector_fibo(indy[tf]["rsi_v"]) + k[tf]["insideway"] = k_weights[weight_counter][3] * indy[tf]["insideway"] + k_result = ( + k[tf]["fibo_macd_v"] + + k[tf]["macd_zcross"] + + k[tf]["rsi_v"] + + k[tf]["insideway"] + ) / sum(k_weights[weight_counter]) + #self.pprint(k) + weight_counter += 1 + k_results[tf] = k_result + #self.pprint(k) + #self.pprint(k_results) + + ''' + Final segtion which will calculate the K_score, + which will be used as the criteria in openning the long or close position. + ''' + weight_counter = 0 + K_score = 0 + for tf in tfs: + K_score += k_results[tf]*tfs_weight[weight_counter] + weight_counter += 1 + K_score = K_score / sum(tfs_weight) + self.log.debug(round(K_score, 2)) + + self.log.debug(self.ta_consistency(k_results)) + exit() + ''' + After we've achived the K_score, OUR INCREDIBLE FACTOR, the actual trading is coming below... + ''' + #self.log.info(K_score) + if self.positionState == "closed": + qty = self.cal_QTY(data[tfs[0]][-1]) + if K_score >= criterion["long_if_over"]: + try: + self.long(qty) + self.log.info("K_score: {}, longed with {} {}".format(round(K_score, 2), qty, self.cryptoName)) + except KeyError as e: + self.log.error(e) + elif K_score <= criterion["short_if_below"]: + try: + self.short(qty) + self.log.info("K_score: {}, short with {} {}".format(round(K_score, 2), qty, self.cryptoName)) + except KeyError as e: + self.log.error(e) + else: + self.log.info("K_score: {}, sit on hands and keep waiting.".format(round(K_score, 2))) + elif self.positionState == "longed": + if K_score < criterion["close_longed_if_below"]: + try: + self.close() + self.log.info("K_score: {}, longed position just closed".format(round(K_score, 2))) + except KeyError as e: + self.log.error(e) + else: + self.log.info("K_score: {}, let {} profit run".format(round(K_score), self.positionState)) + elif self.positionState == "shorted": + if K_score > criterion["close_shorted_if_over"]: + try: + self.close() + self.log.info("K_score: {}, shorted position just closed".format(round(K_score, 2))) + except KeyError as e: + self.log.error(e) + else: + self.log.info("K_score: {}, let {} profit run".format(round(K_score), self.positionState)) + else: + self.log.warning("self.positionState: {}".format(self.positionState)) + #self.pprint(indy) + + elif self.strategy == "test": + time.sleep(5) + ''' + tf="3m" + data = self.fetch_closes(tf, 100) + self.pprint(self.ta_rsi_vector(data)) + ''' + elif self.strategy == "testOpenOrder": + tf="15m" + data = self.fetch_closes(tf, 1) + self.log.info("testing long...") + self.long(self.cal_QTY(data[0], 100)) + time.sleep(5) + self.log.info("testing close...") + self.close() + time.sleep(5) + self.log.info("testing short...") + self.short(self.cal_QTY(data[0], 100)) + time.sleep(5) + self.log.info("testing close...") + self.close() + self.log.info("end test strategy") + elif self.strategy == "MACDez": + pass + else: + self.log.warning("no strategy is set!") + + self.printCurrentStats() + if(self.positionState == "closed"): + scheduler.enter(self.interval_when_closed, self.priority, self.run) + else: + scheduler.enter(self.interval_when_opened, self.priority, self.run) + + +################################################ +# MAIN SECTION +################################################ +parser = argparse.ArgumentParser(description="JAKT bot") +parser.add_argument("-c", "--config", help="configuration JSON file", default="config.json") +args = vars(parser.parse_args()) + +while True: + if path.exists(args["config"]): + try: + f = open(args["config"], encoding = 'utf-8') + config = json.load(f) + f.close() + print("{} loaded".format(args["config"])) + except KeyError as e: + print(e) + exit() + + if config["customWD"]["enable"]: + if platform.system() == "Linux": + chdir(config["customWD"]["linux"]) + elif platform.system() == "Windows": + chdir(config["customWD"]["windows"]) + else: + print("Operating system unknown. Invalid WD might cause an error.") + + scheduler = sched.scheduler(time.time, time.sleep) + + ''' + futures = Futures( + key=config["BinanceAPI"]["key"], + secret=config["BinanceAPI"]["secret"], + base_url=config["BinanceAPI"]["base_url"] + ) + ''' + + futures = Futures(key=config["BinanceAPI"]["key"], secret=config["BinanceAPI"]["secret"]) + + pairsCount = 0 + for pair in config["pairs"]: + if not pair["enable"]: + continue + A = JAKT( + pair=pair, + color=config["static"]["colors"][pair["color"]], + loggingLevel=config["loggingLevel"], + LINE=config["LINE"] + ) + scheduler.enter(1, A.priority, A.run) + pairsCount += 1 + + if pairsCount > 0: + time.sleep(random.randint(2, 3)) + try: + print("getting started...") + scheduler.run() + except NameError as e: + print(e) + else: + print("no pair to run.") + else: + print("could not load the configuration file!") + + print("retry to load {} again in 30 seconds...".format(args["config"])) + time.sleep(30) \ No newline at end of file diff --git a/refs/main-v0.1.py b/refs/main-v0.1.py new file mode 100644 index 0000000..a16fdaf --- /dev/null +++ b/refs/main-v0.1.py @@ -0,0 +1,10 @@ +from recyclebin.binancesloth import BinanceSloth +import argparse + +parser = argparse.ArgumentParser(description="BinanceSloth bot by GeekSloth") +parser.add_argument("-c", "--config", help="configuration JSON file", default="config.json") +args = vars(parser.parse_args()) + +bot = BinanceSloth(config=args["config"]) +print(bot.logging.tint("Test colored msg", color="LIGHTGREEN_EX")) +print("end main") \ No newline at end of file diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..ace112a --- /dev/null +++ b/run.bat @@ -0,0 +1 @@ +docker compose up \ No newline at end of file