Xây dựng một con bot giao dịch chênh lệch giá: Tìm cơ hội chênh lệch giá

Trung cấp4/9/2024, 2:29:22 PM
Trong bài viết này, chúng tôi thực hiện một bước lựa chọn trước về các cặp token quan tâm. Sau đó, chúng tôi suy ra công thức toán học để tìm ra cơ hội giao dịch chênh lệch lợi nhuận tối ưu giữa hai nhóm cùng cặp token.

Nếu thiết lập MEV của bạn không giống như thế này, bạn đang ngmi

Bài viết này là một phần của loạt bài về việc xây dựng một robot giao dịch chênh lệch giá. Mục tiêu của loạt bài này là cung cấp hướng dẫn từng bước để xây dựng một robot giao dịch MEV tự động có thể tìm và thực hiện cơ hội chênh lệch giá trên các sàn giao dịch phi tập trung phổ biến.

Trong bài viết này, chúng tôi thực hiện việc lựa chọn trước các cặp token quan tâm. Sau đó, chúng tôi suy diễn công thức toán học để tìm ra cơ hội giao dịch chênh lệch tối ưu giữa hai nhóm của cùng một cặp token. Cuối cùng, chúng tôi thực hiện công thức trong mã code và trả về một danh sách các cơ hội giao dịch chênh lệch tiềm năng.

Chọn các cặp token

Chi tiết về chiến lược giao dịch chênh lệch giá

Trước khi chúng ta bắt đầu tìm kiếm cơ hội giao dịch chênh lệch giá, chúng ta phải xác định rõ phạm vi hoạt động của bot chênh lệch giá của chúng ta. Cụ thể, loại chênh lệch giá nào chúng ta muốn tham gia. Loại chênh lệch giá an toàn nhất là giữa các pool liên quan đến ETH. Vì ETH là tài sản mà bằng khí của các giao dịch của chúng ta được thanh toán, luôn muốn kết thúc với ETH sau một lệch giá là điều tự nhiên. Nhưng mọi người đều cảm thấy cám dỗ khi nghĩ như vậy. Hãy nhớ rằng trong giao dịch, cơ hội đúng giờ trở nên ít sinh lời hơn khi có nhiều người tham gia.

Vì sự đơn giản, chúng tôi sẽ tập trung vào cơ hội cơ cấu giữa các hồ bơi liên quan đến ETH. Chúng tôi sẽ chỉ tìm kiếm cơ hội giữa hai hồ bơi của cùng một cặp token. Chúng tôi sẽ không giao dịch trên cơ hội mà liên quan đến hơn 2 hồ bơi trong tuyến đường giao dịch (còn được gọi là cơ hội multi-hop). Lưu ý rằng nâng cấp chiến lược này lên một chiến lược rủi ro hơn là bước đầu tiên bạn nên thực hiện để cải thiện khả năng sinh lời của bot của bạn.

Để cải thiện chiến lược này, bạn có thể ví dụ như giữ một số hàng tồn kho trong stablecoins và thực hiện cơ hội cơ cấu lại mà mang lại stablecoins. Việc tương tự có thể được thực hiện cho các tài sản rủi ro cao hơn như shitcoins (với các biện pháp phòng ngừa cần thiết), và định kỳ cân đối lại danh mục của bạn thành ETH để thanh toán cho gas.

Một hướng khác sẽ là từ bỏ giả định ngầm về tính nguyên tử mà chúng ta đã đưa ra, và giới thiệu lý do thống kê vào chiến lược của chúng ta. Ví dụ, bằng cách mua một mã thông báo trong một hồ bơi khi giá đã di chuyển thuận lợi hơn một số lượng độ lệch chuẩn nào đó, và sau đó bán nó (chiến lược hồi phục trung bình). Điều này sẽ lý tưởng cho shitcoins không được liệt kê trên các sàn giao dịch tập trung hiệu quả hơn nhiều, hoặc những cái mà giá của chúng không được theo dõi đúng trên chuỗi. Điều này liên quan đến nhiều phần di chuyển hơn và nằm ngoài phạm vi của loạt bài này.

Chọn cặp token

Bây giờ chúng ta đã xác định phạm vi của bot cơ hội chênh lệch giá của chúng ta, chúng ta cần chọn các cặp token mà chúng ta muốn giao dịch. Đây là 2 tiêu chí lựa chọn mà chúng ta sẽ sử dụng:

  • Các cặp đã chọn phải liên quan đến ETH.
  • Các cặp cần được giao dịch trên ít nhất 2 hồ bơi khác nhau.

Sử dụng lại mã từ bài 2: Đọc hiệu quả giá hồ bơi, chúng tôi có mã sau đây liệt kê tất cả các cặp token được triển khai bởi các hợp đồng nhà máy cung cấp:

# [...]# Tải địa chỉ của các hợp đồng nhà máy với open("FactoriesV2.json", "r") as f:factories = json.load(f)# [...]# Lấy danh sách các pool cho mỗi hợp đồng nhà máypairDataList = []for factoryName, factoryData in factories.items():events = getPairEvents(w3.eth.contract(address=factoryData['factory'], abi=factory_abi), 0, w3.eth.block_number)print(f'Đã tìm thấy {len(events)} pools cho {factoryName}')for e in events:   pairDataList.append({       "token0": e["args"]["token0"],       "token1": e["args"]["token1"],       "pair": e["args"]["pair"],       "factory": factoryName   })

Chúng tôi sẽ đơn giản chỉ đảo ngược pairDataList thành một từ điển trong đó các khóa là các cặp token, và các giá trị là danh sách các hồ bơi thực hiện giao dịch cặp đó. Khi lặp qua danh sách, chúng tôi bỏ qua các cặp không liên quan đến ETH. Khi vòng lặp kết thúc, các cặp có ít nhất 2 hồ bơi được chọn sẽ được lưu trữ trong các danh sách có ít nhất 2 phần tử:

# [...]WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"pair_pool_dict = {}for pair_object in pairDataList:# Kiểm tra ETH (WETH) trong cặp.pair = (pair_object['token0'], pair_object['token1'])if WETH not in pair:   continue# Đảm bảo cặp được tham chiếu trong từ điển. if pair not in pair_pool_dict:   pair_pool_dict[pair] = []# Thêm pool vào danh sách các pool giao dịch cặp này.pair_pool_dict[pair].append(pair_object)# Tạo từ điển cuối cùng của các pool sẽ được giao dịch.pool_dict = {}for pair, pool_list in pair_pool_dict.items():if len(pool_list) >= 2:   pool_dict[pair] = pool_list

Một số số liệu thống kê nên được in ra để nắm vững hơn với dữ liệu mà chúng tôi đang làm việc:

# Số cặp khác nhau
print(f'Chúng tôi có {len(pool_dict)} cặp khác nhau.')

# Tổng số hồ
print(f'Chúng tôi có {sum([len(pool_list) for pool_list in pool_dict.values()])} hồ trong tổng số.')

# Cặp có nhiều nhất hồ
print(f'Cặp có nhiều nhất hồ là {max(pool_dict, key=lambda k: len(pool_dict[k]))} với {len(max(pool_dict.values(), key=len))} hồ.')

# Phân phối số lượng hồ trên mỗi cặp, decile
pool_count_list = [len(pool_list) for pool_list in pool_dict.values()]
pool_count_list.sort(reverse=True)
print(f'Số lượng hồ trên mỗi cặp, theo decile: {pool_count_list[::int(len(pool_count_list)/10)]}')

