1 Commits
0.0.3 ... 0.0.4

Author SHA1 Message Date
808f7112f7 Added status and PnL
All checks were successful
Build and Push Docker Image / build-and-push (release) Successful in 1m13s
2025-05-19 15:45:30 +03:00
7 changed files with 91 additions and 21 deletions

View File

@ -1,8 +1,52 @@
import options import time
from datetime import datetime
import re
from random import randint 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 # Returns array of prices from low to high for each level
levels = [] levels = []
delta = (highPrice - lowPrice)/amount delta = (highPrice - lowPrice)/amount

View File

@ -126,6 +126,7 @@ class tradingData:
tradingLogger.info(f"Placed oder on {self.pair} with TP {tp}; SL {sl}") tradingLogger.info(f"Placed oder on {self.pair} with TP {tp}; SL {sl}")
else: else:
generalLogger.warning(f"Failed to place order on {self.pair}; qty is too small!") 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 return orderID
@ -233,8 +234,9 @@ async def strategy(pair: str, params):
priceDecimals = int(infoContents.get('priceScale')) 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. # Levels have 3 IDs for both long and short position
levelsRaw = await arbus.getLevels(levelsAmount, highEnd, lowEnd, priceDecimals) # The first is main order (market opening), second is SL and third is TP
levelsRaw = arbus.getLevels(levelsAmount, highEnd, lowEnd, priceDecimals)
levels = [] levels = []
for i in range(levelsAmount): for i in range(levelsAmount):
levels.append({'price':levelsRaw[i], 'long':False, 'longIDs':['-1', '-1', '-1'], 'short':False, 'shortIDs':['-1', '-1', '-1']}) 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: while t:
t = await jsonProcessing.checkPair(pair) t = await jsonProcessing.checkPair(pair)
if t != 1: if t != 1:
generalLogger.info("Closing websockets for {pair}") generalLogger.info(f"Closing websockets for {pair}")
ws.exit() ws.exit()
wsPrivate.exit() wsPrivate.exit()
break break
@ -285,4 +287,5 @@ async def strategy(pair: str, params):
i += 1 i += 1
generalLogger.info(f"Ending strategy with {pair}; Ended on the iteration number {i}") generalLogger.info(f"Ending strategy with {pair}; Ended on the iteration number {i}")
tradingLogger.info(f"Ending strategy with {pair}")
return i return i

View File

@ -12,7 +12,7 @@ def startUp():
filePath = 'data.json' filePath = 'data.json'
if os.path.exists(filePath): 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') backupPath = (f'data_backup_{timestamp}.json')
shutil.copy(filePath, backupPath) shutil.copy(filePath, backupPath)
generalLogger.info(f'JSON backup was created: {backupPath}') generalLogger.info(f'JSON backup was created: {backupPath}')

View File

