Added status and PnL
All checks were successful
Build and Push Docker Image / build-and-push (release) Successful in 1m13s
All checks were successful
Build and Push Docker Image / build-and-push (release) Successful in 1m13s
This commit is contained in:
48
src/arbus.py
48
src/arbus.py
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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}')
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
39
src/main.py
39
src/main.py
@ -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())
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
1
todo.md
1
todo.md
@ -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] Основная функция для запуска стратегии
|
||||||
|
|||||||
Reference in New Issue
Block a user