From b1835e2f725ff2d76fa81462efb75097e4fb1aa7 Mon Sep 17 00:00:00 2001 From: Beesquit Date: Thu, 22 May 2025 14:27:04 +0300 Subject: [PATCH] Saving logs+data to dir. fixes --- .gitignore | 1 + README-EN.md | 45 +++++++++++++++++++++++++++++++++++++++ README.md | 49 +++++++++++++++++++++++++++++++++++++++---- compose.yml | 15 +++++++++++++ setup.md | 25 ---------------------- src/arbus.py | 2 +- src/bybit.py | 27 ++++++++++++++++++++++++ src/jsonProcessing.py | 32 ++++++++++++++++------------ src/logger.py | 14 ++++++++----- src/main.py | 29 +++++++++++++++++++------ src/strings.py | 4 ++++ todo.md | 17 ++++++++------- 12 files changed, 198 insertions(+), 62 deletions(-) create mode 100644 README-EN.md create mode 100644 compose.yml delete mode 100644 setup.md diff --git a/.gitignore b/.gitignore index 00c1212..bcbf966 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ src/test2.py tradingLog.log generalLog.log src/.env +data/ diff --git a/README-EN.md b/README-EN.md new file mode 100644 index 0000000..51f8569 --- /dev/null +++ b/README-EN.md @@ -0,0 +1,45 @@ +# [ENG] Side Strategy Bybit Bot +This is a simple semi-automatic trading bot working with Bybit API and written in Python. +The strategy is based on the side moving of the token which crosses user input levels. On the level cross long and short orders are opened. + +# Install guide +Bot is installed via **docker compose** file and **docker pull** command. +This "guide" is made for **linux** and **docker**, but you should be able to run this bot on any system with *Python 3*. +### Installing via `docker compose`: +1) Create bot directory +2) Pull image from git: +```sh +docker pull git.frik.su/eugenebee/tradingbot-with-bybitapi:latest +``` +3) Create **compose.yml** file: +```yaml +services: + bybit-bot: + image: git.frik.su/eugenebee/tradingbot-with-bybitapi:latest + container_name: bybit-bot + environment: + API_KEY: "bybit-API-key" + API_SECRET: "bybit-secret-API-key" + BOT_TOKEN: "telegram-bot-token" + WHITELIST: "chat-id-1, chat-id-2" + LEVERAGE: "1" # Currently not supported. Set leverage in your Bybit account + TESTNET: "False" + DEMOTRADING: "False" + LOOPSLEEPTIME: "1" + SHOWEXTRADEBUGLOGS: "False" + restart: unless-stopped +``` +### After the basic pre-setup: +*It is recommended to use Bybit sub accaunt forn the bot.* +1) Get your **API** keys for the **Bybit** and **Telegram** +2) Enter those in the **compose.yml** variables and your **Telegram id** into the `WHITELIST` +2) At the **Bybit** platform turn on hedge mode foor all tokens you want the bot to trade +3) Read the milk leaves and then make ***4*** circles around the server with the bee hive in your hands +4) Everything is ready for the first start, enjoy! +### To start up the bot enter the next command: +```sh +docker compose up -d +``` +*If you need use the `sudo` before the command* + +Thank you for reafing, hope everything will work fine! diff --git a/README.md b/README.md index b1d1878..f543223 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,45 @@ -# Side Strategy Bybit Bot -This is a simple semi-automatic trading bot working with Bybit API and written in Python. -The strategy is based on the side moving of the token which crosses user input levels. On the level cross long and short orders are opened. -### For the install and setup guide look at the `setup.md` +# [RU] Side Strategy Bybit Bot +Это достаточно простой полуавтоматический торговый бот, работающий с Bybit API, написанный на Python. +Стратегия основана на боковом движении цены токена и пересечении заданных пользователей уровней. При пересечении уровня открываются long и short позиции. + +## Установка +Бот устанавливается с помощью **docker compose** файла и **docker pull**. +Данный "гайд" расчитан на **linux** и **docker**, однако вы можете развернуть данное приложение/бота на любой системе с *Python 3*. +### Для установки через `docker compose`: +1) Создайте директорию для бота +2) Введите команду: +```sh +docker pull git.frik.su/eugenebee/tradingbot-with-bybitapi:latest +``` +3) Создайте **compose.yml** файл с следующим содержимым: +```yaml +services: + bybit-bot: + image: git.frik.su/eugenebee/tradingbot-with-bybitapi:latest + container_name: bybit-bot + environment: + API_KEY: "bybit-API-key" + API_SECRET: "bybit-secret-API-key" + BOT_TOKEN: "telegram-bot-token" + WHITELIST: "chat-id-1, chat-id-2" + LEVERAGE: "1" # Временно не поддерживается, устанавливайте плечо в вашем Bybit аккаунте + TESTNET: "False" + DEMOTRADING: "False" + LOOPSLEEPTIME: "1" + SHOWEXTRADEBUGLOGS: "False" + restart: unless-stopped +``` +### После базовой развёртки выполните следующие шаги: +*Рекомендуется использовать суб аккаунт Bybit для бота.* +1) Получите **API** ключи на **Bybit** и **Telegram**. Рекомендуем использовать суб аккаунт Bybit для бота +2) Введите в **compose.yml** свои ключи в соответствующие поля и ваш **Telegram id** в `WHITELIST` +2) На платформе **Bybit** включите режим хеджирования на все пары, которыми планируете торговать +3) Погадайте на молочной гуще и сделайте ***4*** круга с пчелиным ульем вокруг сервера +4) Всё готово к запуску, наслаждайтесь! +### Для запуска бота введите следующую команду: +```sh +docker compose up -d +``` +*По надобности используйте `sudo` перед командой* + +Спасибо что заглянули, желаем удачной настройки и стабильной работы! diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..34b9874 --- /dev/null +++ b/compose.yml @@ -0,0 +1,15 @@ +services: + bybit-bot: + image: git.frik.su/eugenebee/tradingbot-with-bybitapi:latest + container_name: bybit-bot + environment: + API_KEY: "bybit-API-key" + API_SECRET: "bybit-secret-API-key" + BOT_TOKEN: "telegram-bot-token" + WHITELIST: "chat-id-1, chat-id-2" + LEVERAGE: "1" # Not supported. Set leverage in bybit account + TESTNET: "False" + DEMOTRADING: "False" + LOOPSLEEPTIME: "1" + SHOWEXTRADEBUGLOGS: "False" + restart: unless-stopped \ No newline at end of file diff --git a/setup.md b/setup.md deleted file mode 100644 index 6066042..0000000 --- a/setup.md +++ /dev/null @@ -1,25 +0,0 @@ -# Краткий гайд по установке и настройке бота. -Бот устанавливается с помощью **docker compose** файла и **git pull** (предварительно) и последующей лёгкой настройки через файлы. -Данный "гайд" расчитан на **linux** и **docker**, однако вы можете развернуть данное приложение/бота на любой системе с *Python 3*. -### Для установки через `docker compose`: -1) Создайте директорию для бота -2) Введите команду: -``` -git clone -``` -### После базовой развёртки выполните следующие шаги: -1) Создайте data.json файл и заполните его данным содержимым: -```> {}``` -2) Создайте файл `credentials.py` в `src/` и заполните его по аналогии с `cretentialsExample.py` -*Рекомендуется использовать суб аккаунт Bybit для бота* -3) Настройте options.py файл на ваше усмотрение (обратите особо внимание на параметр `testnet`) -4) Погадайте на молочной гуще и сделайте ***4*** круга с пчелиным ульем вокруг сервера -5) Всё готово к запуску, наслаждайтесь! -### Для запуска бота введите следующую команду: -``` -docker compose up -d -``` -*По надобности используйте `sudo` перед командой* -6) На платформе **Bybit** включите режим хеджирования на все пары, которыми планируете торговать - -Спасибо что заглянули, желаем удачной настройки и стабильной работы! diff --git a/src/arbus.py b/src/arbus.py index cee9be5..0c0d49f 100644 --- a/src/arbus.py +++ b/src/arbus.py @@ -7,10 +7,10 @@ from random import randint startTime = None def setStartTime(): + global startTime startTime = time.time() def getPnL(pair): - PnL = 0.0 with open("tradingLog.log", "r") as f: lines = f.readlines() diff --git a/src/bybit.py b/src/bybit.py index 04b56dd..a72f2d9 100644 --- a/src/bybit.py +++ b/src/bybit.py @@ -14,6 +14,33 @@ from logger import generalLogger from logger import tradingLogger +def getPrice(client, pair): + ticker = client.get_tickers( + category = "linear", + symbol = pair + ) + price = float(ticker.get('result').get('list')[0].get('ask1Price')) + return price + +def getStartBalance(client, pair): + coin = pair[:-4] + response = client.get_wallet_balance( + accountType = "UNIFIED", + coin = coin + ) + balance = float(response['result']['list'][0]['totalAvailableBalance']) + return balance + +def getStartFilters(client, pair): + instrumentInfo = client.get_instruments_info( + symbol = pair, + category = "linear" + ) + infoContents = instrumentInfo.get('result').get('list')[0] + minimumQty = float(infoContents.get('lotSizeFilter').get('minOrderQty')) + return minimumQty + + class tradingData: def __init__(self, pair, levels, highBreak, lowBreak, takeDelta, stopDelta, orderSize): self.client = HTTP( diff --git a/src/jsonProcessing.py b/src/jsonProcessing.py index f1d6cc0..806da17 100644 --- a/src/jsonProcessing.py +++ b/src/jsonProcessing.py @@ -8,12 +8,18 @@ from logger import generalLogger import options +jsonPath = 'data/data.json' + + def startUp(): - filePath = 'data.json' + filePath = jsonPath + + if not(os.path.exists('data')): + os.mkdir('data') if os.path.exists(filePath): - timestamp = datetime.now().strftime('%Y-%m-%d_%H:%M:%S') - backupPath = (f'data_backup_{timestamp}.json') + timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + backupPath = (f'data/data_backup_{timestamp}.json') shutil.copy(filePath, backupPath) generalLogger.info(f'JSON backup was created: {backupPath}') @@ -47,10 +53,10 @@ async def checkPair(pair: str): # Returnes 1 if pair exists and 0 if not currentData = {} try: - with open('data.json', 'r') as f: + with open(jsonPath, 'r') as f: currentData = json.load(f) except json.decoder.JSONDecodeError as e: - generalLogger.info('WARNING: JSON file is empty! Ignore if your installation is fresh.') + generalLogger.warning('JSON file is empty!') if pair in currentData: return 1 @@ -61,14 +67,14 @@ async def deletePair(pair: str): # Returnes 0 if deleted successfully and -1 if not currentData = {} try: - with open('data.json', 'r') as f: + with open(jsonPath, 'r') as f: currentData = json.load(f) except json.decoder.JSONDecodeError as e: - generalLogger.info('WARNING: JSON file is empty! Ignore if your installation is fresh.') + generalLogger.warning('JSON file is empty!') if pair in currentData: del currentData[pair] - with open('data.json', 'w', encoding = 'utf-8') as f: + with open(jsonPath, 'w', encoding = 'utf-8') as f: json.dump(currentData, f, ensure_ascii = False, indent = 4) generalLogger.info(f'Pair {pair} was deleted successfully!') return 0 @@ -85,16 +91,16 @@ async def savePairParams(pair: str, params): currentData = {} try: - with open('data.json', 'r') as f: + with open(jsonPath, 'r') as f: currentData = json.load(f) except json.decoder.JSONDecodeError as e: - generalLogger.info('WARNING: JSON file is empty! Ignore if your installation is fresh.') + generalLogger.warning('JSON file is empty!') if pair in currentData: generalLogger.info(f"Pair {pair} already exists.") return -2 else: - with open('data.json', 'w', encoding = 'utf-8') as f: + with open(jsonPath, 'w', encoding = 'utf-8') as f: currentData.update(newData) json.dump(currentData, f, ensure_ascii = False, indent = 4) generalLogger.info(f"Pair {pair} was added!") @@ -105,8 +111,8 @@ async def loadJson(): # Returnes the contents of the JSON file as a dictionary data = {} try: - with open('data.json', 'r') as f: + with open(jsonPath, 'r') as f: data = json.load(f) except json.decoder.JSONDecodeError as e: - generalLogger.info('WARNING: JSON file is empty! Ignore if your installation is fresh.') + generalLogger.warning('JSON file is empty!') return data diff --git a/src/logger.py b/src/logger.py index 6730cc7..9985bcf 100644 --- a/src/logger.py +++ b/src/logger.py @@ -1,11 +1,12 @@ import logging import sys +import os import options -generalLogPath = "./generalLog.log" -tradingLogPath = "./tradingLog.log" +generalLogPath = "./data/generalLog.log" +tradingLogPath = "./data/tradingLog.log" def setupLogger(name, level, logPath, formatter): @@ -23,15 +24,18 @@ def setupLogger(name, level, logPath, formatter): return logger +if not(os.path.exists('data')): + os.mkdir('data') + # Основной лог generalFormatter = logging.Formatter('%(asctime)s - %(module)s - %(levelname)s - %(message)s') generalLogger = setupLogger('general', logging.INFO, generalLogPath, generalFormatter) -# Торговый лог (ордера) +# Торговый лог (открытие, закрытие ордеров, запуск стратегии) tradingFormatter = logging.Formatter('%(asctime)s - %(message)s') tradingLogger = setupLogger('trade', logging.NOTSET, tradingLogPath, tradingFormatter) -# Максимально подробный общий лог, вызывает повторы в логировании. Его наличие настраивается +# Максимально подробный дебаг лог, вызывает повторы в логировании. Его наличие настраивается if options.showExtraDebugLogs: - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') superGeneralLogger = logging.getLogger('superGeneral') diff --git a/src/main.py b/src/main.py index 3297a83..89de3a6 100644 --- a/src/main.py +++ b/src/main.py @@ -115,6 +115,7 @@ async def capture_params(message: Message, state: FSMContext): data = await state.get_data() t = await jsonProcessing.savePairParams(pair=data.get("pair"), params=data.get("params")) + params = await jsonProcessing.parseParams(params=data.get("params")) if t == 0: client = await bybit.getClient( credentials.api_key, @@ -124,15 +125,31 @@ async def capture_params(message: Message, state: FSMContext): ) if client == -1: msgText = strings.authFailed + generalLogger.info("Auth failed. Strategy not started") await jsonProcessing.deletePair(pair=data.get("pair")) else: - try: - asyncio.create_task(bybit.strategy(data.get("pair"), data.get("params"))) - msgText = (f'Вы запустили стратегию на паре {data.get("pair")} \ - с данными параметрами:\n{data.get("params")}\n') - except: + orderSize = float(params.get('orderSize')) + minqty = bybit.getStartFilters(client, data.get("pair")) + qtyDecimals = arbus.countDecimals(minqty) + balance = bybit.getStartBalance(client, data.get("pair")) + price = bybit.getPrice(client, data.get("pair")) + + qty = arbus.floor(orderSize/price, qtyDecimals) + if qty <= minqty: + generalLogger.info("Qty < minqty. Strategy not started") + msgText = strings.orderSizeLowerThanQty await jsonProcessing.deletePair(pair=data.get("pair")) - msgText = (strings.strategyError) + elif balance <= orderSize: + generalLogger.info("Balance < order size. Strategy not started") + msgText = strings.notEnoughBalance + await jsonProcessing.deletePair(pair=data.get("pair")) + else: + try: + asyncio.create_task(bybit.strategy(data.get("pair"), data.get("params"))) + msgText = (f'Вы запустили стратегию на паре {data.get("pair")} с данными параметрами:\n{data.get("params")}\n') + except: + await jsonProcessing.deletePair(pair=data.get("pair")) + msgText = (strings.strategyError) elif t == -1: msgText = strings.wrongFormat elif t == -2: diff --git a/src/strings.py b/src/strings.py index d91daaa..53bf52a 100644 --- a/src/strings.py +++ b/src/strings.py @@ -54,6 +54,10 @@ pairNotFound = "Стратегия на данную монетную пару authFailed = (f"Аутентификация не удалась, пожалуйста сообщите администратору если увидете данное сообщение.") +orderSizeLowerThanQty = "Введённая сумма меньше минимального размера ордера на Bybit." + +notEnoughBalance = "На балансе недостаточно средств для зпуска стратегии." + wrongFormat = "Параметры введены в неверном формате, пожалуйста начните заново." diff --git a/todo.md b/todo.md index 675fe38..65d0e26 100644 --- a/todo.md +++ b/todo.md @@ -2,6 +2,8 @@ ### ToFix - [x] ID бота (создать нового) - [x] Замена print на логирование + - [x] EN README.md + - [ ] UA README.md ### Новые функции - [x] Реализация базы программы @@ -21,19 +23,18 @@ - - - [x] Strategy (Запуск стратегии) - - - [x] Stop (Остановка стратегии) - - - [x] Info (Информация о запущеных стратегиях) - - - - [ ] Проверка на баланс и минимальное количество + - - - [x] Проверка на баланс и минимальное количество - - [x] Обеспечение безопасности и приватности бота (через chat id или пароль) - [x] Реализация стратегии - - [x] Основная функция для запуска стратегии - - [x] Класс работы по параметрам - - [x] Реализация уровней - - [x] Установка позиций - - [ ] Рализация развёртывания программы - - - [ ] Написать compose.yml - - - [ ] Добавить requirements.txt - - - [ ] Сделать подсасывание контейнера с гита + - [x] Рализация развёртывания программы + - - [x] Написать compose.yml + - - [x] Добавить requirements.txt + - - [x] Сделать подсасывание контейнера с гита - - [x] Составить список и реализовать получение переменных окружения - - [ ] QOL + - [x] QOL - - [x] Написать todo.md - - - [ ] Написать README.md - - - [ ] Написать setup.md + - - [x] Написать README.md