@ -31,7 +31,7 @@ generalLogger = setupLogger('general', logging.INFO, generalLogPath, generalForm
tradingFormatter = logging.Formatter('%(asctime)s - %(message)s') tradingFormatter = logging.Formatter('%(asctime)s - %(message)s')
tradingLogger = setupLogger('trade', logging.NOTSET, tradingLogPath, tradingFormatter) tradingLogger = setupLogger('trade', logging.NOTSET, tradingLogPath, tradingFormatter)
# Максимально подробный общий лог # Максимально подробный общий лог, вызывает повторы в логировании. Его наличие настраивается
if options.showExtraDebugLogs: if options.showExtraDebugLogs:
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
superGeneralLogger = logging.getLogger('superGeneral') superGeneralLogger = logging.getLogger('superGeneral')

View File

@ -1,3 +1,5 @@
import time
from datetime import datetime
import asyncio import asyncio
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
@ -16,6 +18,7 @@ from logger import generalLogger
from logger import tradingLogger from logger import tradingLogger
import bybit import bybit
import arbus
import jsonProcessing import jsonProcessing
import credentials import credentials
@ -54,25 +57,32 @@ stop_router = Router()
@dp.message(Command("start")) @dp.message(Command("start"))
async def commandStart(message: Message) -> None: 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) await message.answer(strings.startCommand)
@dp.message(Command("help"), F.chat.id.in_(whitelist.chatIDs)) @dp.message(Command("help"), F.chat.id.in_(whitelist.chatIDs))
async def commandHelp(message: Message) -> None: async def commandHelp(message: Message) -> None:
await message.answer(strings.helpCommand) 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)) @dp.message(Command("info"), F.chat.id.in_(whitelist.chatIDs))
async def commandInfo(message: Message) -> None: async def commandInfo(message: Message) -> None:
data = await jsonProcessing.loadJson() data = await jsonProcessing.loadJson()
msgText = strings.foundData msgText = ''
if data == {}: if data == {}:
msgText = strings.noData msgText = strings.noData
else: else:
msgText = strings.foundData msgText = strings.foundData
for i in data: for i in data:
msgText += (f"<b>{str(i)}</b>: P&L - x%\n") pnl = arbus.getPnL(str(i))
msgText += (f"<b>{str(i)}</b>: P&L - {pnl}%\n")
await message.answer(msgText) await message.answer(msgText)
@ -88,7 +98,7 @@ async def capture_start_pair(message: Message, state: FSMContext):
t = 0 t = 0
if await jsonProcessing.checkPair(data.get("pair")) == 1: if await jsonProcessing.checkPair(data.get("pair")) == 1:
msgText = (f'Стратегия на паре <b>{data.get("pair")}</b> уже запущена.\nПожалуйста остановите стратегию либо введите другую пару.') msgText = strings.strategyAlreadyRunning
t = 1 t = 1
else: else:
msgText = strings.askParams msgText = strings.askParams
@ -118,16 +128,17 @@ async def capture_params(message: Message, state: FSMContext):
else: else:
try: try:
asyncio.create_task(bybit.strategy(data.get("pair"), data.get("params"))) asyncio.create_task(bybit.strategy(data.get("pair"), data.get("params")))
msgText = (f'Вы запустили стратегию на паре <b>{data.get("pair")}</b> с данными параметрами:\n<b>{data.get("params")}</b>\n') msgText = (f'Вы запустили стратегию на паре <b>{data.get("pair")}</b> \
с данными параметрами:\n<b>{data.get("params")}</b>\n')
except: except:
await jsonProcessing.deletePair(pair=data.get("pair")) await jsonProcessing.deletePair(pair=data.get("pair"))
msgText = (f'Возникла ошибка в работе стратегии =( Пожалуйста сообщите об этом администратору.') msgText = (strings.strategyError)
elif t == -1: elif t == -1:
msgText = (f'Параметры введены в неверном формате, пожалуйста начните заново.') msgText = strings.wrongFormat
elif t == -2: elif t == -2:
msgText = (f'Стратегия на паре <b>{data.get("pair")}</b> уже запущена.') msgText = (f"Стратегия на паре <b>{data.get("pair")}</b> уже запущена.")
else: else:
msgText = (f'Возникла непредвиденная ошибка. =(') msgText = strings.unexpectedError
await message.answer(msgText) await message.answer(msgText)
await state.clear() await state.clear()
@ -165,7 +176,9 @@ async def main() -> None:
# Main # Main
if __name__ == "__main__": if __name__ == "__main__":
generalLogger.info("Started bot!") arbus.setStartTime()
tradingLogger.info("Started bot!")
jsonProcessing.startUp() jsonProcessing.startUp()
currentTime = datetime.now().strftime("%H:%M:%S")
generalLogger.info(f"Started bot!")
tradingLogger.info(f"Started bot!")
asyncio.run(main()) asyncio.run(main())

View File

@ -8,6 +8,10 @@ startStrategy = "Стратегия запущена!"
stopStrategy = "Стратегия остановлена!" stopStrategy = "Стратегия остановлена!"
strategyAlreadyRunning = "Стратегия на данной паре уже запущена.\nПожалуйста остановите стратегию либо введите другую пару."
strategyError = "Возникла ошибка в работе стратегии =( Пожалуйста сообщите об этом администратору."
# Bot Statuses # Bot Statuses
@ -15,6 +19,9 @@ startBot = "Бот запущен!"
stopBot = "Бот остановлен!" stopBot = "Бот остановлен!"
status = "Бот работает в течение "
unexpectedError = "Возникла непредвиденная ошибка. =("
# Commands # Commands
@ -45,7 +52,9 @@ gotParams = "Параметры заданы!"
pairNotFound = "Стратегия на данную монетную пару не найдена." pairNotFound = "Стратегия на данную монетную пару не найдена."
authFailed = (f'Аутентификация не удалась, пожалуйста сообщите администратору если увидете данное сообщение.') authFailed = (f"Аутентификация не удалась, пожалуйста сообщите администратору если увидете данное сообщение.")
wrongFormat = "Параметры введены в неверном формате, пожалуйста начните заново."
# Data status # Data status

View File

@ -21,6 +21,7 @@
- - - [x] Strategy (Запуск стратегии) - - - [x] Strategy (Запуск стратегии)
- - - [x] Stop (Остановка стратегии) - - - [x] Stop (Остановка стратегии)
- - - [x] Info (Информация о запущеных стратегиях) - - - [x] Info (Информация о запущеных стратегиях)
- - - [ ] Проверка на баланс и минимальное количество
- - [x] Обеспечение безопасности и приватности бота (через chat id или пароль) - - [x] Обеспечение безопасности и приватности бота (через chat id или пароль)
- [x] Реализация стратегии - [x] Реализация стратегии
- - [x] Основная функция для запуска стратегии - - [x] Основная функция для запуска стратегии