# Phân phối số lượng hồ trên mỗi cặp, phân vị (decile của decile đầu tiên)
pool_count_list.sort(reverse=True)
print(f'Số lượng hồ trên mỗi cặp, theo phân vị: {pool_count_list[::int(len(pool_count_list)/100)][:10]}')

Vào thời điểm viết, điều này xuất ra như sau:

Chúng tôi có 1431 cặp khác nhau.

Chúng tôi có tổng cộng 3081 hồ bơi.

Cặp có nhiều nhất là ('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', '0xdAC17F958D2ee523a2206206994597C13D831ec7') với 16 hồ bơi.

Số lượng hồ bơi mỗi cặp, theo phân vị: [16, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]

Số lượng hồ bơi trên mỗi cặp, theo phân vị: [16, 5, 4, 3, 3, 3, 3, 3, 3, 3]

Việc lấy dữ trữ cho 3000 hồ bơi có thể được thực hiện trong thời gian ít hơn 1 giây với các nút RPC công cộng. Điều này là một khoảng thời gian hợp lý.

Bây giờ, khi chúng ta đã có tất cả dữ liệu cần thiết, chúng ta cần bắt đầu tìm cơ hội arbitrages.

Tìm cơ hội lợi nhuận chênh lệch giữa các sàn giao dịch

Ý tưởng chung

Cơ hội giao dịch khác biệt luôn tồn tại khi có sự chênh lệch giá giữa hai hồ bơi giao dịch cùng một cặp tiền. Tuy nhiên, không phải tất cả các khác biệt giá đều có thể khai thác: chi phí gas của giao dịch đặt ra một giá trị tối thiểu phải được thu hồi bằng giao dịch, và thanh khoản trong mỗi hồ bơi giới hạn giá trị có thể được khai thác từ sự chênh lệch giá nhất định.

Để tìm cơ hội thu lợi nhuận từ việc mua bán chênh lệch lớn nhất có thể tiếp cận được, chúng ta sẽ cần tính toán giá trị tiềm năng có thể khai thác từ mỗi chênh lệch giá, xem xét dự trữ/tính thanh khoản trong mỗi tứ giác, và ước lượng chi phí gas của giao dịch.

Công thức kích thước giao dịch tối ưu chênh lệch giá

Khi một cơ hội cơ cấu giá được khai thác, giá của hồ chứa mua token đầu vào sẽ giảm, và giá của hồ chứa bán sẽ tăng. Sự di chuyển của giá được mô tả bởi công thức sản phẩm không đổi.

Chúng ta đã thấy trong @emileamajar/building-an-arbitrage-bot-automated-market-makers-and-uniswap-2d208215d8c2">article 1 cách tính toán lợi nhuận của một giao dịch thông qua một hồ bơi, dựa trên dự trữ của hồ bơi đó và số tiền đầu vào.

Để tìm kích thước giao dịch tối ưu, trước tiên chúng ta tìm một công thức cho đầu ra của hai lần trao đổi liên tiếp, với một số lượng đầu vào nhất định, và các dự trữ của hai hồ bơi liên quan đến các lần trao đổi.

Chúng tôi giả định rằng đầu vào của lần đổi đầu tiên là trong token0, và đầu vào của lần đổi thứ hai là trong token1, cuối cùng tạo ra đầu ra trong token0.

Cho x là số lượng đầu vào, (a1, b1) là dự trữ của hồ bơi đầu tiên, và (a2, b2) là dự trữ của hồ bơi thứ hai. Phí là số phí được hồ bơi thu, và được giả định là như nhau cho cả hai hồ bơi (0.3% hầu hết thời gian).

Chúng tôi xác định một hàm tính toán đầu ra của một giao dịch swap, với đầu vào là x và các dự trữ (a, b):

f(x, a, b) = b (1 - a/(a + x(1-fee)))

Chúng tôi sau đó biết rằng đầu ra của lần trao đổi đầu tiên là:

out1(x) = f(x, a1, b1)

out1(x) = b1 (1 - a1/(a1 + x(1-fee)))

Kết quả của lần trao đổi thứ hai là: (lưu ý các biến dự trữ đã được hoán đổi)

out2(x) = f(out1(x), b2, a2)

out2(x) = f(f(x, a1, b1), b2, a2)

out2(x) = a2 (1 - b2/(b2 + f(x, a1, b1)(1-fee)))

out2(x) = a2 (1 - b2/(b2 + b1) (1 - a1/(a1 + x (1-fee))) (1-fee)))

Chúng ta có thể vẽ biểu đồ hàm số này bằng cách desmos. Bằng cách chọn các giá trị dự trữ sao cho chúng ta mô phỏng cặp tiền đầu tiên có 1 ETH và 1750 USDC, và cặp tiền thứ hai có 1340 USDC và 1 ETH, chúng tôi có được đồ thị sau:

Biểu đồ lợi nhuận gộp của giao dịch như một hàm của giá trị đầu vào

Lưu ý rằng chúng tôi đã thực sự vẽ đồ thị out2(x) - x, đó là lợi nhuận của giao dịch, trừ đi số tiền đầu vào.

Đồ họa, chúng ta có thể thấy kích thước giao dịch tối ưu nằm ở mức 0.0607 ETH đầu vào, tạo ra lợi nhuận là 0.0085 ETH. Hợp đồng phải có ít nhất 0.0607 ETH thanh khoản trong WETH để có thể tận dụng cơ hội này.

Giá trị lợi nhuận này của 0.0085 ETH (~$16 khi viết bài này) KHÔNG phải là lợi nhuận cuối cùng của giao dịch, vì chúng ta vẫn cần tính đến chi phí gas của giao dịch. Điều này sẽ được thảo luận trong một bài viết tiếp theo.

Chúng tôi muốn tự động tính toán kích thước giao dịch tối ưu này cho bot MEV của chúng tôi. Điều này có thể được thực hiện thông qua phép tính phổ cập. Chúng tôi có một hàm của một biến x mà chúng tôi muốn tối đa hóa. Hàm đạt giá trị lớn nhất của nó cho một giá trị của x mà đạo hàm của hàm đó bằng 0.

Các công cụ miễn phí và trực tuyến khác nhau có thể được sử dụng để tính nguyên hàm của một hàm một cách biểu tượng, chẳng hạn như wolfram alpha.

Tìm đạo hàm của hàm lợi nhuận gộp của chúng tôi.

Việc tìm một đạo hàm như vậy rất đơn giản với Wolfram Alpha. Bạn cũng có thể làm điều đó bằng tay nếu bạn không chắc chắn về kỹ năng toán học của mình.

Wolfram Alpha đưa ra đạo hàm sau:

dout2(x)/dx = (a1b1a2b2(1-fee)^2)/(a1b2 + (1-fee)x(b1(1-fee)+b2))^2

Vì chúng ta muốn tìm giá trị của x mà làm cho lợi nhuận cực đại (đó là out2(x) - x), chúng ta cần tìm giá trị của x mà đạo hàm là 1 (và không phải là 0).

Wolfram Alpha đưa ra giải pháp sau cho x trong phương trình dout2(x)/dx = 1:

