import time from calcTools import * from osTools import * from web3Tools import * class MaxProfitUtils: # 判断两个数组是否有交集 @staticmethod def has_intersection(arr1, arr2): return len(set(arr1) & set(arr2)) > 0 # 若二维数组中所有数组与指定数组没有交集,返回True @staticmethod def all_has_no_intersection(swap_details_list, check_swap_details): for swap_details in swap_details_list: if MaxProfitUtils.has_intersection(swap_details['swapPath'], check_swap_details['swapPath']): return False return True # 求和swap详情list的利润并返回 @staticmethod def get_profit_sum(swap_details_list): profit_sum = 0 for swap_details in swap_details_list: profit_sum += swap_details['profit'] return profit_sum @staticmethod def get_max_profit_swap_details_list(x, y): return x if MaxProfitUtils.get_profit_sum(x) > MaxProfitUtils.get_profit_sum(y) else y # 得出指定数据结构中的最大利润path @staticmethod def max_profit_path_list(origin_swap_details_list, level=0, swap_details_list=None): # first level if time.time() - initbestTradeListTime>60: printTime('max_profit_path_list loong') if swap_details_list is None: swap_details_list = [] # last level # >= if level == len(origin_swap_details_list): return swap_details_list # 此层的swap_path与当前的swap_path_list内的所有swap_path都没有交集的话,将此层的加入为x路径 x = [] if MaxProfitUtils.all_has_no_intersection(swap_details_list, origin_swap_details_list[level]): new_swap_details_list = json.loads(json.dumps(swap_details_list)) new_swap_details_list.append(origin_swap_details_list[level]) x = MaxProfitUtils.max_profit_path_list(origin_swap_details_list, level+1, new_swap_details_list) # 不将此层加入的y路径 y = MaxProfitUtils.max_profit_path_list(origin_swap_details_list, level+1, swap_details_list) return MaxProfitUtils.get_max_profit_swap_details_list(x, y) def calOnlyLpInRouterArray(routerArray): onlyLpArray = [] for router in routerArray: for lpInfo in router: lp = lpInfo['lp'] if lp in onlyLpArray: continue if lp in nowBlockLpRes: continue onlyLpArray.append(lp) return onlyLpArray def calRouterProfit(router): # 整理LP信息 for lpInfo in router: lpAddress = lpInfo['lp'] r0 = nowBlockLpRes[lpAddress][0] r1 = nowBlockLpRes[lpAddress][1] if lpAddress not in topLpInfo: return False lpInfo['fee'] = topLpInfo[lpAddress]['fee'] if lpInfo['inToken'] < lpInfo['outToken']: lpInfo['rIn'] = r0 lpInfo['rOut'] = r1 else: lpInfo['rIn'] = r1 lpInfo['rOut'] = r0 # ABA if len(router) == 2: tokenLoan = router[1]['outToken'] tokenIn0 = router[0]['inToken'] tokenLoanETHPrice = baseTokenInfo[tokenLoan]['price'] * 10 ** baseTokenInfo[WETH]['decimals'] / 10 ** baseTokenInfo[tokenLoan]['decimals'] # WETH - A - WETH tradeInfo = calBestAmountV2V2(router[0]['rIn'], router[0]['rOut'], router[0]['fee'], router[1]['rIn'], router[1]['rOut'], router[1]['fee'] ) profit = tradeInfo['profit'] tradeInfo['ethProfit'] = profit * tokenLoanETHPrice #printTime(router, amountIn0, amountOut0, amountOut1, profit) if tradeInfo['ethProfit'] < tradeMinProfit: return False else: tradeInfo['lp0'] = router[0]['lp'] tradeInfo['lp1'] = router[1]['lp'] tradeInfo['fee0'] = router[0]['fee'] tradeInfo['fee1'] = router[1]['fee'] tradeInfo['indexIn0'] = router[0]['indexIn'] tradeInfo['indexIn1'] = router[1]['indexIn'] tradeInfo['tokenLoan'] = tokenLoan tradeInfo['tokenIn0'] = tokenIn0 tradeInfo['tradeToken0'] = router[0]['outToken'] tradeInfo['type'] = 'type2' return tradeInfo # ABCA elif len(router) == 3: tokenLoan = router[2]['outToken'] tokenIn0 = router[0]['inToken'] tokenLoanETHPrice = baseTokenInfo[tokenLoan]['price'] * 10 ** baseTokenInfo[WETH]['decimals'] / 10 ** baseTokenInfo[tokenLoan]['decimals'] tradeInfo = calBestAmountABCA(router[0]['rIn'], router[0]['rOut'], router[0]['fee'], router[1]['rIn'], router[1]['rOut'], router[1]['fee'], router[2]['rIn'], router[2]['rOut'], router[2]['fee']) profit = tradeInfo['profit'] tradeInfo['ethProfit'] = profit * tokenLoanETHPrice #printTime(router, amountIn0, amountOut0, amountOut1, amountOut2, profit) if tradeInfo['ethProfit'] < tradeMinProfit: return False else: tradeInfo['lp0'] = router[0]['lp'] tradeInfo['lp1'] = router[1]['lp'] tradeInfo['lp2'] = router[2]['lp'] tradeInfo['fee0'] = router[0]['fee'] tradeInfo['fee1'] = router[1]['fee'] tradeInfo['fee2'] = router[2]['fee'] tradeInfo['indexIn0'] = router[0]['indexIn'] tradeInfo['indexIn1'] = router[1]['indexIn'] tradeInfo['indexIn2'] = router[2]['indexIn'] tradeInfo['tokenLoan'] = tokenLoan tradeInfo['tokenIn0'] = tokenIn0 tradeInfo['tradeToken0'] = router[0]['outToken'] tradeInfo['tradeToken1'] = router[1]['outToken'] tradeInfo['type'] = 'type3' return tradeInfo def callABCA(lp0, lp1, lpLoan, tokenIn0, tokenLoan, fee0, fee1, fee2, lp0InIndex, lp1InIndex, lp2InIndex, minProfit, block = 'latest'): function = '0x1d650c35' inputData = encodeInput(lp0, lp1, lpLoan, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, fee0, fee1, fee2, lp0InIndex, lp1InIndex, lp2InIndex, tokenIn0, tokenLoan, minProfit, 0) inputData = function + inputData r = gasCal(myAddress, v3CCAddress, inputData, block) tradeInfo = {'type':3, 'lp0': lp0, 'lp1': lp1, 'lpLoan':lpLoan, 'tokenIn':tokenIn0, 'tokenLoan':tokenLoan, 'profit':0, 'sumValue':0} if len(r['info']) != 23 or r['info'][-1] == 0: return tradeInfo sumValue = r['info'][3] + r['info'][4] + r['info'][5] + r['info'][6] + r['info'][7] + r['info'][8] tradeInfo['sumValue'] = sumValue tradeInfo['profit'] = r['info'][-1] tradeInfo['gasUsed'] = r['gasUsed'] tradeInfo['inputData'] = inputData return tradeInfo def callABA(lp0, lp1, tokenIn0, tokenLoan, fee0, fee1, lp0InIndex, lp1InIndex, minProfit, block = 'latest'): # estimasGas 不能用 function = '0xa628df6d' inputData = encodeInput(lp0, lp1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, fee0, fee1, 0, lp0InIndex, lp1InIndex, 0, tokenIn0, tokenLoan, minProfit, 0) inputData = function + inputData r = gasCal(myAddress, v3CCAddress, inputData, block) tradeInfo = {'type':2, 'lp0': lp0, 'lpLoan':lp1, 'tokenIn':tokenIn0, 'tokenLoan':tokenLoan, 'profit':0, 'sumValue':0} if len(r['info']) != 23 or r['info'][-1] == 0: return tradeInfo #路径中所有R0R1的和 sumValue = r['info'][3] + r['info'][4] + r['info'][5] + r['info'][6] + r['info'][7] + r['info'][8] tradeInfo['sumValue'] = sumValue tradeInfo['profit'] = r['info'][-1] tradeInfo['gasUsed'] = r['gasUsed'] tradeInfo['inputData'] = inputData return tradeInfo def readABA(): array = [] for i in baseTokenInfo: i = i.lower() iiArray = readAny('aba' + i + i) array = array + iiArray array = array + readAny('aba' + WETHW + WETH) array = array + readAny('aba' + WETH + WETHW) return array def readABCA(): array = [] for i in baseTokenInfo: i = i.lower() iiArray = readAny('abca' + i + i) array = array + iiArray array = array + readAny('abca' + WETHW + WETH) array = array + readAny('abca' + WETH + WETHW) return array # 读取LP,LP怎么来? topLpInfo = readAny('topLP') # 读取路径,路径怎么来? lpRouterInfo = readABCA() lpRouterInfo = lpRouterInfo + readABA() printTime('v3 start') v3CCAddress = '0xB4971D0dda22359Ad86867362b7FC3206eA0d86B' dataUsed = [] nowBlockLpRes = {} # 自定义开始区块号码 initBlock = w3.eth.blockNumber while True: try: if time.time() % 60 > 50 and time.time() % 60 < 55: # 读取LP topLpInfo = readAny('topLP') # 读取路径 lpRouterInfo = readABCA() lpRouterInfo = lpRouterInfo + readABA() blockNumber = w3.eth.blockNumber if blockNumber == initBlock: time.sleep(0.1) continue initBlock = blockNumber # 算出基础gas baseGasPrice = getBaseGasPrice(blockNumber) * 1.22 if baseGasPrice < 1.6 * 1e9: baseGasPrice = 1.6 * 1e9 nowBlockLpRes = {} # 找出有关该LP的路径 == 因为不限制时间,并且速度够快。可能等于所有路径。 lpRouterArray = lpRouterInfo # findLpRouter(nowBlockLpTradeI, lpRouterInfo) # 过滤重复LP。返回一次所有LP ([ABC][ABD])> [ABCD] lpOnlyRouterArray = calOnlyLpInRouterArray(lpRouterArray) # printTime(len(lpOnlyRouterArray), len(lpRouterArray)) # 获取余额 lpResArray = w3.dex.getPairSBalance(lpOnlyRouterArray, blockNumber) # 余额进行缓存 for lpResI in lpResArray: nowBlockLpRes[lpResI] = lpResArray[lpResI] #计算每一个路径的利润 tradeInfoArray = [] initForTime = time.time() for lpRouter in lpRouterArray: if time.time() - initForTime > 60: printTime('lpRouterArray long time') #计算收入,通过本地R0R1,计算的 tradeInfo = calRouterProfit(lpRouter) if tradeInfo: type = tradeInfo['type'] if type == 'type2': #计算利润 = 收入 - gas支出,通过智能合约计算 result = callABA(tradeInfo['lp0'], tradeInfo['lp1'], tradeInfo['tokenIn0'], tradeInfo['tokenLoan'], tradeInfo['fee0'], tradeInfo['fee1'], tradeInfo['indexIn0'], tradeInfo['indexIn1'], baseTokenInfo[tradeInfo['tokenIn0']]['profitMin'], blockNumber) # result['profit'] 是收入 if result['profit'] < tradeMinProfit: continue sumValue = result['sumValue'] #过滤幽灵LP, 其实可以通过eth call可以排除,但是很耗时间。 if sumValue in dataUsed: continue tokenLoanETHPrice = baseTokenInfo[tradeInfo['tokenLoan']]['price'] * 10 ** baseTokenInfo[WETH]['decimals'] / 10 ** \ baseTokenInfo[tradeInfo['tokenLoan']]['decimals'] result['ethProfit'] = result['profit'] * tokenLoanETHPrice result['ethProfit-E18'] = result['profit'] * tokenLoanETHPrice / 1e18 # result['tureProfit'] 才是利润 result['tureProfit'] = result['ethProfit'] - result['gasUsed'] * baseGasPrice result['maxPrice'] = result['ethProfit'] / result['gasUsed'] if result['tureProfit'] < tradeMinProfit: continue #有利润就加入交易数组 tradeInfoArray.append(result) if type == 'type3': result = callABCA(tradeInfo['lp0'], tradeInfo['lp1'], tradeInfo['lp2'], tradeInfo['tokenIn0'], tradeInfo['tokenLoan'], tradeInfo['fee0'], tradeInfo['fee1'], tradeInfo['fee2'], tradeInfo['indexIn0'], tradeInfo['indexIn1'], tradeInfo['indexIn2'], baseTokenInfo[tradeInfo['tokenIn0']]['profitMin'], blockNumber) if result['profit'] < tradeMinProfit: continue sumValue = result['sumValue'] if sumValue in dataUsed: continue tokenLoanETHPrice = baseTokenInfo[tradeInfo['tokenLoan']]['price'] * 10 ** baseTokenInfo[WETH][ 'decimals'] / 10 ** \ baseTokenInfo[tradeInfo['tokenLoan']]['decimals'] result['ethProfit'] = result['profit'] * tokenLoanETHPrice result['ethProfit-E18'] = result['profit'] * tokenLoanETHPrice / 1e18 result['tureProfit'] = result['ethProfit'] - result['gasUsed'] * baseGasPrice result['maxPrice'] = result['ethProfit'] / result['gasUsed'] if result['tureProfit'] < tradeMinProfit: continue tradeInfoArray.append(result) if len(tradeInfoArray) != 0 : #printTime('trade List' ,blockNumber, tradeInfoArray) tradeList = [] initForTime = time.time() #交易详细信息数组转化为只有 LP:利润的信息。然后所有交易信息用tradeInfoI复制一次。 for tradeInfoI in tradeInfoArray: if tradeInfoI['type'] == 2: tradeList.append({"swapPath": [tradeInfoI['lp0'], tradeInfoI['lpLoan']], "profit": tradeInfoI['tureProfit'], 'data':tradeInfoI}) if tradeInfoI['type'] == 3: tradeList.append( {"swapPath": [tradeInfoI['lp0'], tradeInfoI['lp1'],tradeInfoI['lpLoan']], "profit": tradeInfoI['tureProfit'], 'data':tradeInfoI}) initbestTradeListTime = time.time( ) # 进行利润排序,从大到小 tradeList = sorted(tradeList, key=lambda k: k['profit'], reverse=True) params = [] # 进行input的encode for info in tradeList: paramsI = {} paramsI['from'] = myAddress paramsI['to'] = '0xEAf90BcB75e7B92900678c4aBD80883b414bEDD6' tradeInputData = info['data']['inputData'] paramsI['data'] = '0x4327f0db' + encodeInput(v4Address, 0x40, (len(tradeInputData) - 2) / 2, tradeInputData) params.append(paramsI) #BATCH CALL暴力解LP ,按顺序,进行一次模拟交易。是一直叠加态 batchCallResult = batchCall(params, blockNumber) batchCallResult = decodeBatchCall(batchCallResult['result']) totalProfit = 0 length = len(tradeList) bestIndex = [] for i in range(0, length): profit = batchCallResult[i][-1] if profit > 0: tokenLoan = batchCallResult[i][-3] gasUsed = batchCallResult[i][1] tokenLoanETHPrice = baseTokenInfo[tokenLoan]['price'] * 10 ** baseTokenInfo[WETH][ 'decimals'] / 10 ** \ baseTokenInfo[tokenLoan]['decimals'] profit = profit * tokenLoanETHPrice # 最大gasPrice maxPrice = profit / gasUsed profit = profit - gasUsed * baseGasPrice tradeList[i]['data']['maxPrice'] = maxPrice tradeList[i]['data']['profit'] = profit if profit < tradeMinProfit: continue totalProfit = totalProfit + profit # 成功的位置做key bestIndex.append(i) printTime(blockNumber, 'batch', time.time() - initbestTradeListTime, 's , totalProfit', totalProfit/1e18) # 过滤要下单的路径 bestTradeList = [] for index in bestIndex: bestTradeList.append(tradeList[index]) tradeTimes = 0 initForTime = time.time() # 根据路径下单 for bestTradeI in bestTradeList: if time.time() - initForTime > 60: printTime('bestTradeList long time') printTime(blockNumber, 'bset', bestTradeI['swapPath'], bestTradeI['profit']/1e18) sumValue = bestTradeI['data']['sumValue'] inputData = bestTradeI['data']['inputData'] nowGasPrice = bestTradeI['data']['maxPrice'] * 0.2 if nowGasPrice < baseGasPrice *1.2: nowGasPrice = baseGasPrice * 1.2 if nowGasPrice > baseGasPrice * 1.8: nowGasPrice = baseGasPrice * 1.8 # 为了后面gas war,做传参。 gasLimit = 1e6 + bestTradeI['data']['maxPrice'] / 1e9 + 1 if tradeTimes > 0: gasLimit = 1e6 if gasLimit > 1e6 + 2000: gasLimit = 1e6 + 2000 params = {} params['method'] = 'send' params['params'] = {'toAddress': v3CCAddress, 'inputData': inputData, 'gasPrice': nowGasPrice, 'gasLimit': gasLimit} try: hs = requests.post(url='http://127.0.0.1:410/operation', json=params).text if len(hs)>50: printTime(blockNumber, 'https://www.oklink.com/en/ethw/tx/' + str(hs)) dataUsed.append(sumValue) tradeTimes = tradeTimes + 1 except: hs = 'err seed' printTime('-' * 50) except BaseException as err: printTime(traceback.format_exc()) printTime('loop', ) time.sleep(20)