1 Commits
0.0.7 ... 0.0.8

Author SHA1 Message Date
98d770efae Auto strategy close fix
All checks were successful
Build and Push Docker Image / build-and-push (release) Successful in 1m27s
2025-06-03 17:41:28 +03:00
6 changed files with 54 additions and 25 deletions

View File

@ -12,6 +12,7 @@ import arbus
from logger import generalLogger from logger import generalLogger
from logger import tradingLogger from logger import tradingLogger
from logger import debugLogger
def getPrice(client, pair): def getPrice(client, pair):
@ -60,6 +61,7 @@ class tradingData:
self.priceDecimals, self.qtyDecimals, self.minimumQty = self.getFilters(pair) self.priceDecimals, self.qtyDecimals, self.minimumQty = self.getFilters(pair)
self.previousPrice = -1 self.previousPrice = -1
self.orderCounter = 0 self.orderCounter = 0
self.closed = 0
def getBalance(self, pair): def getBalance(self, pair):
@ -84,13 +86,18 @@ class tradingData:
return priceDecimals, qtyDecimals, minimumQty return priceDecimals, qtyDecimals, minimumQty
def close(self): def close(self):
jsonProcessing.deletePair(self.pair) self.closed = 1
t = jsonProcessing.deletePair(self.pair)
generalLogger.info(f"Closing strategy on {self.pair}") generalLogger.info(f"Closing strategy on {self.pair}")
def checkCloseConditions(self, markPrice): def checkCloseConditions(self, markPrice):
# If the price is outside of the (lowBreak; highBreak) interval then stop strategy # If the price is outside of the (lowBreak; highBreak) interval then stop strategy
if not (self.lowBreak < markPrice < self.highBreak): if self.closed == 0:
self.close() 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): def checkOrderConditions(self, markPrice):
@ -174,6 +181,8 @@ class tradingData:
def handlePositionInfo(self, message): def handlePositionInfo(self, message):
data = message.get('data') data = message.get('data')
debugLogger.debug(data)
# Usually the 3-order response means SL + market + TP orders were placed. # Usually the 3-order response means SL + market + TP orders were placed.
if len(data) == 3: if len(data) == 3:
orderType = [] orderType = []
@ -252,7 +261,7 @@ async def strategy(pair: str, params):
generalLogger.info('Starting strategy with ' + pair) generalLogger.info('Starting strategy with ' + pair)
tradingLogger.info('Starting strategy with ' + pair) tradingLogger.info('Starting strategy with ' + pair)
paramsDict = await jsonProcessing.parseParams(params) paramsDict = jsonProcessing.parseParams(params)
client = HTTP(testnet=True) client = HTTP(testnet=True)
@ -309,9 +318,9 @@ async def strategy(pair: str, params):
) )
i = 0 i = 0
t = await jsonProcessing.checkPair(pair) t = jsonProcessing.checkPair(pair)
while t: while t:
t = await jsonProcessing.checkPair(pair) t = jsonProcessing.checkPair(pair)
if t != 1: if t != 1:
generalLogger.info(f"Closing websockets for {pair}") generalLogger.info(f"Closing websockets for {pair}")
ws.exit() ws.exit()

View File

@ -16,10 +16,12 @@ def startUp():
if not(os.path.exists('data')): if not(os.path.exists('data')):
os.mkdir('data') os.mkdir('data')
if not(os.path.exists('data/backup')):
os.mkdir('data/backup')
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/data_backup_{timestamp}.json') backupPath = (f'data/backup/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}')
@ -28,7 +30,7 @@ def startUp():
generalLogger.info(f'New {filePath} created with empty JSON.') 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 # Returnes dictionary of string params as paramsLines in options
paramsList = params.split() paramsList = params.split()
paramsDict = {} paramsDict = {}
@ -39,7 +41,7 @@ async def parseParams(params):
paramsDict[options.paramsLines[i]] = paramsList[i] paramsDict[options.paramsLines[i]] = paramsList[i]
return paramsDict return paramsDict
async def toDictPairParams(pair: str, params): def toDictPairParams(pair: str, params):
# Returnes dictionary as pair:internal(params) # Returnes dictionary as pair:internal(params)
paramsList = params.split() paramsList = params.split()
@ -52,7 +54,7 @@ async def toDictPairParams(pair: str, params):
return paramsDict return paramsDict
async def checkPair(pair: str): def checkPair(pair: str):
# Returnes 1 if pair exists and 0 if not # Returnes 1 if pair exists and 0 if not
currentData = {} currentData = {}
try: try:
@ -66,7 +68,7 @@ async def checkPair(pair: str):
else: else:
return 0 return 0
async def deletePair(pair: str): def deletePair(pair: str):
# Returnes 0 if deleted successfully and -1 if not # Returnes 0 if deleted successfully and -1 if not
currentData = {} currentData = {}
try: 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.') generalLogger.info(f'Pair {pair} was not found in the data file when trying to delete it.')
return -1 return -1
async def savePairParams(pair: str, params): def savePairParams(pair: str, params):
# Saves or updates data in JSON # Saves or updates data in JSON
newData = await toDictPairParams(pair, params) newData = toDictPairParams(pair, params)
if newData == -1: if newData == -1:
return -1 return -1
@ -110,7 +112,7 @@ async def savePairParams(pair: str, params):
return 0 return 0
async def loadJson(): def loadJson():
# Returnes the contents of the JSON file as a dictionary # Returnes the contents of the JSON file as a dictionary
data = {} data = {}
try: try:

View File

