From 808f7112f7a10ba8f0e9ca67c079dba55b500b78 Mon Sep 17 00:00:00 2001 From: EugeneBee Date: Mon, 19 May 2025 15:45:30 +0300 Subject: [PATCH] Added status and PnL --- src/arbus.py | 48 +++++++++++++++++++++++++++++++++++++++++-- src/bybit.py | 9 +++++--- src/jsonProcessing.py | 2 +- src/logger.py | 2 +- src/main.py | 39 +++++++++++++++++++++++------------ src/strings.py | 11 +++++++++- todo.md | 1 + 7 files changed, 91 insertions(+), 21 deletions(-) diff --git a/src/arbus.py b/src/arbus.py index 09aa2a2..cee9be5 100644 --- a/src/arbus.py +++ b/src/arbus.py @@ -1,8 +1,52 @@ -import options +import time +from datetime import datetime +import re from random import randint -async def getLevels(amount, highPrice, lowPrice, roundDecimals): +startTime = None + +def setStartTime(): + startTime = time.time() + +def getPnL(pair): + PnL = 0.0 + with open("tradingLog.log", "r") as f: + lines = f.readlines() + + logEntries = [] + for i in lines: + # Get timestamp + message tuples + t = re.match(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}) - (.+)", i) + if t: + timestamp_str, message = t.groups() + timestamp = datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S,%f") + logEntries.append((timestamp, message.strip())) + + strategyStartTime = None + for timestamp, message in logEntries: + if message == f"Starting strategy with {pair}": + strategyStartTime = timestamp + + if not strategyStartTime: + print(f"No 'Starting strategy' found for pair {pair}") + return 0.0 + + totalPnL = 0.0 + pattern = re.compile( + rf"(Long|Short) order on {re.escape(pair)} level .*? filled with P&L (-?\d+\.?\d*) and qty .*" + ) + + for timestamp, message in logEntries: + if timestamp >= strategyStartTime: + match = pattern.search(message) + if match: + t = float(match.group(2)) + totalPnL += t + + return totalPnL + +def getLevels(amount, highPrice, lowPrice, roundDecimals): # Returns array of prices from low to high for each level levels = [] delta = (highPrice - lowPrice)/amount diff --git a/src/bybit.py b/src/bybit.py index 6a667fb..04b56dd 100644 --- a/src/bybit.py +++ b/src/bybit.py @@ -126,6 +126,7 @@ class tradingData: tradingLogger.info(f"Placed oder on {self.pair} with TP {tp}; SL {sl}") else: generalLogger.warning(f"Failed to place order on {self.pair}; qty is too small!") + tradingLogger.warning(f"Failed to place order on {self.pair}; qty is too small!") return orderID @@ -233,8 +234,9 @@ async def strategy(pair: str, params): priceDecimals = int(infoContents.get('priceScale')) - # Levels have 3 IDs for both long and short position. The 1 is main order (market opening), 2 is SL and 3 is TP. - levelsRaw = await arbus.getLevels(levelsAmount, highEnd, lowEnd, priceDecimals) + # Levels have 3 IDs for both long and short position + # The first is main order (market opening), second is SL and third is TP + levelsRaw = arbus.getLevels(levelsAmount, highEnd, lowEnd, priceDecimals) levels = [] for i in range(levelsAmount): levels.append({'price':levelsRaw[i], 'long':False, 'longIDs':['-1', '-1', '-1'], 'short':False, 'shortIDs':['-1', '-1', '-1']}) @@ -277,7 +279,7 @@ async def strategy(pair: str, params): while t: t = await jsonProcessing.checkPair(pair) if t != 1: - generalLogger.info("Closing websockets for {pair}") + generalLogger.info(f"Closing websockets for {pair}") ws.exit() wsPrivate.exit() break @@ -285,4 +287,5 @@ async def strategy(pair: str, params): i += 1 generalLogger.info(f"Ending strategy with {pair}; Ended on the iteration number {i}") + tradingLogger.info(f"Ending strategy with {pair}") return i diff --git a/src/jsonProcessing.py b/src/jsonProcessing.py index 78e4fd7..f1d6cc0 100644 --- a/src/jsonProcessing.py +++ b/src/jsonProcessing.py @@ -12,7 +12,7 @@ def startUp(): filePath = 'data.json' if os.path.exists(filePath): - timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + timestamp = datetime.now().strftime('%Y-%m-%d_%H:%M:%S') backupPath = (f'data_backup_{timestamp}.json') shutil.copy(filePath, backupPath) generalLogger.info(f'JSON backup was created: {backupPath}') diff --git a/src/logger.py b/src/logger.py index 31d633f..6730cc7 100644 --- a/src/logger.py +++ b/src/logger.py @@ -31,7 +31,7 @@ generalLogger = setupLogger('general', logging.INFO, generalLogPath, generalForm tradingFormatter = logging.Formatter('%(asctime)s - %(message)s') tradingLogger = setupLogger('trade', logging.NOTSET, tradingLogPath, tradingFormatter) -# Максимально подробный общий лог +# Максимально подробный общий лог, вызывает повторы в логировании. Его наличие настраивается if options.showExtraDebugLogs: logging.basicConfig(level=logging.DEBUG) superGeneralLogger = logging.getLogger('superGeneral') diff --git a/src/main.py b/src/main.py index 0838639..3297a83 100644 --- a/src/main.py +++ b/src/main.py @@ -1,3 +1,5 @@ +import time +from datetime import datetime import asyncio from aiogram import Bot, Dispatcher @@ -16,6 +18,7 @@ from logger import generalLogger from logger import tradingLogger import bybit +import arbus import jsonProcessing import credentials @@ -54,25 +57,32 @@ stop_router = Router() @dp.message(Command("start")) async def commandStart(message: Message) -> None: - # print(whitelist.chatIDs) - # id = message.from_user.id - # print('Got message from', id, ' with type ', type(id)) await message.answer(strings.startCommand) @dp.message(Command("help"), F.chat.id.in_(whitelist.chatIDs)) async def commandHelp(message: Message) -> None: await message.answer(strings.helpCommand) +@dp.message(Command("status"), F.chat.id.in_(whitelist.chatIDs)) +async def commandStatus(message: Message) -> None: + currentTime = time.time() + timeDiff = round(currentTime - arbus.startTime) # Время работы в секундах + timeDiffH = round(timeDiff/60/60, 3) # Время работы в часах + await message.answer(strings.status + str(timeDiff) + ' секунд' \ + + ' (' + str(timeDiffH) + ' часов)') + + @dp.message(Command("info"), F.chat.id.in_(whitelist.chatIDs)) async def commandInfo(message: Message) -> None: data = await jsonProcessing.loadJson() - msgText = strings.foundData + msgText = '' if data == {}: msgText = strings.noData else: msgText = strings.foundData for i in data: - msgText += (f"{str(i)}: P&L - x%\n") + pnl = arbus.getPnL(str(i)) + msgText += (f"{str(i)}: P&L - {pnl}%\n") await message.answer(msgText) @@ -88,7 +98,7 @@ async def capture_start_pair(message: Message, state: FSMContext): t = 0 if await jsonProcessing.checkPair(data.get("pair")) == 1: - msgText = (f'Стратегия на паре {data.get("pair")} уже запущена.\nПожалуйста остановите стратегию либо введите другую пару.') + msgText = strings.strategyAlreadyRunning t = 1 else: msgText = strings.askParams @@ -118,16 +128,17 @@ async def capture_params(message: Message, state: FSMContext): else: try: asyncio.create_task(bybit.strategy(data.get("pair"), data.get("params"))) - msgText = (f'Вы запустили стратегию на паре {data.get("pair")} с данными параметрами:\n{data.get("params")}\n') + msgText = (f'Вы запустили стратегию на паре {data.get("pair")} \ + с данными параметрами:\n{data.get("params")}\n') except: await jsonProcessing.deletePair(pair=data.get("pair")) - msgText = (f'Возникла ошибка в работе стратегии =( Пожалуйста сообщите об этом администратору.') + msgText = (strings.strategyError) elif t == -1: - msgText = (f'Параметры введены в неверном формате, пожалуйста начните заново.') + msgText = strings.wrongFormat elif t == -2: - msgText = (f'Стратегия на паре {data.get("pair")} уже запущена.') + msgText = (f"Стратегия на паре {data.get("pair")} уже запущена.") else: - msgText = (f'Возникла непредвиденная ошибка. =(') + msgText = strings.unexpectedError await message.answer(msgText) await state.clear() @@ -165,7 +176,9 @@ async def main() -> None: # Main if __name__ == "__main__": - generalLogger.info("Started bot!") - tradingLogger.info("Started bot!") + arbus.setStartTime() jsonProcessing.startUp() + currentTime = datetime.now().strftime("%H:%M:%S") + generalLogger.info(f"Started bot!") + tradingLogger.info(f"Started bot!") asyncio.run(main()) diff --git a/src/strings.py b/src/strings.py index a68f74a..d91daaa 100644 --- a/src/strings.py +++ b/src/strings.py @@ -8,6 +8,10 @@ startStrategy = "Стратегия запущена!" stopStrategy = "Стратегия остановлена!" +strategyAlreadyRunning = "Стратегия на данной паре уже запущена.\nПожалуйста остановите стратегию либо введите другую пару." + +strategyError = "Возникла ошибка в работе стратегии =( Пожалуйста сообщите об этом администратору." + # Bot Statuses @@ -15,6 +19,9 @@ startBot = "Бот запущен!" stopBot = "Бот остановлен!" +status = "Бот работает в течение " + +unexpectedError = "Возникла непредвиденная ошибка. =(" # Commands @@ -45,7 +52,9 @@ gotParams = "Параметры заданы!" pairNotFound = "Стратегия на данную монетную пару не найдена." -authFailed = (f'Аутентификация не удалась, пожалуйста сообщите администратору если увидете данное сообщение.') +authFailed = (f"Аутентификация не удалась, пожалуйста сообщите администратору если увидете данное сообщение.") + +wrongFormat = "Параметры введены в неверном формате, пожалуйста начните заново." # Data status diff --git a/todo.md b/todo.md index fad1636..675fe38 100644 --- a/todo.md +++ b/todo.md @@ -21,6 +21,7 @@ - - - [x] Strategy (Запуск стратегии) - - - [x] Stop (Остановка стратегии) - - - [x] Info (Информация о запущеных стратегиях) + - - - [ ] Проверка на баланс и минимальное количество - - [x] Обеспечение безопасности и приватности бота (через chat id или пароль) - [x] Реализация стратегии - - [x] Основная функция для запуска стратегии