From de13a172e95f3e0776ab16b2b60f95ba4788e8fc Mon Sep 17 00:00:00 2001 From: Beesquit Date: Thu, 24 Apr 2025 21:19:59 +0300 Subject: [PATCH] Almost telegram integration + Bybit started --- .gitignore | 2 + src/arbus.py | 78 ++++++++++++++++++++++++ src/bybit.py | 75 +++++++++++++++++++++++ src/main.py | 159 +++++++++++++++++++++++++++++-------------------- src/options.py | 12 ++++ src/strings.py | 13 ++-- 6 files changed, 271 insertions(+), 68 deletions(-) create mode 100644 src/arbus.py create mode 100644 src/bybit.py diff --git a/.gitignore b/.gitignore index bf9387f..e4c5ae6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ src/__pycache__ src/test.py src/credentials.py +exampleData.py +data.json diff --git a/src/arbus.py b/src/arbus.py new file mode 100644 index 0000000..eaa16bd --- /dev/null +++ b/src/arbus.py @@ -0,0 +1,78 @@ +import json + +import options + + +async def parseParams(params): + paramsList = params.split() + paramsDict = {} + for i in range(len(options.paramsLines)): + paramsDict[options.paramsLines[i]] = paramsList[i] + return paramsDict + +async def toDictPairParams(pair: str, params): + paramsList = params.split() + + if len(paramsList) != len(options.paramsLines): + return -1 + + paramsDict = {pair: {}} + for i in range(len(options.paramsLines)): + paramsDict[pair][options.paramsLines[i]] = paramsList[i] + return paramsDict + +async def checkPair(pair: str): + currentData = {} + try: + with open('data.json', 'r') as f: + currentData = json.load(f) + except json.decoder.JSONDecodeError as e: + print('WARNING: JSON file is empty! Ignore if your installation is fresh.') + + if pair in currentData: + print(pair, ' exists in data file.') + return 1 + else: + print(pair, ' not found in data file.') + return 0 + +async def deletePair(pair: str): + currentData = {} + try: + with open('data.json', 'r') as f: + currentData = json.load(f) + except json.decoder.JSONDecodeError as e: + print('WARNING: JSON file is empty! Ignore if your installation is fresh.') + + if pair in currentData: + print(pair, ' exists in data file.') + del currentData[pair] + with open('data.json', 'w', encoding = 'utf-8') as f: + json.dump(currentData, f, ensure_ascii = False, indent = 4) + return 0 + else: + print(pair, ' not found in data file.') + return -1 + +async def mainWrapper(pair: str, params): + newData = await toDictPairParams(pair, params) + + if newData == -1: + return -1 + + currentData = {} + try: + with open('data.json', 'r') as f: + currentData = json.load(f) + except json.decoder.JSONDecodeError as e: + print('WARNING: JSON file is empty! Ignore if your installation is fresh.') + + if pair in currentData: + print(pair, ' already exists.') + return -2 + else: + with open('data.json', 'w', encoding = 'utf-8') as f: + currentData.update(newData) + json.dump(currentData, f, ensure_ascii = False, indent = 4) + print(pair, ' was added!') + return 0 diff --git a/src/bybit.py b/src/bybit.py new file mode 100644 index 0000000..9494ea1 --- /dev/null +++ b/src/bybit.py @@ -0,0 +1,75 @@ +import time +import asyncio + +from pybit.unified_trading import HTTP + +import options +import credentials + +import arbus + + +async def getClient(apiKey, apiSecret, testnet): + if testnet: + print('Using testnet API.') + else: + print('Using real API.') + + client = HTTP( + testnet = testnet, + api_key = apiKey, + api_secret = apiSecret, + ) + try: + response = client.get_account_info() + print('Auth succesful!') + print('Account info:', response.get('retMsg')) + return client + except Exception as e: + print('Auth failed! Check API key!') + print('Error:', e) + return -1 + + +async def strategy(client: HTTP, pair: str, params): + startTime = time.time() + print('Starting strategy with ', pair) + paramsDict = await arbus.parseParams(params) + + i = 0 + t = await arbus.checkPair(pair) + while t: + t = await arbus.checkPair(pair) + if t != 1: + break + + # client = getClient(credentials.api_key, credentials.api_secret, options.testnet) + + r1 = client.get_order_history( + category=options.category, + symbol=pair + ) + print(r1, '\n') + + r2 = client.place_order( + category = options.category, + symbol = pair, + side = 'BUY', + orderType = 'Market', + qty = paramsDict['orderSize'], + marketUnit = 'quoteCoin' + ) + + + print(r2, '\n') + if r2['retMsg'] == 'OK': + print('Order placed succesfully!') + + + + await asyncio.sleep(20) + i += 1 + + print('Ending strategy with ', pair) + print('Ended on the iteration number ', i) + return i diff --git a/src/main.py b/src/main.py index 2a6fefa..d819fbc 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,3 @@ -import time import asyncio from aiogram import Bot, Dispatcher @@ -6,126 +5,158 @@ from aiogram.filters import Command from aiogram.types import Message from aiogram.client.default import DefaultBotProperties from aiogram.enums import ParseMode +from aiogram.types import BotCommand, BotCommandScopeDefault from aiogram.fsm.context import FSMContext from aiogram import Router, F from aiogram.fsm.state import State, StatesGroup from aiogram.fsm.storage.memory import MemoryStorage -from aiogram.utils.chat_action import ChatActionSender + +import bybit +import arbus import credentials -import options import strings +import options -# Custom types and vars - -paramsLines = ['highEnd', - 'lowEnd', - 'highBreak', - 'lowBreak', - 'netLevelsAmount', - 'netStep', - 'takeDelta', - 'stopDelta', - 'orderSize' - ] - - -# Custom - -async def parseParams(params: str): - paramsList = params.split() - paramsDict = {} - for i in range(len(paramsLines)): - paramsDict(paramsLines[i]) = paramsList[i] - return paramsDict - - -# Bybit - -async def strategy(pair, params): - print(params) - await time.sleep(5) - print('Test succes') - result = pair + '\n' + params - return result - - -# Telegram - # Входные поля для трейдинг-бота: # Верхняя граница ордеров # Нижняя граница ордеров # Верхняя граница для брейка # Нижняя граница для брейка # Количество уровней сетки -# Шаг сетки # Дельта для тейка # Дельта для стопа # Размер позиции на каждом уровне -class Form(StatesGroup): + +async def set_commands(): + commands = [BotCommand(command='start', description='Старт'), + BotCommand(command='help', description='Инструкция'), + BotCommand(command='strategy', description='Запустить стратегию'), + BotCommand(command='stop', description='Остановить стратегию') + ] + await bot.set_my_commands(commands, BotCommandScopeDefault()) + + +class startForm(StatesGroup): pair = State() params = State() +class stopForm(StatesGroup): + pair = State() + dp = Dispatcher(storage=MemoryStorage()) bot = Bot( token=credentials.bot_token, default=DefaultBotProperties(parse_mode=ParseMode.HTML), ) + strategy_router = Router() +stop_router = Router() @dp.message(Command("start")) async def commandStart(message: Message) -> None: + print("Called function commandStart") await message.answer(strings.startCommand) @dp.message(Command("help")) async def commandHelp(message: Message) -> None: + print("Called function commandHelp") await message.answer(strings.helpCommand) @strategy_router.message(Command("strategy")) async def commandStrategy(message: Message, state: FSMContext): - print("F command strategy + capture pair") - async with ChatActionSender.typing(bot=bot, chat_id=message.chat.id): - await asyncio.sleep(1) - await message.answer(strings.strategyCommand + '\n' + strings.askPair) - await state.set_state(Form.pair) + print("Called function commandStrategy") + await message.answer(strings.strategyCommand + '\n' + strings.askPair) + await state.set_state(startForm.pair) -@strategy_router.message(F.text, Form.pair) -async def capture_pair(message: Message, state: FSMContext): - print("F capture pair + capture params") +@strategy_router.message(F.text, startForm.pair) +async def capture_start_pair(message: Message, state: FSMContext): + print("Called function capture_start_pair") await state.update_data(pair=message.text) - async with ChatActionSender.typing(bot=bot, chat_id=message.chat.id): - await asyncio.sleep(1) - await message.answer(strings.askParams) - await state.set_state(Form.params) + data = await state.get_data() -@strategy_router.message(F.text, Form.params) + t = 0 + if await arbus.checkPair(data.get("pair")) == 1: + msg_text = (f'Стратегия на паре {data.get("pair")} уже запущена.\nПожалуйста остановите стратегию либо введите другую пару.') + t = 1 + else: + msg_text = strings.askParams + + await message.answer(msg_text) + if t == 1: + print('Clearing state!') + await state.clear() + else: + await state.set_state(startForm.params) + +@strategy_router.message(F.text, startForm.params) async def capture_params(message: Message, state: FSMContext): - print("F capture params + end") + print("Called function capture_params") await state.update_data(params=message.text) data = await state.get_data() - t = await strategy(pair=data.get("pair"), params=data.get("params")) - msg_text = (f'Вы запускаете стратегию на паре {data.get("pair")} с данными параметрами\n{data.get("params")}\n') + t = await arbus.mainWrapper(pair=data.get("pair"), params=data.get("params")) + if t == 0: + client = await bybit.getClient(credentials.api_key, credentials.api_secret, options.testnet) + if client == -1: + msg_text = (f'Аутентификация не удалась, сообщите администратору если увидете данное сообщение.') + else: + asyncio.create_task(bybit.strategy(client, data.get("pair"), data.get("params"))) + msg_text = (f'Вы запустили стратегию на паре {data.get("pair")} с данными параметрами:\n{data.get("params")}\n') + elif t == -1: + msg_text = (f'Параметры введены в неверном формате, пожалуйста начните заново.') + elif t == -2: + msg_text = (f'Стратегия на паре {data.get("pair")} уже запущена.') + else: + msg_text = (f'Возникла непредвиденная ошибка. =(') await message.answer(msg_text) await state.clear() -@dp.message(Command("stop")) -async def commandStop(message: Message) -> None: - await message.answer(strings.stopCommand + '\n' + strings.stopStrategy) + +@stop_router.message(Command("stop")) +async def commandStop(message: Message, state: FSMContext): + print("Called function commandStop") + await message.answer(strings.stopCommand + '\n' + strings.askPair) + await state.set_state(stopForm.pair) + +@stop_router.message(F.text, stopForm.pair) +async def capture_stop_pair(message: Message, state: FSMContext): + print("Called function capture_stop_pair") + await state.update_data(pair=message.text) + data = await state.get_data() + + if await arbus.checkPair(data.get("pair")) == 1: + t = await arbus.deletePair(data.get("pair")) + if t == 0: + print('Deleted pair succesfuly') + else: + print('Error with deleting pair') + msg_text = strings.stopStrategy + else: + msg_text = strings.pairNotFound + + await message.answer(msg_text) + await state.clear() + + +async def start_bot(): + await set_commands() + +async def main() -> None: + dp.include_router(strategy_router) + dp.include_router(stop_router) + dp.startup.register(start_bot) + await dp.start_polling(bot) # Main -async def main() -> None: - dp.include_router(strategy_router) - await dp.start_polling(bot) - if __name__ == "__main__": print('Started bot!') asyncio.run(main()) diff --git a/src/options.py b/src/options.py index ca359a8..525dbed 100644 --- a/src/options.py +++ b/src/options.py @@ -4,8 +4,20 @@ testnet = True # Use testnet or not pairSymbol = 'ETHUSDT' # Trading pair mainSymbol = 'USDT' # Balance asset timeScape = '15m' # Candle length +category = 'spot' leverage = 1 # Leverage notification = 1 # Telegram notifications loopSleepTime = 2 # Time passing between loops/checks + +paramsLines = ['highEnd', + 'lowEnd', + 'highBreak', + 'lowBreak', + 'netLevelsAmount', + 'takeDelta', + 'stopDelta', + 'orderSize' + ] + diff --git a/src/strings.py b/src/strings.py index 952f506..ea6bd20 100644 --- a/src/strings.py +++ b/src/strings.py @@ -1,3 +1,7 @@ +from aiogram.types import Message +from aiogram.client.default import DefaultBotProperties +from aiogram.enums import ParseMode + # Strategy startStrategy = "Стратегия запущена!" @@ -16,18 +20,17 @@ stopBot = "Бот остановлен!" startCommand = "Привет! Это приватный бот для полуавтоматической торговли криптовалютой. Хороших позиций!" -stopCommand = "Стратегия остановлена!" +stopCommand = "Вы собираетесь остановить стратегию." -helpCommand = "При старте стратегии требуется задать параметры в следующем формате:\n" \ +helpCommand = (f"При старте стратегии требуется задать параметры в следующем формате:\n" \ "Верхняя граница ордеров\n" \ "Нижняя граница ордеров\n" \ "Верхняя граница для брейка\n" \ "Нижняя граница для брейка\n" \ "Количество уровней сетки\n" \ -"Шаг сетки\n" \ "Дельта для тейка\n" \ "Дельта для стопа\n" \ -"Размер позиции на каждом уровне" +"Размер позиции на каждом уровне") strategyCommand = "Вы собираетесь запустить стратегию." @@ -39,3 +42,5 @@ askPair = "Введите монетную пару:" askParams = "Введите параметры:" gotParams = "Параметры заданы!" + +pairNotFound = "Стратегия на данную монетную пару не найдена."