From 98d770efae892246984fbce199e2fff1063bed71 Mon Sep 17 00:00:00 2001 From: EugeneBee Date: Tue, 3 Jun 2025 17:41:28 +0300 Subject: [PATCH] Auto strategy close fix --- src/bybit.py | 23 ++++++++++++++++------- src/jsonProcessing.py | 18 ++++++++++-------- src/logger.py | 5 +++++ src/main.py | 19 ++++++++++--------- src/strings.py | 4 +++- todo2.md | 10 ++++++++++ 6 files changed, 54 insertions(+), 25 deletions(-) create mode 100644 todo2.md diff --git a/src/bybit.py b/src/bybit.py index 0fe4c88..0303437 100644 --- a/src/bybit.py +++ b/src/bybit.py @@ -12,6 +12,7 @@ import arbus from logger import generalLogger from logger import tradingLogger +from logger import debugLogger def getPrice(client, pair): @@ -60,6 +61,7 @@ class tradingData: self.priceDecimals, self.qtyDecimals, self.minimumQty = self.getFilters(pair) self.previousPrice = -1 self.orderCounter = 0 + self.closed = 0 def getBalance(self, pair): @@ -84,13 +86,18 @@ class tradingData: return priceDecimals, qtyDecimals, minimumQty def close(self): - jsonProcessing.deletePair(self.pair) + self.closed = 1 + t = jsonProcessing.deletePair(self.pair) generalLogger.info(f"Closing strategy on {self.pair}") def checkCloseConditions(self, markPrice): # If the price is outside of the (lowBreak; highBreak) interval then stop strategy - if not (self.lowBreak < markPrice < self.highBreak): - self.close() + if self.closed == 0: + if not (self.lowBreak < markPrice < self.highBreak): + self.close() + elif self.closed == 1: + generalLogger.error(f"Called checkCloseConditions with selfClose = 1 on {self.pair}") + self.closed = 2 def checkOrderConditions(self, markPrice): @@ -173,7 +180,9 @@ class tradingData: def handlePositionInfo(self, message): data = message.get('data') - + + debugLogger.debug(data) + # Usually the 3-order response means SL + market + TP orders were placed. if len(data) == 3: orderType = [] @@ -252,7 +261,7 @@ async def strategy(pair: str, params): generalLogger.info('Starting strategy with ' + pair) tradingLogger.info('Starting strategy with ' + pair) - paramsDict = await jsonProcessing.parseParams(params) + paramsDict = jsonProcessing.parseParams(params) client = HTTP(testnet=True) @@ -309,9 +318,9 @@ async def strategy(pair: str, params): ) i = 0 - t = await jsonProcessing.checkPair(pair) + t = jsonProcessing.checkPair(pair) while t: - t = await jsonProcessing.checkPair(pair) + t = jsonProcessing.checkPair(pair) if t != 1: generalLogger.info(f"Closing websockets for {pair}") ws.exit() diff --git a/src/jsonProcessing.py b/src/jsonProcessing.py index 0fbafb4..4e90587 100644 --- a/src/jsonProcessing.py +++ b/src/jsonProcessing.py @@ -16,10 +16,12 @@ def startUp(): if not(os.path.exists('data')): os.mkdir('data') + if not(os.path.exists('data/backup')): + os.mkdir('data/backup') if os.path.exists(filePath): timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S') - backupPath = (f'data/data_backup_{timestamp}.json') + backupPath = (f'data/backup/data_backup_{timestamp}.json') shutil.copy(filePath, backupPath) generalLogger.info(f'JSON backup was created: {backupPath}') @@ -28,7 +30,7 @@ def startUp(): generalLogger.info(f'New {filePath} created with empty JSON.') -async def parseParams(params): +def parseParams(params): # Returnes dictionary of string params as paramsLines in options paramsList = params.split() paramsDict = {} @@ -39,7 +41,7 @@ async def parseParams(params): paramsDict[options.paramsLines[i]] = paramsList[i] return paramsDict -async def toDictPairParams(pair: str, params): +def toDictPairParams(pair: str, params): # Returnes dictionary as pair:internal(params) paramsList = params.split() @@ -52,7 +54,7 @@ async def toDictPairParams(pair: str, params): return paramsDict -async def checkPair(pair: str): +def checkPair(pair: str): # Returnes 1 if pair exists and 0 if not currentData = {} try: @@ -66,7 +68,7 @@ async def checkPair(pair: str): else: return 0 -async def deletePair(pair: str): +def deletePair(pair: str): # Returnes 0 if deleted successfully and -1 if not currentData = {} try: @@ -85,9 +87,9 @@ async def deletePair(pair: str): generalLogger.info(f'Pair {pair} was not found in the data file when trying to delete it.') return -1 -async def savePairParams(pair: str, params): +def savePairParams(pair: str, params): # Saves or updates data in JSON - newData = await toDictPairParams(pair, params) + newData = toDictPairParams(pair, params) if newData == -1: return -1 @@ -110,7 +112,7 @@ async def savePairParams(pair: str, params): return 0 -async def loadJson(): +def loadJson(): # Returnes the contents of the JSON file as a dictionary data = {} try: diff --git a/src/logger.py b/src/logger.py index 9985bcf..ec8c761 100644 --- a/src/logger.py +++ b/src/logger.py @@ -7,6 +7,7 @@ import options generalLogPath = "./data/generalLog.log" tradingLogPath = "./data/tradingLog.log" +debugLogPath = "./data/debugLog.log" def setupLogger(name, level, logPath, formatter): @@ -35,6 +36,10 @@ generalLogger = setupLogger('general', logging.INFO, generalLogPath, generalForm tradingFormatter = logging.Formatter('%(asctime)s - %(message)s') tradingLogger = setupLogger('trade', logging.NOTSET, tradingLogPath, tradingFormatter) +# Дебаг лог (содержимое ответов от API, всякие мелочи) +debugFormatter = logging.Formatter('%(asctime)s - %(message)s') +debugLogger = setupLogger('debug', logging.DEBUG, debugLogPath, debugFormatter) + # Максимально подробный дебаг лог, вызывает повторы в логировании. Его наличие настраивается if options.showExtraDebugLogs: logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') diff --git a/src/main.py b/src/main.py index fba6f09..2351ac6 100644 --- a/src/main.py +++ b/src/main.py @@ -16,6 +16,7 @@ from aiogram.fsm.storage.memory import MemoryStorage from logger import generalLogger from logger import tradingLogger +from logger import debugLogger import bybit import arbus @@ -75,7 +76,7 @@ async def commandStatus(message: Message) -> None: @dp.message(Command("info"), F.chat.id.in_(whitelist.chatIDs)) async def commandInfo(message: Message) -> None: - data = await jsonProcessing.loadJson() + data = jsonProcessing.loadJson() msgText = '' if data == {}: msgText = strings.noData @@ -98,7 +99,7 @@ async def captureStartPair(message: Message, state: FSMContext): data = await state.get_data() t = 0 - if await jsonProcessing.checkPair(data.get("pair")) == 1: + if jsonProcessing.checkPair(data.get("pair")) == 1: msgText = strings.strategyAlreadyRunning t = 1 else: @@ -115,9 +116,9 @@ async def captureParams(message: Message, state: FSMContext): await state.update_data(params=message.text) data = await state.get_data() - t = await jsonProcessing.savePairParams(pair=data.get("pair"), params=data.get("params")) + t = jsonProcessing.savePairParams(pair=data.get("pair"), params=data.get("params")) if t == 0: - params = await jsonProcessing.parseParams(params=data.get("params")) + params = jsonProcessing.parseParams(params=data.get("params")) client = await bybit.getClient( credentials.api_key, credentials.api_secret, @@ -127,7 +128,7 @@ async def captureParams(message: Message, state: FSMContext): if client == -1: msgText = strings.authFailed generalLogger.info("Auth failed. Strategy not started") - await jsonProcessing.deletePair(pair=data.get("pair")) + jsonProcessing.deletePair(pair=data.get("pair")) else: orderSize = float(params.get('orderSize')) minqty = bybit.getStartFilters(client, data.get("pair")) @@ -139,17 +140,17 @@ async def captureParams(message: Message, state: FSMContext): if qty <= minqty: generalLogger.info("Qty < minqty. Strategy not started") msgText = strings.orderSizeLowerThanQty - await jsonProcessing.deletePair(pair=data.get("pair")) + jsonProcessing.deletePair(pair=data.get("pair")) elif balance <= orderSize: generalLogger.info("Balance < order size. Strategy not started") msgText = strings.notEnoughBalance - await jsonProcessing.deletePair(pair=data.get("pair")) + 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")) + jsonProcessing.deletePair(pair=data.get("pair")) msgText = (strings.strategyError) elif t == -1: msgText = strings.wrongFormat @@ -171,7 +172,7 @@ async def captureStopPair(message: Message, state: FSMContext): await state.update_data(pair=message.text) data = await state.get_data() - t = await jsonProcessing.deletePair(data.get("pair")) + t = jsonProcessing.deletePair(data.get("pair")) if t == 0: msgText = strings.stopStrategy else: diff --git a/src/strings.py b/src/strings.py index 53bf52a..793eb96 100644 --- a/src/strings.py +++ b/src/strings.py @@ -37,7 +37,9 @@ helpCommand = (f"При старте стратегии требуется за "Количество уровней сетки\n" \ "Дельта для тейка\n" \ "Дельта для стопа\n" \ -"Размер позиции на каждом уровне") +"Размер позиции на каждом уровне\n" \ +"\n"\ +"Чтобы остановаить запуск стратегии просто при запросе параметров введите не 8 строк, можно одну любую букву.") strategyCommand = "Вы собираетесь запустить стратегию." diff --git a/todo2.md b/todo2.md new file mode 100644 index 0000000..341d76b --- /dev/null +++ b/todo2.md @@ -0,0 +1,10 @@ +Версия 1.x.x +### ToFix: + - [ ] Подсчёт P&L (активные ордера) + +### Новые функции: + - [ ] Запрос P&L за период + - [ ] Поддержка параллельного пользователя + - [ ] Мультиюзер версия + - [ ] Команда отмены + - [ ] Команда myid