@ -7,6 +7,7 @@ import options
generalLogPath = "./data/generalLog.log" generalLogPath = "./data/generalLog.log"
tradingLogPath = "./data/tradingLog.log" tradingLogPath = "./data/tradingLog.log"
debugLogPath = "./data/debugLog.log"
def setupLogger(name, level, logPath, formatter): def setupLogger(name, level, logPath, formatter):
@ -35,6 +36,10 @@ 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)
# Дебаг лог (содержимое ответов от API, всякие мелочи)
debugFormatter = logging.Formatter('%(asctime)s - %(message)s')
debugLogger = setupLogger('debug', logging.DEBUG, debugLogPath, debugFormatter)
# Максимально подробный дебаг лог, вызывает повторы в логировании. Его наличие настраивается # Максимально подробный дебаг лог, вызывает повторы в логировании. Его наличие настраивается
if options.showExtraDebugLogs: if options.showExtraDebugLogs:
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

View File

@ -16,6 +16,7 @@ from aiogram.fsm.storage.memory import MemoryStorage
from logger import generalLogger from logger import generalLogger
from logger import tradingLogger from logger import tradingLogger
from logger import debugLogger
import bybit import bybit
import arbus import arbus
@ -75,7 +76,7 @@ async def commandStatus(message: Message) -> None:
@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 = jsonProcessing.loadJson()
msgText = '' msgText = ''
if data == {}: if data == {}:
msgText = strings.noData msgText = strings.noData
@ -98,7 +99,7 @@ async def captureStartPair(message: Message, state: FSMContext):
data = await state.get_data() data = await state.get_data()
t = 0 t = 0
if await jsonProcessing.checkPair(data.get("pair")) == 1: if jsonProcessing.checkPair(data.get("pair")) == 1:
msgText = strings.strategyAlreadyRunning msgText = strings.strategyAlreadyRunning
t = 1 t = 1
else: else:
@ -115,9 +116,9 @@ async def captureParams(message: Message, state: FSMContext):
await state.update_data(params=message.text) await state.update_data(params=message.text)
data = await state.get_data() 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: if t == 0:
params = await jsonProcessing.parseParams(params=data.get("params")) params = jsonProcessing.parseParams(params=data.get("params"))
client = await bybit.getClient( client = await bybit.getClient(
credentials.api_key, credentials.api_key,
credentials.api_secret, credentials.api_secret,
@ -127,7 +128,7 @@ async def captureParams(message: Message, state: FSMContext):
if client == -1: if client == -1:
msgText = strings.authFailed msgText = strings.authFailed
generalLogger.info("Auth failed. Strategy not started") generalLogger.info("Auth failed. Strategy not started")
await jsonProcessing.deletePair(pair=data.get("pair")) jsonProcessing.deletePair(pair=data.get("pair"))
else: else:
orderSize = float(params.get('orderSize')) orderSize = float(params.get('orderSize'))
minqty = bybit.getStartFilters(client, data.get("pair")) minqty = bybit.getStartFilters(client, data.get("pair"))
@ -139,17 +140,17 @@ async def captureParams(message: Message, state: FSMContext):
if qty <= minqty: if qty <= minqty:
generalLogger.info("Qty < minqty. Strategy not started") generalLogger.info("Qty < minqty. Strategy not started")
msgText = strings.orderSizeLowerThanQty msgText = strings.orderSizeLowerThanQty
await jsonProcessing.deletePair(pair=data.get("pair")) jsonProcessing.deletePair(pair=data.get("pair"))
elif balance <= orderSize: elif balance <= orderSize:
generalLogger.info("Balance < order size. Strategy not started") generalLogger.info("Balance < order size. Strategy not started")
msgText = strings.notEnoughBalance msgText = strings.notEnoughBalance
await jsonProcessing.deletePair(pair=data.get("pair")) jsonProcessing.deletePair(pair=data.get("pair"))
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")) jsonProcessing.deletePair(pair=data.get("pair"))
msgText = (strings.strategyError) msgText = (strings.strategyError)
elif t == -1: elif t == -1:
msgText = strings.wrongFormat msgText = strings.wrongFormat
@ -171,7 +172,7 @@ async def captureStopPair(message: Message, state: FSMContext):
await state.update_data(pair=message.text) await state.update_data(pair=message.text)
data = await state.get_data() data = await state.get_data()
t = await jsonProcessing.deletePair(data.get("pair")) t = jsonProcessing.deletePair(data.get("pair"))
if t == 0: if t == 0:
msgText = strings.stopStrategy msgText = strings.stopStrategy
else: else:

View File

@ -37,7 +37,9 @@ helpCommand = (f"При старте стратегии требуется за
"Количество уровней сетки\n" \ "Количество уровней сетки\n" \
"Дельта для тейка\n" \ "Дельта для тейка\n" \
"Дельта для стопа\n" \ "Дельта для стопа\n" \
"Размер позиции на каждом уровне</b>") "Размер позиции на каждом уровне</b>\n" \
"\n"\
"Чтобы остановаить запуск стратегии просто при запросе параметров введите не 8 строк, можно одну любую букву.")
strategyCommand = "Вы собираетесь запустить стратегию." strategyCommand = "Вы собираетесь запустить стратегию."

10
todo2.md Normal file
View File

@ -0,0 +1,10 @@
Версия 1.x.x
### ToFix:
- [ ] Подсчёт P&L (активные ордера)
### Новые функции:
- [ ] Запрос P&L за период
- [ ] Поддержка параллельного пользователя
- [ ] Мультиюзер версия
- [ ] Команда отмены
- [ ] Команда myid