x = (sqrt(a1b1a2b2(1-phí)^4 (b1(1-fee)+b2)^2) - a1b2(1-fee)(b1(1-fee)+b2)) / ((1-fee) (b1(1-fee) + b2))^2

Với các giá trị của các nguồn dự trữ mà chúng tôi đã sử dụng trong biểu đồ ở trên, chúng tôi có x_optimal = 0.0607203782551, điều này chứng minh công thức của chúng tôi là đúng (so với giá trị trên biểu đồ là 0.0607).

Mặc dù công thức này không đọc được lắm, nhưng dễ dàng thực hiện trong mã. Đây là một sự thực hiện python của công thức để tính toán đầu ra của 2 lần trao đổi và kích thước giao dịch tối ưu:

# Các hàm trợ giúp cho việc tính kích thước giao dịch tối ưu# Đầu ra của một lần đổi trảdef swap_output(x, a, b, fee=0.003):trả lại b * (1 - a/(a + x*(1-fee)))# Lợi nhuận gộp của hai lần đổi trả liên tiếpdef trade_profit(x, reserves1, reserves2, fee=0.003): a1, b1 = reserves1a2, b2 = reserves2return swap_output(swap_output(x, a1, b1, fee), b2, a2, fee) - x# Số lượng đầu vào tối ưudef optimal_trade_size(reserves1, reserves2, fee=0.003):a1, b1 = reserves1a2, b2 = reserves2return (math.sqrt(a1*b1*a2*b2*(1-fee)**4 * (b1*(1-fee)+b2)**2) - a1*b2*(1-fee)*(b1*(1-fee)+b2)) / ((1-fee) * (b1*(1-fee) + b2))**2

Tìm cơ hội Arbitrage

Bây giờ chúng ta biết cách tính lợi nhuận gộp từ cơ hội giao dich chênh lệch giá giữa hai hồ bơi cho cùng một cặp token, chúng ta chỉ cần lặp lại tất cả các cặp token, và kiểm tra từng cặp hai cặp tất cả các hồ bơi có cùng một cặp token. Điều này sẽ cho chúng ta lợi nhuận gộp của tất cả các cơ hội giao dịch chênh lệch giá có thể có nằm trong phạm vi chiến lược của chúng ta.

Để ước lượng lợi nhuận ròng của một giao dịch, chúng ta cần ước lượng chi phí gas để khai thác một cơ hội cụ thể. Điều này có thể được thực hiện một cách chính xác bằng cách mô phỏng giao dịch thông qua eth_call đến một nút RPC, nhưng mất rất nhiều thời gian và chỉ có thể thực hiện cho một vài chục cơ hội mỗi khối.

Chúng tôi sẽ đầu tiên thực hiện một ước lượng tổng cộng về chi phí gas bằng cách giả định một chi phí gas giao dịch cố định (một giới hạn dưới, thực ra), và loại bỏ những cơ hội không đủ lợi nhuận để bù đắp chi phí gas. Chỉ sau đó chúng tôi sẽ thực hiện một ước lượng chính xác về chi phí gas cho những cơ hội còn lại.

Đây là mã mà đi qua tất cả các cặp và tất cả các hồ, và sắp xếp các cơ hội theo lợi nhuận:

# [...] # Tìm nạp trữ lượng của mỗi nhóm trong pool_dictto_fetch = [] # Danh sách các địa chỉ nhóm mà dự trữ cần được tìm nạp.for cặp, pool_list trong pool_dict.items():for pair_object trong pool_list: to_fetch.append(pair_object["pair"]) # Thêm địa chỉ của poolprint(f"Fetching reserve of {len(to_fetch)} pools...")# getReservesParallel() là từ bài viết 2 trong loạt bot MEVRESERVEList = asyncio.get_event_loop().run_until_complete(getReservesParallel(to_fetch,  providersAsync))# Xây dựng danh sách các cơ hội giao dịchindex = 0opps = []cho cặp, pool_list trong pool_dict.items():# Lưu trữ dự trữ trong các đối tượng pool để sử dụng sau này pair_object trong pool_list: pair_object["reserves"] = reserveList[index] index += 1# Lặp lại trên tất cả các pool của pairfor poolA trong pool_list: đối với poolB trong pool_list: # Bỏ qua nếu đó là cùng một pool nếu poolA["pair"] == poolB["pair"]:            continue # Bỏ qua nếu một trong các dự trữ là 0 (chia cho 0) nếu 0 trong poolA["reserves"] hoặc 0 trong poolB["reserves"]: tiếp tục # Sắp xếp lại các khoản dự trữ để WETH luôn là token đầu tiên nếu poolA["token0"] == WETH: res_A = (poolA["reserves"][0], poolA["reserves"][1]) res_B = (poolB["reserves"][0], poolB["reserves"][1]) khác: res_A = (poolA["reserves"][1],  poolA["reserves"][0]) res_B = (poolB["reserves"][1], poolB["reserves"][0]) # Tính giá trị đầu vào tối ưu thông qua công thức x = optimal_trade_size(res_A, res_B) # Bỏ qua nếu đầu vào tối ưu là âm (thứ tự của các pool bị đảo ngược) nếu x < 0: tiếp tục # Tính lợi nhuận gộp bằng Wei (trước chi phí gas) lợi nhuận = trade_profit(x,  res_A, res_B) # Lưu trữ chi tiết về cơ hội. Các giá trị nằm trong ETH. (1e18 Wei = 1 ETH) opps.append({ "profit": profit / 1e18, "input": x / 1e18, "pair": pair, "poolA": poolA, "poolB": poolB, })print(f"Found {len(opps)} opportunities.")

Điều này tạo ra đầu ra sau:

Đang lấy dữ trữ của 3081 hồ bơi.

Tìm thấy 1791 cơ hội.

Chúng tôi hiện đã có một danh sách tất cả các cơ hội. Chúng tôi chỉ cần ước lượng lợi nhuận của họ. Hiện tại, chúng tôi sẽ đơn giản giả sử một chi phí gas không đổi cho việc giao dịch trên một cơ hội.

Chúng ta phải sử dụng một ngưỡng dưới cho chi phí gas của một giao dịch trên Uniswap V2. Thực nghiệm, chúng tôi đã phát hiện rằng giá trị này gần với 43k gas.

Tận dụng cơ hội yêu cầu 2 lần trao đổi, và thực hiện giao dịch trên Ethereum tốn phí cố định là 21k gas, tổng cộng 107k gas cho mỗi cơ hội.

Đây là mã tính toán lợi nhuận ròng ước lượng của mỗi cơ hội:

# [...]# Sử dụng chi phí gas cứng là 107k gas mỗi cơ hội gp = w3.eth.gas_pricefor opp in opps:opp["lợi_nhuận_net"] = opp["lợi_nhuận"] - 107000 * gp / 1e18# Sắp xếp theo ước lượng lợi nhuận netopps.sort(key=lambda x: x["lợi_nhuận_net"], reverse=True)# Giữ các cơ hội tích cựcpositive_opps = [opp for opp in opps if opp["lợi_nhuận_net"] > 0]

In bảng thống kê

# Cơ hội tích cực đếmprint(f"Đã tìm thấy {len(positive_opps)} cơ hội tích cực.")# Chi tiết về mỗi cơ hội ETH_PRICE = 1900 # Bạn nên động thời lấy giá của ETHcho opp trong positive_opps:print(f"Lợi nhuận: {opp['net_profit']} ETH (${opp['net_profit'] * ETH_PRICE})")print(f"Đầu vào: {opp['input']} ETH (${opp['input'] * ETH_PRICE})")print(f"Gói A: {opp['poolA']['pair']}")print(f"Gói B: {opp['poolB']['pair']}")print()

