documentation WIP
This commit is contained in:
318
src/bybit.py
318
src/bybit.py
@ -1,4 +1,5 @@
|
||||
import time
|
||||
import traceback
|
||||
import asyncio
|
||||
|
||||
from pybit.unified_trading import HTTP
|
||||
@ -8,74 +9,268 @@ import options
|
||||
import credentials
|
||||
|
||||
import jsonProcessing
|
||||
import arbus
|
||||
|
||||
from logger import generalLogger
|
||||
from logger import tradingLogger
|
||||
|
||||
|
||||
async def getClient(apiKey, apiSecret, testnet):
|
||||
if testnet:
|
||||
print('Using testnet API.')
|
||||
else:
|
||||
print('Using real API.')
|
||||
class tradingData:
|
||||
def __init__(self, pair, levels, highBreak, lowBreak, takeDelta, stopDelta, orderSize):
|
||||
self.client = HTTP(
|
||||
testnet = options.testnet,
|
||||
demo = options.demoTrading,
|
||||
api_key = credentials.api_key,
|
||||
api_secret = credentials.api_secret
|
||||
)
|
||||
self.pair = pair
|
||||
self.balance = self.getBalance(pair)
|
||||
self.levels = levels
|
||||
self.highBreak = highBreak
|
||||
self.lowBreak = lowBreak
|
||||
self.takeDelta = takeDelta
|
||||
self.stopDelta = stopDelta
|
||||
self.orderSize = orderSize
|
||||
self.priceDecimals, self.qtyDecimals, self.minimumQty = self.getFilters(pair)
|
||||
self.previousPrice = -1
|
||||
self.counter = 0
|
||||
|
||||
|
||||
def getBalance(self, pair):
|
||||
coin = pair[:-4]
|
||||
response = self.client.get_wallet_balance(
|
||||
accountType = 'UNIFIED',
|
||||
coin = coin
|
||||
)
|
||||
balance = float(response['result']['list'][0]['totalAvailableBalance'])
|
||||
return balance
|
||||
|
||||
def getFilters(self, pair):
|
||||
instrumentInfo = self.client.get_instruments_info(
|
||||
symbol = pair,
|
||||
category = "linear"
|
||||
)
|
||||
infoContents = instrumentInfo.get('result').get('list')[0]
|
||||
|
||||
minimumQty = float(infoContents.get('lotSizeFilter').get('minOrderQty'))
|
||||
qtyDecimals = arbus.countDecimals(minimumQty)
|
||||
priceDecimals = int(infoContents.get('priceScale'))
|
||||
return priceDecimals, qtyDecimals, minimumQty
|
||||
|
||||
def close(self):
|
||||
jsonProcessing.deletePair(self.pair)
|
||||
generalLogger.info(f"Closing strategy on {self.pair}")
|
||||
|
||||
def checkCloseConditions(self, markPrice):
|
||||
# If the price is outside of the (lowBreak; highBreak) interval then stop strategy
|
||||
if not (self.lowBreak < markPrice < self.highBreak):
|
||||
self.close()
|
||||
|
||||
|
||||
def checkOrderConditions(self, markPrice):
|
||||
for i in self.levels:
|
||||
levelPrice = i.get('price')
|
||||
|
||||
# If level gets crossed from below or from above then go on
|
||||
# Can be split for different sides (long or short)
|
||||
if self.previousPrice <= levelPrice <= markPrice or \
|
||||
self.previousPrice >= levelPrice >= markPrice:
|
||||
if i.get('long') == False:
|
||||
id = self.placeCombiOrder(
|
||||
True,
|
||||
markPrice,
|
||||
'Buy',
|
||||
levelPrice+self.takeDelta,
|
||||
levelPrice-self.stopDelta,
|
||||
)
|
||||
i['long'] = True
|
||||
i['longIDs'][0] = id
|
||||
if i.get('short') == False:
|
||||
id = self.placeCombiOrder(
|
||||
True,
|
||||
markPrice,
|
||||
'Sell',
|
||||
levelPrice-self.takeDelta,
|
||||
levelPrice+self.stopDelta,
|
||||
)
|
||||
i['short'] = True
|
||||
i['shortIDs'][0] = id
|
||||
|
||||
def placeCombiOrder(self, useQuoteSymbol, markPrice, side, tp, sl):
|
||||
positionIdx = 1
|
||||
if side == 'Buy':
|
||||
positionIdx = 1
|
||||
if side == 'Sell':
|
||||
positionIdx = 2
|
||||
|
||||
qty = self.orderSize
|
||||
if useQuoteSymbol:
|
||||
qty = arbus.floor(self.orderSize/markPrice, self.qtyDecimals)
|
||||
|
||||
orderID = '-1'
|
||||
if qty >= self.minimumQty and qty < self.balance:
|
||||
response = self.client.place_order(
|
||||
category = "linear",
|
||||
symbol = self.pair,
|
||||
side = side,
|
||||
orderType = "Market",
|
||||
qty = str(qty),
|
||||
isLeverage = 1,
|
||||
positionIdx = positionIdx,
|
||||
takeProfit = str(tp),
|
||||
stopLoss = str(sl),
|
||||
tpslMode = "Full"
|
||||
)
|
||||
orderID = response.get('result').get('orderId')
|
||||
|
||||
generalLogger.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:
|
||||
generalLogger.warning(f"Failed to place order on {self.pair}; qty is too small!")
|
||||
return orderID
|
||||
|
||||
|
||||
def handlePrice(self, message):
|
||||
try:
|
||||
markPrice = float(message.get('data')['markPrice'])
|
||||
self.checkCloseConditions(markPrice)
|
||||
|
||||
if self.previousPrice != -1:
|
||||
self.checkOrderConditions(markPrice)
|
||||
self.previousPrice = markPrice
|
||||
except Exception as e:
|
||||
generalLogger.error(e)
|
||||
for line in traceback.format_exception(e):
|
||||
generalLogger.error(line)
|
||||
|
||||
def handlePositionInfo(self, message):
|
||||
data = message.get('data')
|
||||
|
||||
# Usually the 3-order response means SL + market + TP orders were placed.
|
||||
if len(data) == 3:
|
||||
orderType = []
|
||||
orderIDs = []
|
||||
mainOrderID = ''
|
||||
|
||||
f = 0
|
||||
# Fill order types accordingly to the ids
|
||||
for i in data:
|
||||
orderType.append(i['stopOrderType'])
|
||||
orderIDs.append(i['orderId'])
|
||||
author = i['createType']
|
||||
# Verifying it was the TP/SL order placement
|
||||
if i['stopOrderType'] == '' and author == 'CreateByUser':
|
||||
mainOrderID = i['orderID']
|
||||
f = 1
|
||||
if f:
|
||||
for i in self.levels:
|
||||
if mainOrderID == i.get('longIDs')[0]:
|
||||
for j in range(3):
|
||||
if orderType[j] == 'StopLoss':
|
||||
i['longIDs'][1] = orderIDs[j]
|
||||
if orderType[j] == 'TakeProfit':
|
||||
i['longIDs'][2] = orderIDs[j]
|
||||
else:
|
||||
for i in data:
|
||||
orderID = i['orderId']
|
||||
orderStatus = i['orderStatus']
|
||||
if orderStatus == 'Triggered':
|
||||
for j in self.levels:
|
||||
longIDs = j['longIDs']
|
||||
shortIDs = j['shortIDs']
|
||||
if orderID in longIDs:
|
||||
j['long'] = False
|
||||
j['longIDs'] = ['-1', '-1', '-1']
|
||||
generalLogger.info(f"Long order on {self.pair} level {j['price']} triggered TP/SL")
|
||||
if orderID in shortIDs:
|
||||
j['short'] = False
|
||||
j['shortIDs'] = ['-1', '-1', '-1']
|
||||
generalLogger.info(f"Short order on {self.pair} level {j['price']} triggered TP/SL")
|
||||
if orderStatus == 'Filled':
|
||||
for j in self.levels:
|
||||
longIDs = j['longIDs']
|
||||
shortIDs = j['shortIDs']
|
||||
if orderID in longIDs:
|
||||
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']}")
|
||||
if orderID in shortIDs:
|
||||
tradingLogger.info(f"Short order on {self.pair} level {j['price']} filled with P&L {i['closedPnl']} and qty {i['qty']}")
|
||||
|
||||
|
||||
async def getClient(apiKey, apiSecret, testnet, demoTrading):
|
||||
client = HTTP(
|
||||
testnet = testnet,
|
||||
demo = demoTrading,
|
||||
api_key = apiKey,
|
||||
api_secret = apiSecret,
|
||||
)
|
||||
|
||||
try:
|
||||
response = client.get_account_info()
|
||||
print('Auth succesful!')
|
||||
print('Account info:', response.get('retMsg'))
|
||||
generalLogger.info("Got client from getClient")
|
||||
generalLogger.info(f"Account info: {response.get('retMsg')}")
|
||||
return client
|
||||
except Exception as e:
|
||||
print('Auth failed! Check API key!')
|
||||
print('Error:', e)
|
||||
generalLogger.warning("Auth failed! Check API key or internet connection!")
|
||||
return -1
|
||||
|
||||
|
||||
def handlePrice(message):
|
||||
if message['price'] == '':
|
||||
print('meow')
|
||||
print(message)
|
||||
|
||||
|
||||
def handleMessage(message):
|
||||
print(message)
|
||||
|
||||
|
||||
async def socketStrategy(pair: str, params):
|
||||
print('Starting strategy with ', pair)
|
||||
|
||||
# 'highEnd',
|
||||
# 'lowEnd',
|
||||
# 'highBreak',
|
||||
# 'lowBreak',
|
||||
# 'netLevelsAmount',
|
||||
# 'takeDelta',
|
||||
# 'stopDelta',
|
||||
# 'orderSize'
|
||||
async def strategy(pair: str, params):
|
||||
generalLogger.info('Starting strategy with ' + pair)
|
||||
|
||||
paramsDict = await jsonProcessing.parseParams(params)
|
||||
|
||||
client = HTTP(testnet=True)
|
||||
|
||||
levelsAmount = int(paramsDict['netLevelsAmount'])
|
||||
highEnd = float(paramsDict['highEnd'])
|
||||
lowEnd = float(paramsDict['lowEnd'])
|
||||
|
||||
instrumentInfo = client.get_instruments_info(
|
||||
symbol = pair,
|
||||
category = "linear"
|
||||
)
|
||||
infoContents = instrumentInfo.get('result').get('list')[0]
|
||||
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.
|
||||
levelsRaw = await arbus.getLevels(levelsAmount, highEnd, lowEnd, priceDecimals)
|
||||
levels = []
|
||||
for i in range(levelsAmount):
|
||||
levels.append({'price':levelsRaw[i], 'long':False, 'longIDs':['-1', '-1', '-1'], 'short':False, 'shortIDs':['-1', '-1', '-1']})
|
||||
|
||||
td = tradingData(
|
||||
pair,
|
||||
levels,
|
||||
float(paramsDict['highBreak']),
|
||||
float(paramsDict['lowBreak']),
|
||||
float(paramsDict['takeDelta']),
|
||||
float(paramsDict['stopDelta']),
|
||||
float(paramsDict['orderSize'])
|
||||
)
|
||||
|
||||
ws = WebSocket(
|
||||
testnet = options.testnet,
|
||||
channel_type = options.category,
|
||||
# callback = handleMessage
|
||||
channel_type = 'linear',
|
||||
)
|
||||
|
||||
ws_private = WebSocket(
|
||||
wsPrivate = WebSocket(
|
||||
testnet = options.testnet,
|
||||
demo = options.demoTrading,
|
||||
channel_type = "private",
|
||||
api_key = credentials.api_key,
|
||||
api_secret = credentials.api_secret,
|
||||
# callback = handleMessage
|
||||
# trace_logging = True
|
||||
)
|
||||
|
||||
print(ws.is_connected())
|
||||
generalLogger.info(f"Websocket connection state: {ws.is_connected()} (for {pair})")
|
||||
|
||||
ws.ticker_stream(
|
||||
symbol = pair,
|
||||
callback = handlePrice
|
||||
callback = td.handlePrice
|
||||
)
|
||||
wsPrivate.order_stream(
|
||||
callback = td.handlePositionInfo
|
||||
)
|
||||
|
||||
i = 0
|
||||
@ -83,53 +278,12 @@ async def socketStrategy(pair: str, params):
|
||||
while t:
|
||||
t = await jsonProcessing.checkPair(pair)
|
||||
if t != 1:
|
||||
# ws.exit()
|
||||
# ws_private.exit()
|
||||
generalLogger.info("Closing websockets for {pair}")
|
||||
ws.exit()
|
||||
wsPrivate.exit()
|
||||
break
|
||||
await asyncio.sleep(options.loopSleepTime)
|
||||
i += 1
|
||||
|
||||
print('Ending strategy with ', pair)
|
||||
print('Ended on the iteration number ', i)
|
||||
return i
|
||||
|
||||
|
||||
async def strategy(client: HTTP, pair: str, params):
|
||||
startTime = time.time()
|
||||
print('Starting strategy with ', pair)
|
||||
paramsDict = await jsonProcessing.parseParams(params)
|
||||
|
||||
i = 0
|
||||
t = await jsonProcessing.checkPair(pair)
|
||||
while t:
|
||||
t = await jsonProcessing.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)
|
||||
generalLogger.info(f"Ending strategy with {pair}; Ended on the iteration number {i}")
|
||||
return i
|
||||
|
||||
Reference in New Issue
Block a user