tradeV4.5.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. import time
  2. from calcTools import *
  3. from osTools import *
  4. from web3Tools import *
  5. class MaxProfitUtils:
  6. # 判断两个数组是否有交集
  7. @staticmethod
  8. def has_intersection(arr1, arr2):
  9. return len(set(arr1) & set(arr2)) > 0
  10. # 若二维数组中所有数组与指定数组没有交集,返回True
  11. @staticmethod
  12. def all_has_no_intersection(swap_details_list, check_swap_details):
  13. for swap_details in swap_details_list:
  14. if MaxProfitUtils.has_intersection(swap_details['swapPath'], check_swap_details['swapPath']):
  15. return False
  16. return True
  17. # 求和swap详情list的利润并返回
  18. @staticmethod
  19. def get_profit_sum(swap_details_list):
  20. profit_sum = 0
  21. for swap_details in swap_details_list:
  22. profit_sum += swap_details['profit']
  23. return profit_sum
  24. @staticmethod
  25. def get_max_profit_swap_details_list(x, y):
  26. return x if MaxProfitUtils.get_profit_sum(x) > MaxProfitUtils.get_profit_sum(y) else y
  27. # 得出指定数据结构中的最大利润path
  28. @staticmethod
  29. def max_profit_path_list(origin_swap_details_list, level=0, swap_details_list=None):
  30. # first level
  31. if time.time() - initbestTradeListTime>60:
  32. printTime('max_profit_path_list loong')
  33. if swap_details_list is None:
  34. swap_details_list = []
  35. # last level
  36. # >=
  37. if level == len(origin_swap_details_list):
  38. return swap_details_list
  39. # 此层的swap_path与当前的swap_path_list内的所有swap_path都没有交集的话,将此层的加入为x路径
  40. x = []
  41. if MaxProfitUtils.all_has_no_intersection(swap_details_list, origin_swap_details_list[level]):
  42. new_swap_details_list = json.loads(json.dumps(swap_details_list))
  43. new_swap_details_list.append(origin_swap_details_list[level])
  44. x = MaxProfitUtils.max_profit_path_list(origin_swap_details_list, level+1, new_swap_details_list)
  45. # 不将此层加入的y路径
  46. y = MaxProfitUtils.max_profit_path_list(origin_swap_details_list, level+1, swap_details_list)
  47. return MaxProfitUtils.get_max_profit_swap_details_list(x, y)
  48. def calOnlyLpInRouterArray(routerArray):
  49. onlyLpArray = []
  50. for router in routerArray:
  51. for lpInfo in router:
  52. lp = lpInfo['lp']
  53. if lp in onlyLpArray:
  54. continue
  55. if lp in nowBlockLpRes:
  56. continue
  57. onlyLpArray.append(lp)
  58. return onlyLpArray
  59. def calRouterProfit(router):
  60. # 整理LP信息
  61. for lpInfo in router:
  62. lpAddress = lpInfo['lp']
  63. r0 = nowBlockLpRes[lpAddress][0]
  64. r1 = nowBlockLpRes[lpAddress][1]
  65. if lpAddress not in topLpInfo:
  66. return False
  67. lpInfo['fee'] = topLpInfo[lpAddress]['fee']
  68. if lpInfo['inToken'] < lpInfo['outToken']:
  69. lpInfo['rIn'] = r0
  70. lpInfo['rOut'] = r1
  71. else:
  72. lpInfo['rIn'] = r1
  73. lpInfo['rOut'] = r0
  74. # ABA
  75. if len(router) == 2:
  76. tokenLoan = router[1]['outToken']
  77. tokenIn0 = router[0]['inToken']
  78. tokenLoanETHPrice = baseTokenInfo[tokenLoan]['price'] * 10 ** baseTokenInfo[WETH]['decimals'] / 10 ** baseTokenInfo[tokenLoan]['decimals']
  79. # WETH - A - WETH
  80. tradeInfo = calBestAmountV2V2(router[0]['rIn'], router[0]['rOut'], router[0]['fee'], router[1]['rIn'], router[1]['rOut'], router[1]['fee'] )
  81. profit = tradeInfo['profit']
  82. tradeInfo['ethProfit'] = profit * tokenLoanETHPrice
  83. #printTime(router, amountIn0, amountOut0, amountOut1, profit)
  84. if tradeInfo['ethProfit'] < tradeMinProfit:
  85. return False
  86. else:
  87. tradeInfo['lp0'] = router[0]['lp']
  88. tradeInfo['lp1'] = router[1]['lp']
  89. tradeInfo['fee0'] = router[0]['fee']
  90. tradeInfo['fee1'] = router[1]['fee']
  91. tradeInfo['indexIn0'] = router[0]['indexIn']
  92. tradeInfo['indexIn1'] = router[1]['indexIn']
  93. tradeInfo['tokenLoan'] = tokenLoan
  94. tradeInfo['tokenIn0'] = tokenIn0
  95. tradeInfo['tradeToken0'] = router[0]['outToken']
  96. tradeInfo['type'] = 'type2'
  97. return tradeInfo
  98. # ABCA
  99. elif len(router) == 3:
  100. tokenLoan = router[2]['outToken']
  101. tokenIn0 = router[0]['inToken']
  102. tokenLoanETHPrice = baseTokenInfo[tokenLoan]['price'] * 10 ** baseTokenInfo[WETH]['decimals'] / 10 ** baseTokenInfo[tokenLoan]['decimals']
  103. tradeInfo = calBestAmountABCA(router[0]['rIn'], router[0]['rOut'], router[0]['fee'],
  104. router[1]['rIn'], router[1]['rOut'], router[1]['fee'],
  105. router[2]['rIn'], router[2]['rOut'], router[2]['fee'])
  106. profit = tradeInfo['profit']
  107. tradeInfo['ethProfit'] = profit * tokenLoanETHPrice
  108. #printTime(router, amountIn0, amountOut0, amountOut1, amountOut2, profit)
  109. if tradeInfo['ethProfit'] < tradeMinProfit:
  110. return False
  111. else:
  112. tradeInfo['lp0'] = router[0]['lp']
  113. tradeInfo['lp1'] = router[1]['lp']
  114. tradeInfo['lp2'] = router[2]['lp']
  115. tradeInfo['fee0'] = router[0]['fee']
  116. tradeInfo['fee1'] = router[1]['fee']
  117. tradeInfo['fee2'] = router[2]['fee']
  118. tradeInfo['indexIn0'] = router[0]['indexIn']
  119. tradeInfo['indexIn1'] = router[1]['indexIn']
  120. tradeInfo['indexIn2'] = router[2]['indexIn']
  121. tradeInfo['tokenLoan'] = tokenLoan
  122. tradeInfo['tokenIn0'] = tokenIn0
  123. tradeInfo['tradeToken0'] = router[0]['outToken']
  124. tradeInfo['tradeToken1'] = router[1]['outToken']
  125. tradeInfo['type'] = 'type3'
  126. return tradeInfo
  127. def callABCA(lp0, lp1, lpLoan, tokenIn0, tokenLoan, fee0, fee1, fee2, lp0InIndex, lp1InIndex, lp2InIndex, minProfit, block = 'latest'):
  128. function = '0x1d650c35'
  129. inputData = encodeInput(lp0, lp1, lpLoan, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  130. fee0, fee1, fee2, lp0InIndex, lp1InIndex, lp2InIndex,
  131. tokenIn0, tokenLoan, minProfit, 0)
  132. inputData = function + inputData
  133. r = gasCal(myAddress, v3CCAddress, inputData, block)
  134. tradeInfo = {'type':3, 'lp0': lp0, 'lp1': lp1, 'lpLoan':lpLoan, 'tokenIn':tokenIn0, 'tokenLoan':tokenLoan, 'profit':0, 'sumValue':0}
  135. if len(r['info']) != 23 or r['info'][-1] == 0:
  136. return tradeInfo
  137. sumValue = r['info'][3] + r['info'][4] + r['info'][5] + r['info'][6] + r['info'][7] + r['info'][8]
  138. tradeInfo['sumValue'] = sumValue
  139. tradeInfo['profit'] = r['info'][-1]
  140. tradeInfo['gasUsed'] = r['gasUsed']
  141. tradeInfo['inputData'] = inputData
  142. return tradeInfo
  143. def callABA(lp0, lp1, tokenIn0, tokenLoan, fee0, fee1, lp0InIndex, lp1InIndex, minProfit, block = 'latest'):
  144. # estimasGas 不能用
  145. function = '0xa628df6d'
  146. inputData = encodeInput(lp0, lp1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  147. fee0, fee1, 0, lp0InIndex, lp1InIndex, 0,
  148. tokenIn0, tokenLoan, minProfit, 0)
  149. inputData = function + inputData
  150. r = gasCal(myAddress, v3CCAddress, inputData, block)
  151. tradeInfo = {'type':2, 'lp0': lp0, 'lpLoan':lp1, 'tokenIn':tokenIn0, 'tokenLoan':tokenLoan, 'profit':0, 'sumValue':0}
  152. if len(r['info']) != 23 or r['info'][-1] == 0:
  153. return tradeInfo
  154. #路径中所有R0R1的和
  155. sumValue = r['info'][3] + r['info'][4] + r['info'][5] + r['info'][6] + r['info'][7] + r['info'][8]
  156. tradeInfo['sumValue'] = sumValue
  157. tradeInfo['profit'] = r['info'][-1]
  158. tradeInfo['gasUsed'] = r['gasUsed']
  159. tradeInfo['inputData'] = inputData
  160. return tradeInfo
  161. def readABA():
  162. array = []
  163. for i in baseTokenInfo:
  164. i = i.lower()
  165. iiArray = readAny('aba' + i + i)
  166. array = array + iiArray
  167. array = array + readAny('aba' + WETHW + WETH)
  168. array = array + readAny('aba' + WETH + WETHW)
  169. return array
  170. def readABCA():
  171. array = []
  172. for i in baseTokenInfo:
  173. i = i.lower()
  174. iiArray = readAny('abca' + i + i)
  175. array = array + iiArray
  176. array = array + readAny('abca' + WETHW + WETH)
  177. array = array + readAny('abca' + WETH + WETHW)
  178. return array
  179. # 读取LP,LP怎么来?
  180. topLpInfo = readAny('topLP')
  181. # 读取路径,路径怎么来?
  182. lpRouterInfo = readABCA()
  183. lpRouterInfo = lpRouterInfo + readABA()
  184. printTime('v3 start')
  185. v3CCAddress = '0xB4971D0dda22359Ad86867362b7FC3206eA0d86B'
  186. dataUsed = []
  187. nowBlockLpRes = {}
  188. # 自定义开始区块号码
  189. initBlock = w3.eth.blockNumber
  190. while True:
  191. try:
  192. if time.time() % 60 > 50 and time.time() % 60 < 55:
  193. # 读取LP
  194. topLpInfo = readAny('topLP')
  195. # 读取路径
  196. lpRouterInfo = readABCA()
  197. lpRouterInfo = lpRouterInfo + readABA()
  198. blockNumber = w3.eth.blockNumber
  199. if blockNumber == initBlock:
  200. time.sleep(0.1)
  201. continue
  202. initBlock = blockNumber
  203. # 算出基础gas
  204. baseGasPrice = getBaseGasPrice(blockNumber) * 1.22
  205. if baseGasPrice < 1.6 * 1e9:
  206. baseGasPrice = 1.6 * 1e9
  207. nowBlockLpRes = {}
  208. # 找出有关该LP的路径 == 因为不限制时间,并且速度够快。可能等于所有路径。
  209. lpRouterArray = lpRouterInfo # findLpRouter(nowBlockLpTradeI, lpRouterInfo)
  210. # 过滤重复LP。返回一次所有LP ([ABC][ABD])> [ABCD]
  211. lpOnlyRouterArray = calOnlyLpInRouterArray(lpRouterArray)
  212. # printTime(len(lpOnlyRouterArray), len(lpRouterArray))
  213. # 获取余额
  214. lpResArray = w3.dex.getPairSBalance(lpOnlyRouterArray, blockNumber)
  215. # 余额进行缓存
  216. for lpResI in lpResArray:
  217. nowBlockLpRes[lpResI] = lpResArray[lpResI]
  218. #计算每一个路径的利润
  219. tradeInfoArray = []
  220. initForTime = time.time()
  221. for lpRouter in lpRouterArray:
  222. if time.time() - initForTime > 60:
  223. printTime('lpRouterArray long time')
  224. #计算收入,通过本地R0R1,计算的
  225. tradeInfo = calRouterProfit(lpRouter)
  226. if tradeInfo:
  227. type = tradeInfo['type']
  228. if type == 'type2':
  229. #计算利润 = 收入 - gas支出,通过智能合约计算
  230. result = callABA(tradeInfo['lp0'], tradeInfo['lp1'],
  231. tradeInfo['tokenIn0'], tradeInfo['tokenLoan'],
  232. tradeInfo['fee0'], tradeInfo['fee1'],
  233. tradeInfo['indexIn0'], tradeInfo['indexIn1'],
  234. baseTokenInfo[tradeInfo['tokenIn0']]['profitMin'], blockNumber)
  235. # result['profit'] 是收入
  236. if result['profit'] < tradeMinProfit:
  237. continue
  238. sumValue = result['sumValue']
  239. #过滤幽灵LP, 其实可以通过eth call可以排除,但是很耗时间。
  240. if sumValue in dataUsed:
  241. continue
  242. tokenLoanETHPrice = baseTokenInfo[tradeInfo['tokenLoan']]['price'] * 10 ** baseTokenInfo[WETH]['decimals'] / 10 ** \
  243. baseTokenInfo[tradeInfo['tokenLoan']]['decimals']
  244. result['ethProfit'] = result['profit'] * tokenLoanETHPrice
  245. result['ethProfit-E18'] = result['profit'] * tokenLoanETHPrice / 1e18
  246. # result['tureProfit'] 才是利润
  247. result['tureProfit'] = result['ethProfit'] - result['gasUsed'] * baseGasPrice
  248. result['maxPrice'] = result['ethProfit'] / result['gasUsed']
  249. if result['tureProfit'] < tradeMinProfit:
  250. continue
  251. #有利润就加入交易数组
  252. tradeInfoArray.append(result)
  253. if type == 'type3':
  254. result = callABCA(tradeInfo['lp0'], tradeInfo['lp1'], tradeInfo['lp2'],
  255. tradeInfo['tokenIn0'], tradeInfo['tokenLoan'],
  256. tradeInfo['fee0'], tradeInfo['fee1'], tradeInfo['fee2'],
  257. tradeInfo['indexIn0'], tradeInfo['indexIn1'], tradeInfo['indexIn2'],
  258. baseTokenInfo[tradeInfo['tokenIn0']]['profitMin'], blockNumber)
  259. if result['profit'] < tradeMinProfit:
  260. continue
  261. sumValue = result['sumValue']
  262. if sumValue in dataUsed:
  263. continue
  264. tokenLoanETHPrice = baseTokenInfo[tradeInfo['tokenLoan']]['price'] * 10 ** baseTokenInfo[WETH][
  265. 'decimals'] / 10 ** \
  266. baseTokenInfo[tradeInfo['tokenLoan']]['decimals']
  267. result['ethProfit'] = result['profit'] * tokenLoanETHPrice
  268. result['ethProfit-E18'] = result['profit'] * tokenLoanETHPrice / 1e18
  269. result['tureProfit'] = result['ethProfit'] - result['gasUsed'] * baseGasPrice
  270. result['maxPrice'] = result['ethProfit'] / result['gasUsed']
  271. if result['tureProfit'] < tradeMinProfit:
  272. continue
  273. tradeInfoArray.append(result)
  274. if len(tradeInfoArray) != 0 :
  275. #printTime('trade List' ,blockNumber, tradeInfoArray)
  276. tradeList = []
  277. initForTime = time.time()
  278. #交易详细信息数组转化为只有 LP:利润的信息。然后所有交易信息用tradeInfoI复制一次。
  279. for tradeInfoI in tradeInfoArray:
  280. if tradeInfoI['type'] == 2:
  281. tradeList.append({"swapPath": [tradeInfoI['lp0'], tradeInfoI['lpLoan']], "profit": tradeInfoI['tureProfit'], 'data':tradeInfoI})
  282. if tradeInfoI['type'] == 3:
  283. tradeList.append(
  284. {"swapPath": [tradeInfoI['lp0'], tradeInfoI['lp1'],tradeInfoI['lpLoan']], "profit": tradeInfoI['tureProfit'], 'data':tradeInfoI})
  285. initbestTradeListTime = time.time( )
  286. # 进行利润排序,从大到小
  287. tradeList = sorted(tradeList, key=lambda k: k['profit'], reverse=True)
  288. params = []
  289. # 进行input的encode
  290. for info in tradeList:
  291. paramsI = {}
  292. paramsI['from'] = myAddress
  293. paramsI['to'] = '0xEAf90BcB75e7B92900678c4aBD80883b414bEDD6'
  294. tradeInputData = info['data']['inputData']
  295. paramsI['data'] = '0x4327f0db' + encodeInput(v4Address, 0x40, (len(tradeInputData) - 2) / 2, tradeInputData)
  296. params.append(paramsI)
  297. #BATCH CALL暴力解LP ,按顺序,进行一次模拟交易。是一直叠加态
  298. batchCallResult = batchCall(params, blockNumber)
  299. batchCallResult = decodeBatchCall(batchCallResult['result'])
  300. totalProfit = 0
  301. length = len(tradeList)
  302. bestIndex = []
  303. for i in range(0, length):
  304. profit = batchCallResult[i][-1]
  305. if profit > 0:
  306. tokenLoan = batchCallResult[i][-3]
  307. gasUsed = batchCallResult[i][1]
  308. tokenLoanETHPrice = baseTokenInfo[tokenLoan]['price'] * 10 ** baseTokenInfo[WETH][
  309. 'decimals'] / 10 ** \
  310. baseTokenInfo[tokenLoan]['decimals']
  311. profit = profit * tokenLoanETHPrice
  312. # 最大gasPrice
  313. maxPrice = profit / gasUsed
  314. profit = profit - gasUsed * baseGasPrice
  315. tradeList[i]['data']['maxPrice'] = maxPrice
  316. tradeList[i]['data']['profit'] = profit
  317. if profit < tradeMinProfit:
  318. continue
  319. totalProfit = totalProfit + profit
  320. # 成功的位置做key
  321. bestIndex.append(i)
  322. printTime(blockNumber, 'batch', time.time() - initbestTradeListTime, 's , totalProfit', totalProfit/1e18)
  323. # 过滤要下单的路径
  324. bestTradeList = []
  325. for index in bestIndex:
  326. bestTradeList.append(tradeList[index])
  327. tradeTimes = 0
  328. initForTime = time.time()
  329. # 根据路径下单
  330. for bestTradeI in bestTradeList:
  331. if time.time() - initForTime > 60:
  332. printTime('bestTradeList long time')
  333. printTime(blockNumber, 'bset', bestTradeI['swapPath'], bestTradeI['profit']/1e18)
  334. sumValue = bestTradeI['data']['sumValue']
  335. inputData = bestTradeI['data']['inputData']
  336. nowGasPrice = bestTradeI['data']['maxPrice'] * 0.2
  337. if nowGasPrice < baseGasPrice *1.2:
  338. nowGasPrice = baseGasPrice * 1.2
  339. if nowGasPrice > baseGasPrice * 1.8:
  340. nowGasPrice = baseGasPrice * 1.8
  341. # 为了后面gas war,做传参。
  342. gasLimit = 1e6 + bestTradeI['data']['maxPrice'] / 1e9 + 1
  343. if tradeTimes > 0:
  344. gasLimit = 1e6
  345. if gasLimit > 1e6 + 2000:
  346. gasLimit = 1e6 + 2000
  347. params = {}
  348. params['method'] = 'send'
  349. params['params'] = {'toAddress': v3CCAddress, 'inputData': inputData,
  350. 'gasPrice': nowGasPrice, 'gasLimit': gasLimit}
  351. try:
  352. hs = requests.post(url='http://127.0.0.1:410/operation', json=params).text
  353. if len(hs)>50:
  354. printTime(blockNumber, 'https://www.oklink.com/en/ethw/tx/' + str(hs))
  355. dataUsed.append(sumValue)
  356. tradeTimes = tradeTimes + 1
  357. except:
  358. hs = 'err seed'
  359. printTime('-' * 50)
  360. except BaseException as err:
  361. printTime(traceback.format_exc())
  362. printTime('loop', )
  363. time.sleep(20)