Đây là kết quả của kịch bản:

Tìm thấy 57 cơ hội tích cực.

Lợi nhuận: 4.936025725859028 ETH ($9378.448879132153)

Input: 1.7958289984719014 ETH ($3412.075097096613)

Nhóm A: 0x1498bd576454159Bb81B5Ce532692a8752D163e8

Pool B: 0x7D7E813082eF6c143277c71786e5bE626ec77b20

{‘profit’: 4.9374642090282865, ‘input’: 1.7958(...)

Lợi nhuận: 4.756587769768892 ETH ($9037.516762560894)

Input: 0.32908348765283796 ETH ($625.2586265403921)

Nhóm A: 0x486c1609f9605fA14C28E311b7D708B0541cd2f5

Pool B: 0x5e81b946b61F3C7F73Bf84dd961dE3A0A78E8c33

{‘profit’: 4.7580262529381505, ‘input’: 0.329(…)

Lợi nhuận: 0.8147203063054365 ETH ($1547.9685819803292)

Input: 0.6715171730669338 ETH ($1275.8826288271744)

Pool A: 0x1f1B4836Dde1859e2edE1C6155140318EF5931C2

Pool B: 0x1f7efDcD748F43Fc4BeAe6897e5a6DDd865DcceA

{‘profit’: 0.8161587894746954, ‘input’: 0.671(…)

(...)

Lợi nhuận cao đáng ngờ. Bước đầu tiên cần thực hiện là xác minh rằng mã là chính xác. Sau khi kiểm tra mã một cách cẩn thận, chúng tôi đã phát hiện rằng mã là chính xác.

Những lợi nhuận này có thực không? Nhưng dường như không. Chúng tôi đã tung lưới quá rộng khi chọn lựa xem xét các hồ bơi nào trong chiến lược của chúng tôi, và đã bắt được những hồ bơi chứa các token độc hại.

Tiêu chuẩn mã token ERC20 chỉ mô tả một giao diện cho tính tương tác. Bất kỳ ai cũng có thể triển khai một token mà thực hiện giao diện này, và chọn cách thực hiện hành vi không theo khuôn phép, đó chính là điều đang diễn ra ở đây.

Một số nhà tạo token thiết kế ERC20 của họ sao cho các pool mà họ được giao dịch không thể bán, mà chỉ có thể mua token. Một số hợp đồng token thậm chí còn có cơ chế kill-switches cho phép người tạo có thể rút tiền tất cả người dùng của nó.

Trong bot MEV của chúng tôi, những token độc hại này phải được lọc ra. Điều này sẽ được đề cập trong một bài viết trong tương lai.

Nếu chúng ta loại bỏ thủ công các token có hại một cách rõ ràng, chúng ta sẽ còn lại 42 cơ hội sau đây:

Lợi nhuận: 0.004126583158496902 ETH ($7.840508001144114)

Input: 0.008369804833786892 ETH ($15.902629184195094)

Nhóm A: 0xdF42388059692150d0A9De836E4171c7B9c09CBf

Pool B: 0xf98fCEB2DC0Fa2B3f32ABccc5e8495E961370B23

{‘profit’: 0.005565066327755902, (...)

Lợi nhuận: 0.004092580415474992 ETH ($7.775902789402485)

Input: 0.014696360216108083 ETH ($27.92308441060536)

Pool A: 0xfDBFb4239935A15C2C348400570E34De3b044c5F

Pool B: 0x0F15d69a7E5998252ccC39Ad239Cef67fa2a9369

{‘lợi nhuận’: 0.005531063584733992, (...)

Lợi nhuận: 0.003693235163284344 ETH ($7.017146810240254)

Input: 0.1392339178514088 ETH ($264.5444439176767)

Nhóm A: 0x2957215d0473d2c811A075725Da3C31D2af075F1

Pool B: 0xF110783EbD020DCFBA91Cd1976b79a6E510846AA

{‘lợi nhuận’: 0.005131718332543344, (...)

Lợi nhuận: 0.003674128918827048 ETH ($6.980844945771391)

Input: 0.2719041848570484 ETH ($516.617951228392)

Nhóm A: 0xBa19343ff3E9f496F17C7333cdeeD212D65A8425

Pool B: 0xD30567f1d084f411572f202ebb13261CE9F46325

{‘lợi nhuận’: 0.005112612088086048, (...)

(...)

Chú ý rằng nói chung lợi nhuận thấp hơn số tiền cần thiết để thực hiện giao dịch.

Lợi nhuận này khá hợp lý hơn nhiều. Nhưng hãy nhớ rằng họ vẫn là lợi nhuận trong trường hợp tốt nhất, vì chúng tôi đã sử dụng một ước lượng rất thô sơ về chi phí gas của mỗi cơ hội.

Trong một bài viết tương lai, chúng tôi sẽ mô phỏng việc thực hiện giao dịch của chúng tôi để có được một giá trị chính xác về chi phí khí gas của mỗi cơ hội.

Để mô phỏng việc thực hiện, chúng ta cần phát triển hợp đồng thông minh sẽ thực hiện giao dịch. Đây là chủ đề của bài viết tiếp theo.

Kết luận

Chúng tôi hiện có một định nghĩa rõ ràng về phạm vi của bot cơ hội giao dịch MEV của chúng tôi.

Chúng tôi đã khám phá lý thuyết toán học đằng sau chiến lược cơ hội thương mại, và đã triển khai nó trong Python.

Chúng tôi hiện có một danh sách các cơ hội cơ động tiềm năng, và chúng tôi cần mô phỏng việc thực hiện chúng để có được giá trị lợi nhuận cuối cùng. Để làm điều đó, chúng tôi cần có hợp đồng giao dịch thông minh của mình sẵn sàng.

Trong bài viết tiếp theo, chúng tôi sẽ phát triển một hợp đồng thông minh như vậy trong Solidity, và mô phỏng giao dịch arbitrages đầu tiên của chúng tôi.

Bạn có thể tìm mã hoàn chỉnh trong thư viện github liên kết với bài viết nàyKịch bản hoạt động tốt nhất khi chạy trên sổ ghi chú Jupyter.

Miễn trách nhiệm:

  1. Bài viết này được tái bản từ [Gatetrung bình], Tất cả bản quyền thuộc về tác giả gốc [Emile Amajar]. Tiêu đề bài viết gốc “Xây dựng một con bot thương mại: Tìm cơ hội thương mại (bài 3/n)”, Nếu có ý kiến phản đối về việc sao chép này, vui lòng liên hệ với Gate Họcteam, và họ sẽ xử lý ngay lập tức.
  2. Bản phủ nhận trách nhiệm: Quan điểm và ý kiến được biểu đạt trong bài viết này chỉ thuộc về tác giả và không đại diện cho bất kỳ lời khuyên đầu tư nào.
  3. Các bản dịch của bài viết sang các ngôn ngữ khác được thực hiện bởi nhóm Gate Learn. Trừ khi được nêu rõ, việc sao chép, phân phối hoặc đạo văn các bài viết dịch là không được phép.

Xây dựng một con bot giao dịch chênh lệch giá: Tìm cơ hội chênh lệch giá

Trung cấp4/9/2024, 2:29:22 PM
Trong bài viết này, chúng tôi thực hiện một bước lựa chọn trước về các cặp token quan tâm. Sau đó, chúng tôi suy ra công thức toán học để tìm ra cơ hội giao dịch chênh lệch lợi nhuận tối ưu giữa hai nhóm cùng cặp token.

Nếu thiết lập MEV của bạn không giống như thế này, bạn đang ngmi

Bài viết này là một phần của loạt bài về việc xây dựng một robot giao dịch chênh lệch giá. Mục tiêu của loạt bài này là cung cấp hướng dẫn từng bước để xây dựng một robot giao dịch MEV tự động có thể tìm và thực hiện cơ hội chênh lệch giá trên các sàn giao dịch phi tập trung phổ biến.

Trong bài viết này, chúng tôi thực hiện việc lựa chọn trước các cặp token quan tâm. Sau đó, chúng tôi suy diễn công thức toán học để tìm ra cơ hội giao dịch chênh lệch tối ưu giữa hai nhóm của cùng một cặp token. Cuối cùng, chúng tôi thực hiện công thức trong mã code và trả về một danh sách các cơ hội giao dịch chênh lệch tiềm năng.

Chọn các cặp token

Chi tiết về chiến lược giao dịch chênh lệch giá

Trước khi chúng ta bắt đầu tìm kiếm cơ hội giao dịch chênh lệch giá, chúng ta phải xác định rõ phạm vi hoạt động của bot chênh lệch giá của chúng ta. Cụ thể, loại chênh lệch giá nào chúng ta muốn tham gia. Loại chênh lệch giá an toàn nhất là giữa các pool liên quan đến ETH. Vì ETH là tài sản mà bằng khí của các giao dịch của chúng ta được thanh toán, luôn muốn kết thúc với ETH sau một lệch giá là điều tự nhiên. Nhưng mọi người đều cảm thấy cám dỗ khi nghĩ như vậy. Hãy nhớ rằng trong giao dịch, cơ hội đúng giờ trở nên ít sinh lời hơn khi có nhiều người tham gia.

Vì sự đơn giản, chúng tôi sẽ tập trung vào cơ hội cơ cấu giữa các hồ bơi liên quan đến ETH. Chúng tôi sẽ chỉ tìm kiếm cơ hội giữa hai hồ bơi của cùng một cặp token. Chúng tôi sẽ không giao dịch trên cơ hội mà liên quan đến hơn 2 hồ bơi trong tuyến đường giao dịch (còn được gọi là cơ hội multi-hop). Lưu ý rằng nâng cấp chiến lược này lên một chiến lược rủi ro hơn là bước đầu tiên bạn nên thực hiện để cải thiện khả năng sinh lời của bot của bạn.

Để cải thiện chiến lược này, bạn có thể ví dụ như giữ một số hàng tồn kho trong stablecoins và thực hiện cơ hội cơ cấu lại mà mang lại stablecoins. Việc tương tự có thể được thực hiện cho các tài sản rủi ro cao hơn như shitcoins (với các biện pháp phòng ngừa cần thiết), và định kỳ cân đối lại danh mục của bạn thành ETH để thanh toán cho gas.

Một hướng khác sẽ là từ bỏ giả định ngầm về tính nguyên tử mà chúng ta đã đưa ra, và giới thiệu lý do thống kê vào chiến lược của chúng ta. Ví dụ, bằng cách mua một mã thông báo trong một hồ bơi khi giá đã di chuyển thuận lợi hơn một số lượng độ lệch chuẩn nào đó, và sau đó bán nó (chiến lược hồi phục trung bình). Điều này sẽ lý tưởng cho shitcoins không được liệt kê trên các sàn giao dịch tập trung hiệu quả hơn nhiều, hoặc những cái mà giá của chúng không được theo dõi đúng trên chuỗi. Điều này liên quan đến nhiều phần di chuyển hơn và nằm ngoài phạm vi của loạt bài này.

Chọn cặp token

Bây giờ chúng ta đã xác định phạm vi của bot cơ hội chênh lệch giá của chúng ta, chúng ta cần chọn các cặp token mà chúng ta muốn giao dịch. Đây là 2 tiêu chí lựa chọn mà chúng ta sẽ sử dụng:

  • Các cặp đã chọn phải liên quan đến ETH.
  • Các cặp cần được giao dịch trên ít nhất 2 hồ bơi khác nhau.

Sử dụng lại mã từ bài 2: Đọc hiệu quả giá hồ bơi, chúng tôi có mã sau đây liệt kê tất cả các cặp token được triển khai bởi các hợp đồng nhà máy cung cấp:

# [...]# Tải địa chỉ của các hợp đồng nhà máy với open("FactoriesV2.json", "r") as f:factories = json.load(f)# [...]# Lấy danh sách các pool cho mỗi hợp đồng nhà máypairDataList = []for factoryName, factoryData in factories.items():events = getPairEvents(w3.eth.contract(address=factoryData['factory'], abi=factory_abi), 0, w3.eth.block_number)print(f'Đã tìm thấy {len(events)} pools cho {factoryName}')for e in events:   pairDataList.append({       "token0": e["args"]["token0"],       "token1": e["args"]["token1"],       "pair": e["args"]["pair"],       "factory": factoryName   })

Chúng tôi sẽ đơn giản chỉ đảo ngược pairDataList thành một từ điển trong đó các khóa là các cặp token, và các giá trị là danh sách các hồ bơi thực hiện giao dịch cặp đó. Khi lặp qua danh sách, chúng tôi bỏ qua các cặp không liên quan đến ETH. Khi vòng lặp kết thúc, các cặp có ít nhất 2 hồ bơi được chọn sẽ được lưu trữ trong các danh sách có ít nhất 2 phần tử:

# [...]WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"pair_pool_dict = {}for pair_object in pairDataList:# Kiểm tra ETH (WETH) trong cặp.pair = (pair_object['token0'], pair_object['token1'])if WETH not in pair:   continue# Đảm bảo cặp được tham chiếu trong từ điển. if pair not in pair_pool_dict:   pair_pool_dict[pair] = []# Thêm pool vào danh sách các pool giao dịch cặp này.pair_pool_dict[pair].append(pair_object)# Tạo từ điển cuối cùng của các pool sẽ được giao dịch.pool_dict = {}for pair, pool_list in pair_pool_dict.items():if len(pool_list) >= 2:   pool_dict[pair] = pool_list

Một số số liệu thống kê nên được in ra để nắm vững hơn với dữ liệu mà chúng tôi đang làm việc:

# Số cặp khác nhau
print(f'Chúng tôi có {len(pool_dict)} cặp khác nhau.')

# Tổng số hồ
print(f'Chúng tôi có {sum([len(pool_list) for pool_list in pool_dict.values()])} hồ trong tổng số.')

# Cặp có nhiều nhất hồ
print(f'Cặp có nhiều nhất hồ là {max(pool_dict, key=lambda k: len(pool_dict[k]))} với {len(max(pool_dict.values(), key=len))} hồ.')

# Phân phối số lượng hồ trên mỗi cặp, decile
pool_count_list = [len(pool_list) for pool_list in pool_dict.values()]
pool_count_list.sort(reverse=True)
print(f'Số lượng hồ trên mỗi cặp, theo decile: {pool_count_list[::int(len(pool_count_list)/10)]}')

# Phân phối số lượng hồ trên mỗi cặp, phân vị (decile của decile đầu tiên)
pool_count_list.sort(reverse=True)
print(f'Số lượng hồ trên mỗi cặp, theo phân vị: {pool_count_list[::int(len(pool_count_list)/100)][:10]}')

Vào thời điểm viết, điều này xuất ra như sau:

Chúng tôi có 1431 cặp khác nhau.

Chúng tôi có tổng cộng 3081 hồ bơi.

Cặp có nhiều nhất là ('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', '0xdAC17F958D2ee523a2206206994597C13D831ec7') với 16 hồ bơi.

Số lượng hồ bơi mỗi cặp, theo phân vị: [16, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]

Số lượng hồ bơi trên mỗi cặp, theo phân vị: [16, 5, 4, 3, 3, 3, 3, 3, 3, 3]

Việc lấy dữ trữ cho 3000 hồ bơi có thể được thực hiện trong thời gian ít hơn 1 giây với các nút RPC công cộng. Điều này là một khoảng thời gian hợp lý.

Bây giờ, khi chúng ta đã có tất cả dữ liệu cần thiết, chúng ta cần bắt đầu tìm cơ hội arbitrages.

Tìm cơ hội lợi nhuận chênh lệch giữa các sàn giao dịch

Ý tưởng chung

Cơ hội giao dịch khác biệt luôn tồn tại khi có sự chênh lệch giá giữa hai hồ bơi giao dịch cùng một cặp tiền. Tuy nhiên, không phải tất cả các khác biệt giá đều có thể khai thác: chi phí gas của giao dịch đặt ra một giá trị tối thiểu phải được thu hồi bằng giao dịch, và thanh khoản trong mỗi hồ bơi giới hạn giá trị có thể được khai thác từ sự chênh lệch giá nhất định.

Để tìm cơ hội thu lợi nhuận từ việc mua bán chênh lệch lớn nhất có thể tiếp cận được, chúng ta sẽ cần tính toán giá trị tiềm năng có thể khai thác từ mỗi chênh lệch giá, xem xét dự trữ/tính thanh khoản trong mỗi tứ giác, và ước lượng chi phí gas của giao dịch.

Công thức kích thước giao dịch tối ưu chênh lệch giá

Khi một cơ hội cơ cấu giá được khai thác, giá của hồ chứa mua token đầu vào sẽ giảm, và giá của hồ chứa bán sẽ tăng. Sự di chuyển của giá được mô tả bởi công thức sản phẩm không đổi.

Chúng ta đã thấy trong @emileamajar/building-an-arbitrage-bot-automated-market-makers-and-uniswap-2d208215d8c2">article 1 cách tính toán lợi nhuận của một giao dịch thông qua một hồ bơi, dựa trên dự trữ của hồ bơi đó và số tiền đầu vào.

Để tìm kích thước giao dịch tối ưu, trước tiên chúng ta tìm một công thức cho đầu ra của hai lần trao đổi liên tiếp, với một số lượng đầu vào nhất định, và các dự trữ của hai hồ bơi liên quan đến các lần trao đổi.

Chúng tôi giả định rằng đầu vào của lần đổi đầu tiên là trong token0, và đầu vào của lần đổi thứ hai là trong token1, cuối cùng tạo ra đầu ra trong token0.

Cho x là số lượng đầu vào, (a1, b1) là dự trữ của hồ bơi đầu tiên, và (a2, b2) là dự trữ của hồ bơi thứ hai. Phí là số phí được hồ bơi thu, và được giả định là như nhau cho cả hai hồ bơi (0.3% hầu hết thời gian).

Chúng tôi xác định một hàm tính toán đầu ra của một giao dịch swap, với đầu vào là x và các dự trữ (a, b):

f(x, a, b) = b (1 - a/(a + x(1-fee)))

Chúng tôi sau đó biết rằng đầu ra của lần trao đổi đầu tiên là:

out1(x) = f(x, a1, b1)

out1(x) = b1 (1 - a1/(a1 + x(1-fee)))

Kết quả của lần trao đổi thứ hai là: (lưu ý các biến dự trữ đã được hoán đổi)

out2(x) = f(out1(x), b2, a2)

out2(x) = f(f(x, a1, b1), b2, a2)

out2(x) = a2 (1 - b2/(b2 + f(x, a1, b1)(1-fee)))

out2(x) = a2 (1 - b2/(b2 + b1) (1 - a1/(a1 + x (1-fee))) (1-fee)))

Chúng ta có thể vẽ biểu đồ hàm số này bằng cách desmos. Bằng cách chọn các giá trị dự trữ sao cho chúng ta mô phỏng cặp tiền đầu tiên có 1 ETH và 1750 USDC, và cặp tiền thứ hai có 1340 USDC và 1 ETH, chúng tôi có được đồ thị sau:

Biểu đồ lợi nhuận gộp của giao dịch như một hàm của giá trị đầu vào

Lưu ý rằng chúng tôi đã thực sự vẽ đồ thị out2(x) - x, đó là lợi nhuận của giao dịch, trừ đi số tiền đầu vào.

Đồ họa, chúng ta có thể thấy kích thước giao dịch tối ưu nằm ở mức 0.0607 ETH đầu vào, tạo ra lợi nhuận là 0.0085 ETH. Hợp đồng phải có ít nhất 0.0607 ETH thanh khoản trong WETH để có thể tận dụng cơ hội này.

Giá trị lợi nhuận này của 0.0085 ETH (~$16 khi viết bài này) KHÔNG phải là lợi nhuận cuối cùng của giao dịch, vì chúng ta vẫn cần tính đến chi phí gas của giao dịch. Điều này sẽ được thảo luận trong một bài viết tiếp theo.

Chúng tôi muốn tự động tính toán kích thước giao dịch tối ưu này cho bot MEV của chúng tôi. Điều này có thể được thực hiện thông qua phép tính phổ cập. Chúng tôi có một hàm của một biến x mà chúng tôi muốn tối đa hóa. Hàm đạt giá trị lớn nhất của nó cho một giá trị của x mà đạo hàm của hàm đó bằng 0.

Các công cụ miễn phí và trực tuyến khác nhau có thể được sử dụng để tính nguyên hàm của một hàm một cách biểu tượng, chẳng hạn như wolfram alpha.

Tìm đạo hàm của hàm lợi nhuận gộp của chúng tôi.

Việc tìm một đạo hàm như vậy rất đơn giản với Wolfram Alpha. Bạn cũng có thể làm điều đó bằng tay nếu bạn không chắc chắn về kỹ năng toán học của mình.

Wolfram Alpha đưa ra đạo hàm sau:

dout2(x)/dx = (a1b1a2b2(1-fee)^2)/(a1b2 + (1-fee)x(b1(1-fee)+b2))^2

Vì chúng ta muốn tìm giá trị của x mà làm cho lợi nhuận cực đại (đó là out2(x) - x), chúng ta cần tìm giá trị của x mà đạo hàm là 1 (và không phải là 0).

Wolfram Alpha đưa ra giải pháp sau cho x trong phương trình dout2(x)/dx = 1:

x = (sqrt(a1b1a2b2(1-phí)^4 (b1(1-fee)+b2)^2) - a1b2(1-fee)(b1(1-fee)+b2)) / ((1-fee) (b1(1-fee) + b2))^2

Với các giá trị của các nguồn dự trữ mà chúng tôi đã sử dụng trong biểu đồ ở trên, chúng tôi có x_optimal = 0.0607203782551, điều này chứng minh công thức của chúng tôi là đúng (so với giá trị trên biểu đồ là 0.0607).

Mặc dù công thức này không đọc được lắm, nhưng dễ dàng thực hiện trong mã. Đây là một sự thực hiện python của công thức để tính toán đầu ra của 2 lần trao đổi và kích thước giao dịch tối ưu:

# Các hàm trợ giúp cho việc tính kích thước giao dịch tối ưu# Đầu ra của một lần đổi trảdef swap_output(x, a, b, fee=0.003):trả lại b * (1 - a/(a + x*(1-fee)))# Lợi nhuận gộp của hai lần đổi trả liên tiếpdef trade_profit(x, reserves1, reserves2, fee=0.003): a1, b1 = reserves1a2, b2 = reserves2return swap_output(swap_output(x, a1, b1, fee), b2, a2, fee) - x# Số lượng đầu vào tối ưudef optimal_trade_size(reserves1, reserves2, fee=0.003):a1, b1 = reserves1a2, b2 = reserves2return (math.sqrt(a1*b1*a2*b2*(1-fee)**4 * (b1*(1-fee)+b2)**2) - a1*b2*(1-fee)*(b1*(1-fee)+b2)) / ((1-fee) * (b1*(1-fee) + b2))**2

Tìm cơ hội Arbitrage

Bây giờ chúng ta biết cách tính lợi nhuận gộp từ cơ hội giao dich chênh lệch giá giữa hai hồ bơi cho cùng một cặp token, chúng ta chỉ cần lặp lại tất cả các cặp token, và kiểm tra từng cặp hai cặp tất cả các hồ bơi có cùng một cặp token. Điều này sẽ cho chúng ta lợi nhuận gộp của tất cả các cơ hội giao dịch chênh lệch giá có thể có nằm trong phạm vi chiến lược của chúng ta.

Để ước lượng lợi nhuận ròng của một giao dịch, chúng ta cần ước lượng chi phí gas để khai thác một cơ hội cụ thể. Điều này có thể được thực hiện một cách chính xác bằng cách mô phỏng giao dịch thông qua eth_call đến một nút RPC, nhưng mất rất nhiều thời gian và chỉ có thể thực hiện cho một vài chục cơ hội mỗi khối.

Chúng tôi sẽ đầu tiên thực hiện một ước lượng tổng cộng về chi phí gas bằng cách giả định một chi phí gas giao dịch cố định (một giới hạn dưới, thực ra), và loại bỏ những cơ hội không đủ lợi nhuận để bù đắp chi phí gas. Chỉ sau đó chúng tôi sẽ thực hiện một ước lượng chính xác về chi phí gas cho những cơ hội còn lại.

Đây là mã mà đi qua tất cả các cặp và tất cả các hồ, và sắp xếp các cơ hội theo lợi nhuận:

# [...] # Tìm nạp trữ lượng của mỗi nhóm trong pool_dictto_fetch = [] # Danh sách các địa chỉ nhóm mà dự trữ cần được tìm nạp.for cặp, pool_list trong pool_dict.items():for pair_object trong pool_list: to_fetch.append(pair_object["pair"]) # Thêm địa chỉ của poolprint(f"Fetching reserve of {len(to_fetch)} pools...")# getReservesParallel() là từ bài viết 2 trong loạt bot MEVRESERVEList = asyncio.get_event_loop().run_until_complete(getReservesParallel(to_fetch,  providersAsync))# Xây dựng danh sách các cơ hội giao dịchindex = 0opps = []cho cặp, pool_list trong pool_dict.items():# Lưu trữ dự trữ trong các đối tượng pool để sử dụng sau này pair_object trong pool_list: pair_object["reserves"] = reserveList[index] index += 1# Lặp lại trên tất cả các pool của pairfor poolA trong pool_list: đối với poolB trong pool_list: # Bỏ qua nếu đó là cùng một pool nếu poolA["pair"] == poolB["pair"]:            continue # Bỏ qua nếu một trong các dự trữ là 0 (chia cho 0) nếu 0 trong poolA["reserves"] hoặc 0 trong poolB["reserves"]: tiếp tục # Sắp xếp lại các khoản dự trữ để WETH luôn là token đầu tiên nếu poolA["token0"] == WETH: res_A = (poolA["reserves"][0], poolA["reserves"][1]) res_B = (poolB["reserves"][0], poolB["reserves"][1]) khác: res_A = (poolA["reserves"][1],  poolA["reserves"][0]) res_B = (poolB["reserves"][1], poolB["reserves"][0]) # Tính giá trị đầu vào tối ưu thông qua công thức x = optimal_trade_size(res_A, res_B) # Bỏ qua nếu đầu vào tối ưu là âm (thứ tự của các pool bị đảo ngược) nếu x < 0: tiếp tục # Tính lợi nhuận gộp bằng Wei (trước chi phí gas) lợi nhuận = trade_profit(x,  res_A, res_B) # Lưu trữ chi tiết về cơ hội. Các giá trị nằm trong ETH. (1e18 Wei = 1 ETH) opps.append({ "profit": profit / 1e18, "input": x / 1e18, "pair": pair, "poolA": poolA, "poolB": poolB, })print(f"Found {len(opps)} opportunities.")

Điều này tạo ra đầu ra sau:

Đang lấy dữ trữ của 3081 hồ bơi.

Tìm thấy 1791 cơ hội.

Chúng tôi hiện đã có một danh sách tất cả các cơ hội. Chúng tôi chỉ cần ước lượng lợi nhuận của họ. Hiện tại, chúng tôi sẽ đơn giản giả sử một chi phí gas không đổi cho việc giao dịch trên một cơ hội.

Chúng ta phải sử dụng một ngưỡng dưới cho chi phí gas của một giao dịch trên Uniswap V2. Thực nghiệm, chúng tôi đã phát hiện rằng giá trị này gần với 43k gas.

Tận dụng cơ hội yêu cầu 2 lần trao đổi, và thực hiện giao dịch trên Ethereum tốn phí cố định là 21k gas, tổng cộng 107k gas cho mỗi cơ hội.

Đây là mã tính toán lợi nhuận ròng ước lượng của mỗi cơ hội:

# [...]# Sử dụng chi phí gas cứng là 107k gas mỗi cơ hội gp = w3.eth.gas_pricefor opp in opps:opp["lợi_nhuận_net"] = opp["lợi_nhuận"] - 107000 * gp / 1e18# Sắp xếp theo ước lượng lợi nhuận netopps.sort(key=lambda x: x["lợi_nhuận_net"], reverse=True)# Giữ các cơ hội tích cựcpositive_opps = [opp for opp in opps if opp["lợi_nhuận_net"] > 0]

In bảng thống kê

# Cơ hội tích cực đếmprint(f"Đã tìm thấy {len(positive_opps)} cơ hội tích cực.")# Chi tiết về mỗi cơ hội ETH_PRICE = 1900 # Bạn nên động thời lấy giá của ETHcho opp trong positive_opps:print(f"Lợi nhuận: {opp['net_profit']} ETH (${opp['net_profit'] * ETH_PRICE})")print(f"Đầu vào: {opp['input']} ETH (${opp['input'] * ETH_PRICE})")print(f"Gói A: {opp['poolA']['pair']}")print(f"Gói B: {opp['poolB']['pair']}")print()

Đây là kết quả của kịch bản:

Tìm thấy 57 cơ hội tích cực.

Lợi nhuận: 4.936025725859028 ETH ($9378.448879132153)

Input: 1.7958289984719014 ETH ($3412.075097096613)

Nhóm A: 0x1498bd576454159Bb81B5Ce532692a8752D163e8

Pool B: 0x7D7E813082eF6c143277c71786e5bE626ec77b20

{‘profit’: 4.9374642090282865, ‘input’: 1.7958(...)

Lợi nhuận: 4.756587769768892 ETH ($9037.516762560894)

Input: 0.32908348765283796 ETH ($625.2586265403921)

Nhóm A: 0x486c1609f9605fA14C28E311b7D708B0541cd2f5

Pool B: 0x5e81b946b61F3C7F73Bf84dd961dE3A0A78E8c33

{‘profit’: 4.7580262529381505, ‘input’: 0.329(…)

Lợi nhuận: 0.8147203063054365 ETH ($1547.9685819803292)

Input: 0.6715171730669338 ETH ($1275.8826288271744)

Pool A: 0x1f1B4836Dde1859e2edE1C6155140318EF5931C2

Pool B: 0x1f7efDcD748F43Fc4BeAe6897e5a6DDd865DcceA

{‘profit’: 0.8161587894746954, ‘input’: 0.671(…)

(...)

Lợi nhuận cao đáng ngờ. Bước đầu tiên cần thực hiện là xác minh rằng mã là chính xác. Sau khi kiểm tra mã một cách cẩn thận, chúng tôi đã phát hiện rằng mã là chính xác.

Những lợi nhuận này có thực không? Nhưng dường như không. Chúng tôi đã tung lưới quá rộng khi chọn lựa xem xét các hồ bơi nào trong chiến lược của chúng tôi, và đã bắt được những hồ bơi chứa các token độc hại.

Tiêu chuẩn mã token ERC20 chỉ mô tả một giao diện cho tính tương tác. Bất kỳ ai cũng có thể triển khai một token mà thực hiện giao diện này, và chọn cách thực hiện hành vi không theo khuôn phép, đó chính là điều đang diễn ra ở đây.

Một số nhà tạo token thiết kế ERC20 của họ sao cho các pool mà họ được giao dịch không thể bán, mà chỉ có thể mua token. Một số hợp đồng token thậm chí còn có cơ chế kill-switches cho phép người tạo có thể rút tiền tất cả người dùng của nó.

Trong bot MEV của chúng tôi, những token độc hại này phải được lọc ra. Điều này sẽ được đề cập trong một bài viết trong tương lai.

Nếu chúng ta loại bỏ thủ công các token có hại một cách rõ ràng, chúng ta sẽ còn lại 42 cơ hội sau đây:

Lợi nhuận: 0.004126583158496902 ETH ($7.840508001144114)

Input: 0.008369804833786892 ETH ($15.902629184195094)

Nhóm A: 0xdF42388059692150d0A9De836E4171c7B9c09CBf

Pool B: 0xf98fCEB2DC0Fa2B3f32ABccc5e8495E961370B23

{‘profit’: 0.005565066327755902, (...)

Lợi nhuận: 0.004092580415474992 ETH ($7.775902789402485)

Input: 0.014696360216108083 ETH ($27.92308441060536)

Pool A: 0xfDBFb4239935A15C2C348400570E34De3b044c5F

Pool B: 0x0F15d69a7E5998252ccC39Ad239Cef67fa2a9369

{‘lợi nhuận’: 0.005531063584733992, (...)

Lợi nhuận: 0.003693235163284344 ETH ($7.017146810240254)

Input: 0.1392339178514088 ETH ($264.5444439176767)

Nhóm A: 0x2957215d0473d2c811A075725Da3C31D2af075F1

Pool B: 0xF110783EbD020DCFBA91Cd1976b79a6E510846AA

{‘lợi nhuận’: 0.005131718332543344, (...)

Lợi nhuận: 0.003674128918827048 ETH ($6.980844945771391)

Input: 0.2719041848570484 ETH ($516.617951228392)

Nhóm A: 0xBa19343ff3E9f496F17C7333cdeeD212D65A8425

Pool B: 0xD30567f1d084f411572f202ebb13261CE9F46325

{‘lợi nhuận’: 0.005112612088086048, (...)

(...)

Chú ý rằng nói chung lợi nhuận thấp hơn số tiền cần thiết để thực hiện giao dịch.

Lợi nhuận này khá hợp lý hơn nhiều. Nhưng hãy nhớ rằng họ vẫn là lợi nhuận trong trường hợp tốt nhất, vì chúng tôi đã sử dụng một ước lượng rất thô sơ về chi phí gas của mỗi cơ hội.

Trong một bài viết tương lai, chúng tôi sẽ mô phỏng việc thực hiện giao dịch của chúng tôi để có được một giá trị chính xác về chi phí khí gas của mỗi cơ hội.

Để mô phỏng việc thực hiện, chúng ta cần phát triển hợp đồng thông minh sẽ thực hiện giao dịch. Đây là chủ đề của bài viết tiếp theo.

Kết luận

Chúng tôi hiện có một định nghĩa rõ ràng về phạm vi của bot cơ hội giao dịch MEV của chúng tôi.

Chúng tôi đã khám phá lý thuyết toán học đằng sau chiến lược cơ hội thương mại, và đã triển khai nó trong Python.

Chúng tôi hiện có một danh sách các cơ hội cơ động tiềm năng, và chúng tôi cần mô phỏng việc thực hiện chúng để có được giá trị lợi nhuận cuối cùng. Để làm điều đó, chúng tôi cần có hợp đồng giao dịch thông minh của mình sẵn sàng.

Trong bài viết tiếp theo, chúng tôi sẽ phát triển một hợp đồng thông minh như vậy trong Solidity, và mô phỏng giao dịch arbitrages đầu tiên của chúng tôi.

Bạn có thể tìm mã hoàn chỉnh trong thư viện github liên kết với bài viết nàyKịch bản hoạt động tốt nhất khi chạy trên sổ ghi chú Jupyter.

Miễn trách nhiệm:

  1. Bài viết này được tái bản từ [Gatetrung bình], Tất cả bản quyền thuộc về tác giả gốc [Emile Amajar]. Tiêu đề bài viết gốc “Xây dựng một con bot thương mại: Tìm cơ hội thương mại (bài 3/n)”, Nếu có ý kiến phản đối về việc sao chép này, vui lòng liên hệ với Gate Họcteam, và họ sẽ xử lý ngay lập tức.
  2. Bản phủ nhận trách nhiệm: Quan điểm và ý kiến được biểu đạt trong bài viết này chỉ thuộc về tác giả và không đại diện cho bất kỳ lời khuyên đầu tư nào.
  3. Các bản dịch của bài viết sang các ngôn ngữ khác được thực hiện bởi nhóm Gate Learn. Trừ khi được nêu rõ, việc sao chép, phân phối hoặc đạo văn các bài viết dịch là không được phép.
เริ่มตอนนี้
สมัครและรับรางวัล
$100