Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 98d770efae | |||
| c1835ca4eb |
@ -25,7 +25,7 @@ def getPnL(pair):
|
|||||||
|
|
||||||
strategyStartTime = None
|
strategyStartTime = None
|
||||||
for timestamp, message in logEntries:
|
for timestamp, message in logEntries:
|
||||||
if message == f"Starting strategy with {pair}":
|
if message == (f"Starting strategy with {pair}"):
|
||||||
strategyStartTime = timestamp
|
strategyStartTime = timestamp
|
||||||
|
|
||||||
if not strategyStartTime:
|
if not strategyStartTime:
|
||||||
|
|||||||
27
src/bybit.py
27
src/bybit.py
@ -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):
|
||||||
@ -173,7 +180,9 @@ 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 = []
|
||||||
@ -210,11 +219,13 @@ class tradingData:
|
|||||||
j['long'] = False
|
j['long'] = False
|
||||||
j['longIDs'] = ['-1', '-1', '-1']
|
j['longIDs'] = ['-1', '-1', '-1']
|
||||||
generalLogger.info(f"Long order on {self.pair} level {j['price']} triggered TP/SL")
|
generalLogger.info(f"Long order on {self.pair} level {j['price']} triggered TP/SL")
|
||||||
|
tradingLogger.info(f"Long order on {self.pair} level {j['price']} triggered TP/SL")
|
||||||
self.orderCounter -= 1
|
self.orderCounter -= 1
|
||||||
if orderID in shortIDs:
|
if orderID in shortIDs:
|
||||||
j['short'] = False
|
j['short'] = False
|
||||||
j['shortIDs'] = ['-1', '-1', '-1']
|
j['shortIDs'] = ['-1', '-1', '-1']
|
||||||
generalLogger.info(f"Short order on {self.pair} level {j['price']} triggered TP/SL")
|
generalLogger.info(f"Short order on {self.pair} level {j['price']} triggered TP/SL")
|
||||||
|
tradingLogger.info(f"Short order on {self.pair} level {j['price']} triggered TP/SL")
|
||||||
self.orderCounter -= 1
|
self.orderCounter -= 1
|
||||||
if orderStatus == 'Filled':
|
if orderStatus == 'Filled':
|
||||||
for j in self.levels:
|
for j in self.levels:
|
||||||
@ -224,6 +235,7 @@ class tradingData:
|
|||||||
generalLogger.info(f"Long order on {self.pair} level {j['price']} filled with P&L {i['closedPnl']} and qty {i['qty']}")
|
generalLogger.info(f"Long order on {self.pair} level {j['price']} filled with P&L {i['closedPnl']} and qty {i['qty']}")
|
||||||
tradingLogger.info(f"Long order on {self.pair} level {j['price']} filled with P&L {i['closedPnl']} and qty {i['qty']}")
|
tradingLogger.info(f"Long order on {self.pair} level {j['price']} filled with P&L {i['closedPnl']} and qty {i['qty']}")
|
||||||
if orderID in shortIDs:
|
if orderID in shortIDs:
|
||||||
|
generalLogger.info(f"Short order on {self.pair} level {j['price']} filled with P&L {i['closedPnl']} and qty {i['qty']}")
|
||||||
tradingLogger.info(f"Short order on {self.pair} level {j['price']} filled with P&L {i['closedPnl']} and qty {i['qty']}")
|
tradingLogger.info(f"Short order on {self.pair} level {j['price']} filled with P&L {i['closedPnl']} and qty {i['qty']}")
|
||||||
|
|
||||||
|
|
||||||
@ -247,8 +259,9 @@ async def getClient(apiKey, apiSecret, testnet, demoTrading):
|
|||||||
|
|
||||||
async def strategy(pair: str, params):
|
async def strategy(pair: str, params):
|
||||||
generalLogger.info('Starting strategy with ' + pair)
|
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)
|
client = HTTP(testnet=True)
|
||||||
|
|
||||||
@ -305,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()
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
19
src/main.py
19
src/main.py
@ -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:
|
||||||
|
|||||||
@ -37,7 +37,9 @@ helpCommand = (f"При старте стратегии требуется за
|
|||||||
"Количество уровней сетки\n" \
|
"Количество уровней сетки\n" \
|
||||||
"Дельта для тейка\n" \
|
"Дельта для тейка\n" \
|
||||||
"Дельта для стопа\n" \
|
"Дельта для стопа\n" \
|
||||||
"Размер позиции на каждом уровне</b>")
|
"Размер позиции на каждом уровне</b>\n" \
|
||||||
|
"\n"\
|
||||||
|
"Чтобы остановаить запуск стратегии просто при запросе параметров введите не 8 строк, можно одну любую букву.")
|
||||||
|
|
||||||
strategyCommand = "Вы собираетесь запустить стратегию."
|
strategyCommand = "Вы собираетесь запустить стратегию."
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user