ถ้าการตั้งค่า MEV ของคุณไม่เหมือนนี้ คุณกำลังหมดทาง
บทความนี้เป็นส่วนหนึ่งของชุดบทความเกี่ยวกับการสร้างบอตอาร์บิทราจ จุดมุ่งหมายของชุดบทความนี้คือการให้คำแนะนำขั้นตอนตามขั้นตอนในการสร้างหุ่นยนต์การซื้อขาย MEV อัตโนมัติซึ่งสามารถค้นหาและดำเนินการเออบิทราจบนตลาดแบบไม่มีกฎเกณฑ์ที่นิยม
ในบทความนี้ เราจะดำเนินการเลือกตัวเลือกก่อนหน้าของคู่โทเค็นที่น่าสนใจ จากนั้นเราจะนำสูตรทางคณิตศาสตร์ในการค้นหาการอาร์บิเทรจที่เหมาะที่สุดระหว่างสองพูลของคู่โทเค็นเดียวกัน ในที่สุด เราจะนำสูตรมาใช้ในรหัสและส่งกลับรายการของโอกาสการอาร์บิเทรจที่เป็นไปได้
ก่อนที่เราจะเริ่มมองหาโอกาสทางการตรวจสอบราคา ที่เราต้องกำหนดขอบเขตของบอทการตรวจสอบราคาของเราอย่างชัดเจน โดยเฉพาะอย่างยิ่งว่าประเภทของการตรวจสอบราคาที่เราต้องการดำเนินการ การตรวจสอบราคาชนิดที่ปลอดภัยที่สุดคือระหว่างพูลที่เกี่ยวข้องกับ ETH โดยเนื่องจาก ETH เป็นสินทรัพย์ที่ใช้จ่ายเรื่องก๊าซในธุรกรรมของเรา มันเป็นเรื่องธรรมชาติที่เราต้องการจะจบลงด้วย ETH เสมอหลังจากการตรวจสอบราคา แต่ทุกคนก็จะกระตือรือร้นที่จะคิดอย่างนี้ จำไว้ว่าในการซื้อขาย โอกาสแบบตรงเวลาน้อยลงเรื่อย ๆ ตามที่มีคนกระทำตามมัน
เพื่อความง่าย เราจะมุ่งเน้นที่โอกาสในการอาร์บิเทรจที่เกี่ยวข้องกับ ETH เราจะมองหาโอกาสเฉพาะระหว่างสองพูลของคู่เหรียญเดียวกันเท่านั้น เราจะไม่ซื้อขายในโอกาสที่เกี่ยวข้องกับพูลมากกว่า 2 ในเส้นทางการซื้อขาย (โอกาส multi-hop) โปรดทราบว่าการอัปเกรดกลยุทธ์นี้ให้เป็นกลยุทธ์ที่เสี่ยงกว่าคือขั้นตอนแรกที่คุณควรทำเพื่อเพิ่มผลกำไรของบอทของคุณ
เพื่อปรับปรุงกลยุทธ์นี้ คุณสามารถเก็บสินค้าบางส่วนใน stablecoins และกระทำตามโอกาส arbitrage ที่ให้ผลตอบแทนในรูปแบบ stablecoins ได้ เช่นกัน สิ่งเดียวกันสามารถทำได้กับสินทรัพย์ที่เสี่ยงมากมาย เช่น shitcoins (โดยทำการระวังตามที่จำเป็น) และสม่ำเสมอปรับนโยบายการลงทุนของคุณเข้า ETH เพื่อชำระค่า gas
ทิศทางอื่น ๆ คือการละทิ้งการสมมติฐานที่ถือว่ามีความแข็งแกร่งที่เราทำไว้ และนำเสนอตัวอย่างทางสถิติในกลยุทธ์ของเรา ตัวอย่างเช่น โดยการซื้อโทเค็นหนึ่งในสระเวลาที่ราคาเคลื่อนไหวไปในทิศทางที่ดีกว่ามากกว่าปริมาณของการแจกแจงมาตรฐานบางส่วน และขายมันภายหลัง (กลยุทธ์การกลับสู่ค่าเฉลี่ย) นี่จะเป็นสิ่งที่เหมาะสมสำหรับ shitcoins ที่ไม่ได้รับการจดทะเบียนบนตลาดให้มีประสิทธิภาพมากกว่า และอีกอย่างคือ ราคาที่ไม่ได้ติดตามอย่างถูกต้องบน blockchain นี้เกี่ยวข้องกับส่วนที่เคลื่อนไหวมากมายมากขึ้นและอยู่นอกขอบเขตของซีรีส์นี้
ตอนนี้เราได้กำหนดขอบเขตของบอท Arbitrage ของเราแล้ว ตอนนี้เราต้องเลือกคู่โทเค็นที่เราต้องการเทรด นี่คือเกณฑ์การเลือก 2 อันที่เราจะใช้
การใช้โค้ดจาก บทความ 2: การอ่านราคาสระว่ายน้ำอย่างมีประสิทธิภาพ, เรามีรหัสต่อไปนี้ซึ่งรายการทุกคู่โทเค็นที่ถูกวางไว้โดยสัญญาโรงงานที่ให้มา:
# [...]# โหลดที่อยู่ของสัญญาโรงงานด้วย open("FactoriesV2.json", "r") as f:factories = json.load(f)# [...]# เรียกข้อมูลรายการสระว่ายน้ำสำหรับแต่ละสัญญาโรงงานpairDataList = []for factoryName, factoryData in factories.items():events = getPairEvents(w3.eth.contract(address=factoryData['factory'], abi=factory_abi), 0, w3.eth.block_number)print(f'พบ {len(events)} สระว่ายน้ำสำหรับ {factoryName}')for e in events: pairDataList.append({ "token0": e["args"]["token0"], "token1": e["args"]["token1"], "pair": e["args"]["pair"], "factory": factoryName })
เราจะเพียงแค่กลับค่า pairDataList เป็นพจนานุกรมที่มี key เป็น token pairs และ value เป็นรายการของ pools ที่ซื้อขายคู่นี้ เมื่อวน loop ผ่านรายการ เราจะละเว้นคู่ที่ไม่เกี่ยวข้องกับ ETH เมื่อ loop สิ้นสุด คู่ที่มี pools อย่างน้อย 2 แหล่งจะถูกเลือกไว้ในรายการที่มีอย่างน้อย 2 องค์ประกอบ:
# [...]WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"pair_pool_dict = {}for pair_object in pairDataList:# Check for ETH (WETH) in the pair.pair = (pair_object['token0'], pair_object['token1'])if WETH not in pair: continue# Make sure the pair is referenced in the dictionary. if pair not in pair_pool_dict: pair_pool_dict[pair] = []# Add the pool to the list of pools that trade this pair.pair_pool_dict[pair].append(pair_object)# Create the final dictionnary of pools that will be traded on.pool_dict = {}for pair, pool_list in pair_pool_dict.items():if len(pool_list) >= 2: pool_dict[pair] = pool_list
บางสถิติควรถูกพิมพ์เพื่อมีการควบคุมที่ดีกับข้อมูลที่เรากำลังทำงาน
# จำนวนคู่ที่แตกต่างกันprint(f'เรามี {len(pool_dict)} คู่ที่แตกต่างกัน')# จำนวนรวมของสระว่ายน้ำprint(f'เรามี {sum([len(pool_list) for pool_list in pool_dict.values()])} สระว่ายน้ำทั้งหมด')# คู่ที่มีสระว่ายน้ำมากที่สุดprint(f'คู่ที่มีสระว่ายน้ำมากที่สุดคือ {max(pool_dict, key=lambda k: len(pool_dict[k]))} มี {len(max(pool_dict.values(), key=len))} สระว่ายน้ำ')# การกระจายของจำนวนสระว่ายน้ำต่อคู่ เป็นเดซิล pool_count_list = [len(pool_list) for pool_list in pool_dict.values()]pool_count_list.sort(reverse=True)print(f'จำนวนสระว่ายน้ำต่อคู่, เป็นเดซิล: {pool_count_list[::int(len(pool_count_list)/10)]}')# การกระจายของจำนวนสระว่ายน้ำต่อคู่ เป็นเพอร์เซ็นไทล์ (เดซิลของเดซิลแรก)pool_count_list.sort(reverse=True)print(f'จำนวนสระว่ายน้ำต่อคู่, เป็นเพอร์เซ็นไทล์: {pool_count_list[::int(len(pool_count_list)/100)][:10]}')
ขณะที่เขียนอยู่ ส่งออกต่อไปนี้
เรามีคู่ที่แตกต่างกัน 1431 คู่
เรามีพูลทั้งหมด 3081 พูล
คู่ที่มีสระว่ายน้ำมากที่สุดคือ ('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', '0xdAC17F958D2ee523a2206206994597C13D831ec7') มี 16 สระว่ายน้ำ
จำนวนของพูลต่อคู่ ในระดับทศนิยม: [16, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
จำนวนของสระว่ายน้ำต่อคู่ ในเปอร์เซ็นไทล์: [16, 5, 4, 3, 3, 3, 3, 3, 3, 3]
การเรียกข้อมูลสำรองสำหรับ 3000 สระว่ายน้ำสามารถทำได้ในเวลาน้อยกว่า 1 วินาทีด้วยโหนด RPC สาธารณะ นี่คือเวลาที่เหมาะสม
ตอนนี้เรามีข้อมูลทั้งหมดที่เราต้องการแล้ว ต้องเริ่มการค้นหาโอกาสในการทำอาร์บิทราช
มีโอกาสในการล้วงกระแสเมื่อมีความไม่สมดุลในราคาระหว่างสองพูลที่ซื้อขายคู่เดียวกัน อย่างไรก็ตาม ความแตกต่างในราคาไม่ใช่ทุกกรณีที่สามารถใช้ประโยชน์ได้: ค่าแก๊สของธุรกรรมกำหนดค่าขั้นต่ำที่ต้องได้รับกลับมาจากการซื้อขายและสภาพ Likuidity ในแต่ละ พูล จำกัด ค่าที่สามารถถอดได้จากความแตกต่างในราคาที่โดยประมาณ
เพื่อหาโอกาส Arbitrage ที่มีกำไรมากที่สุดที่สามารถเข้าถึงได้ เราจะต้องคำนวณมูลค่าที่สามารถสกัดได้จากความแตกต่างของราคาแต่ละราคา โดยพิจารณาเก็บสำรอง/ Likuiditi ในแต่ละสระ และประมาณค่า Gas ของธุรกรรม
เมื่อมีโอกาสในการอาร์บิเทรจ ราคาของพูลที่ซื้อโทเค็นนำเข้าจะลดลง และราคาของพูลที่ขายจะเพิ่มขึ้น การเคลื่อนไหวของราคาถูกอธิบายโดยสูตรผลคูณคงที่
เราเห็นแล้วใน@emileamajar/building-an-arbitrage-bot-automated-market-makers-and-uniswap-2d208215d8c2">article 1 how to compute the output of a swap through a pool, given the reserves of that pool and the input amount.
เพื่อหาขนาดการซื้อขายที่เหมาะสม เราจะเริ่มจากการหาสูตรสำหรับผลลัพธ์ของการแลกเปลี่ยนสองครั้งติดต่อกัน โดยให้ปริมาณนำเข้าบางส่วน และสำรองของสองสระที่เกี่ยวข้องในการแลกเปลี่ยน
เราสมมติว่าอินพุตของสวาพแรกอยู่ในโทเค็น0 และอินพุตของสวาพที่สองอยู่ในโทเค็น1 ซึ่งในที่สุดได้ผลลัพธ์เป็นเหรียญโทเค็น0
Let x เป็นปริมาณข้อมูลนำเข้า (a1, b1) คงเหลือของสระว่ายน้ำแรก และ (a2, b2) คงเหลือของสระว่ายน้ำที่สอง ค่าธรรมเนียมคือค่าธรรมเนียมที่สระว่ายน้ำเรียกเก็บ และถือว่าเป็นเท่ากันสำหรับทั้งสองสระว่ายน้ำ (0.3% ตลอดเวลาส่วนใหญ่)
เรากำหนดฟังก์ชันที่คำนวณผลลัพธ์ของสวอพ โดยให้ข้อมูลนำเข้า x และสำรอง (a, b):
f(x, a, b) = b (1 - a/(a + x(1-fee)))
เราจึงทราบว่าผลลัพธ์ของการสลับครั้งแรกคือ:
out1(x) = f(x, a1, b1)
out1(x) = b1 (1 - a1/(a1 + x(1-fee)))
ผลลัพธ์ของสวอพครั้งที่สองคือ: (โปรดสังเกตตัวแปรสำรองที่สลับ)
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)))
เราสามารถพล็อตฟังก์ชันนี้โดยใช้desmos. โดยการเลือกค่าสำรองเพื่อจำลองพูลแรกที่มี 1 ETH และ 1750 USDC และพูลที่สองมี 1340 USDC และ 1 ETH เราจะได้กราฟต่อไปนี้:
กราฟกำไรขั้นต้นของการค้าเป็นฟังก์ชันของค่านำเข้า
โปรดทราบว่าเราได้วาดเส้นกราฟของ out2(x) - x ซึ่งเป็นกำไรจากการซื้อขาย ลบด้วยจำนวนเงินที่นำเข้า
กราฟฟิกแสดงให้เห็นว่าขนาดการซื้อขายที่เหมาะสมคือที่ 0.0607 ETH ของเงินทุนเข้า ซึ่งทำให้ได้กำไร 0.0085 ETH สัญญาต้องมีอย่างน้อย 0.0607 ETH ของ Likuidity ใน WETH เพื่อที่จะสามารถใช้โอกาสนี้ได้
ค่ากำไรนี้มูลค่า 0.0085 ETH (~$16 เมื่อเขียนบทความนี้) ไม่ใช่กำไรสุทธิของการเทรด โดยเรายังต้องคำนึงถึงค่า gas ของธุรกรรมด้วย สิ่งนี้จะถูกพูดถึงในบทความถัดไป
เราต้องการคำนวณขนาดซื้อขายที่เหมาะสมนี้โดยอัตโนมัติสำหรับบอท MEV ของเรา สามารถทำได้ผ่านการคณิตศาสตร์พื้นฐาน เรามีฟังก์ชันของตัวแปรเดียว x ที่เราต้องการเพิ่มขึ้น ฟังก์ชันนั้นมีค่าสูงสุดสำหรับค่า x ที่อนุพันธ์ของฟังก์ชันเป็น 0
เครื่องมือฟรีและออนไลน์ต่าง ๆ สามารถใช้คำนวณอนุพันธ์ของฟังก์ชันในรูปสัญลักษณ์ได้ เช่น wolfram alpha.
การหาอนุพันธ์ของฟังก์ชันกำไรขั้นต้นของเรา
การค้นหาผลลัพธ์ดัชนีเชิงอนุพันธ์นั้นง่ายมากๆ ด้วย Wolfram Alpha คุณ c็าทำได้ด้วยมือได้ถ้าคุณไม่มั่นใจในทักษะคณิตศาสตร์ของคุณ
Wolfram Alpha ได้ผลเดิมพันดังต่อไปนี้:
dout2(x)/dx = (a1b1a2b2(1-ค่าธรรมเนียม)^2)/(a1b2 + (1-fee)x(b1(1-fee)+b2))^2
เนื่องจากเราต้องการหาค่า x ที่ทำให้กำไรเยอะที่สุด (ซึ่งคือ out2(x) - x) เราต้องหาค่า x ที่อนุพันธ์เท่ากับ 1 (และไม่ใช่ 0)
Wolfram Alpha ให้ผลลัพธ์ต่อไปนี้สำหรับ x ในสมการ dout2(x)/dx = 1:
x = (sqrt(a1b1a2b2(1-fee)^4 (b1(1-fee)+b2)^2) - a1b2(1-fee)(b1(1-fee)+b2)) / ((1-fee) (b1(1-fee) + b2))^2
ด้วยค่าของสำรองที่เราใช้ในกราฟด้านบน เราได้ x_optimal = 0.0607203782551 ซึ่งยืนยันสูตรของเรา (เปรียบเทียบกับค่ากราฟของ 0.0607)
แม้ว่าสูตรนี้จะไม่อ่านง่ายมาก แต่ง่ายต่อการนำมาใช้ในรหัส นี่คือการนำสูตร python มาประยุกต์ใช้เพื่อคำนวณผลลัพธ์ของการสวิทช์ 2 และขนาดการซื้อขายที่เหมาะสม
# ฟังก์ชั่นตัวช่วยสําหรับการคํานวณขนาดการซื้อขายที่เหมาะสมที่สุด # เอาต์พุตของ swapdef เดียว swap_output(x, a, b, fee=0.003):return b * (1 - a/(a + x*(1-fee)))# กําไรขั้นต้นของ swapsdef สองตัวติดต่อกัน trade_profit(x, reserves1, reserves2, fee=0.003): a1, b1 = reserves1a2, b2 = reserves2return swap_output(swap_output(x, a1, b1, ค่าธรรมเนียม), b2, a2, ค่าธรรมเนียม) - x# จํานวนอินพุตที่เหมาะสม def 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
ตอนนี้ที่เราทราบวิธีการคำนวณกำไรขั้นต้นจากโอกาสทางการค้าระหว่างสองพูลใดๆของคู่สกุลเหรียญเดียวกันเราเพียงต้องทำการลูปผ่านคู่สกุลเหรียญทั้งหมดและทดสอบทีละคู่สองสองกับทุกพูลที่มีคู่สกุลเหรียญเดียวกัน สิ่งนี้จะให้เราทราบกำไรขั้นต้นของโอกาสทางการค้าที่เป็นไปได้ทั้งหมดภายในขอบเขตของกลยุทธ์ของเรา
เพื่อประมาณกำไรสุทธิของการเทรด เราต้องประมาณค่า gas ในการใช้โอกาสที่กำหนดไว้ สามารถทำได้โดยเจาะจงโดยการจำลองธุรกรรมผ่าน eth_call ไปยังโหนด RPC แต่ใช้เวลามากและสามารถทำได้เฉพาะสำหรับโอกาสไม่กี่โอกาสต่อบล็อก
เราจะทำการประมาณการค่าใช้จ่ายในการใช้ก๊าซในขั้นต้นโดยการสมมติค่าใช้จ่ายในการทำธุรกรรมที่คงที่ (ที่ต่ำกว่าจริง), และตัดสิทธิ์ของโอกาสที่ไม่ได้กำไรมากพอที่จะปิดค่าใช้จ่ายในการใช้ก๊าซ จากนั้นเราจะทำการประมาณการค่าใช้จ่ายในการใช้ก๊าซอย่างแม่นยำสำหรับโอกาสที่เหลือ
นี่คือโค้ดที่ผ่านไปทุกคู่และทุกพูลและเรียงโอกาสทั้งหมดตามกำไร:
# [...] # ดึงเงินสํารองของแต่ละพูลใน pool_dictto_fetch = [] # รายการที่อยู่พูลที่ต้องดึงข้อมูลสํารองสําหรับคู่ pool_list ใน pool_dict.items():for pair_object in pool_list: to_fetch.append(pair_object["คู่"]) # เพิ่มที่อยู่ของ poolprint(f"Fetching reserves of {len(to_fetch)} pools...")# getReservesParallel() มาจากบทความ 2 ใน MEV bot seriesreserveList = asyncio.get_event_loop().run_until_complete(getReservesParallel(to_fetch, providersAsync))# สร้างรายการโอกาสในการซื้อขาย index = 0opps = []สําหรับคู่ pool_list ใน pool_dict.items():# เก็บเงินสํารองไว้ในออบเจ็กต์พูลเพื่อใช้ในภายหลังสําหรับ pair_object ใน pool_list: pair_object["reserves"] = reserveList[index] index += 1# ทําซ้ําพูลทั้งหมดของ pairfor poolA ใน pool_list: สําหรับ poolB ใน pool_list: # ข้ามถ้าเป็นพูลเดียวกันถ้า poolA["pair"] == poolB["pair"]: ดําเนินการต่อ # ข้ามถ้าหนึ่งในทุนสํารองเป็น 0 (หารด้วย 0) ถ้า 0 ใน poolA["reserves"] หรือ 0 ใน poolB["reserves"]: ดําเนินการต่อ # จัดลําดับเงินสํารองใหม่เพื่อให้ WETH เป็นโทเค็นแรกเสมอหาก poolA["token0"] == WETH: res_A = (poolA["reserves"][0], poolA["reserves"][1]) res_B = (poolB["reserves"][0], poolB["reserves"][1]) else: res_A = (poolA["reserves"][1], poolA["สํารอง"][0]) res_B = (poolB["reserves"][1], poolB["reserves"][0]) # คํานวณค่าอินพุตที่เหมาะสมผ่านสูตร x = optimal_trade_size(res_A, res_B) # ข้ามถ้าอินพุตที่เหมาะสมเป็นลบ (ลําดับของพูลกลับด้าน) ถ้า x < 0: ดําเนินการต่อ # คํานวณกําไรขั้นต้นใน Wei (ก่อนต้นทุนก๊าซ) กําไร = trade_profit(x, res_A, res_B) # รายละเอียดร้านค้าของโอกาส ค่าอยู่ใน ETH (1e18 Wei = 1 ETH) opps.append({ "กําไร": กําไร / 1e18, "อินพุต": x / 1e18, "คู่": คู่, "poolA": poolA, "poolB": poolB, })print(f"พบ {len(opps)} โอกาส")
ซึ่งก่อให้เกิดผลลัพธ์ต่อไปนี้:
การเรียกข้อมูลสำรองของ 3081 สระว่ายน้ำ
พบโอกาส 1791 โอกาส
เรามีรายการของโอกาสทั้งหมดแล้ว ขณะนี้เราต้องประเมินกำไรของพวกเขาเท่านั้น ขณะนี้เราจะสมมติว่าค่าก๊าซในการซื้อขายในโอกาสเป็นคงที่
เราต้องใช้ lower bound สำหรับค่า gas ของการสลับบน Uniswap V2 ทดลองดูแล้วพบว่าค่านี้เป็นประมาณ 43k gas
การใช้โอกาสต้องการการแลกเปลี่ยน 2 ครั้ง และการดำเนินการทางธุรกรรมบน Ethereum มีค่าใช้จ่ายแบบคงที่ 21k gas สำหรับรวมเป็น 107k gas ต่อโอกาส
นี่คือรหัสที่คำนวณกำไรสุทธิโดยประมาณของแต่ละโอกาส:
# [...]# ใช้ค่า gas โค้ดแฮร์ดแคสต์ของ 107k gas ต่อโอกาส gp = w3.eth.gas_pricefor opp in opps:opp["net_profit"] = opp["profit"] - 107000 * gp / 1e18# เรียงลำดับตามกำไรสุทธิโดยประมาณopps.sort(key=lambda x: x["net_profit"], reverse=True)# รักษาโอกาสบวกpositive_opps = [opp for opp in opps if opp["net_profit"] > 0]
# โอกาสที่เชิงบวก countprint(f"พบ {len(positive_opps)} โอกาสเชิงบวก")# รายละเอียดเกี่ยวกับแต่ละโอกาส ETH_PRICE = 1900 # คุณควรดึงราคา ETH ไดนามิกสำหรับ opp in positive_opps:print(f"กำไร: {opp['net_profit']} ETH (${opp['net_profit'] * ETH_PRICE})")print(f"อินพุท: {opp['input']} ETH (${opp['input'] * ETH_PRICE})")print(f"พูล A: {opp['poolA']['pair']}")print(f"พูล B: {opp['poolB']['pair']}")print()
นี่คือผลลัพธ์ของสคริปต์:
พบโอกาสบวก 57 โอกาส
กำไร: 4.936025725859028 ETH ($9378.448879132153)
Input: 1.7958289984719014 ETH ($3412.075097096613)
Pool A: 0x1498bd576454159Bb81B5Ce532692a8752D163e8
Pool B: 0x7D7E813082eF6c143277c71786e5bE626ec77b20
{‘profit’: 4.9374642090282865, ‘input’: 1.7958(…)
กำไร: 4.756587769768892 ETH ($9037.516762560894)
Input: 0.32908348765283796 ETH ($625.2586265403921)
Pool A: 0x486c1609f9605fA14C28E311b7D708B0541cd2f5
Pool B: 0x5e81b946b61F3C7F73Bf84dd961dE3A0A78E8c33
{‘profit’: 4.7580262529381505, ‘input’: 0.329(…)
กำไร: 0.8147203063054365 ETH ($1547.9685819803292)
Input: 0.6715171730669338 ETH ($1275.8826288271744)
Pool A: 0x1f1B4836Dde1859e2edE1C6155140318EF5931C2
Pool B: 0x1f7efDcD748F43Fc4BeAe6897e5a6DDd865DcceA
{‘profit’: 0.8161587894746954, ‘input’: 0.671(…)
(…)
ซึ่งมีกำไรสูงอย่างน่าสงสัย เป็นขั้นตอนแรกที่ควรทำคือการตรวจสอบว่าโค้ดถูกต้องหรือไม่ หลังจากตรวจสอบโค้ดอย่างระมัดระวัง เราพบว่าโค้ดถูกต้อง
พวกกำไรเหล่านี้จริงหรือไม่? พบว่าไม่ เมื่อเราเลือกว่าจะพิจารณากลุ่มพูลในกลยุทธ์ของเราเกินไป และได้เข้าไปในกลุ่มพูลของโทเซียส
มาตรฐานโทเค็น ERC20 กล่าวถึงอินเทอร์เฟซสำหรับการทำงานร่วมกันเท่านั้น ผู้ใดก็สามารถจัดการโทเค็นที่ใช้อินเทอร์เฟซนี้และเลือกที่จะใช้พฤติกรรมที่ไม่เป็นทางการ ซึ่งเป็นสิ่งที่เกิดขึ้นในที่นี้
บางผู้สร้างโทเค็นออกแบบ ERC20 ของตนให้สระที่ใช้เทรดไม่สามารถขายได้ แต่เฉพาะการซื้อโทเค็น บางสัญญาโทเค็นยังมีกลไก kill-switch ที่อนุญาตให้ผู้สร้าง pull ผู้ใช้ทั้งหมดของมัน
ในบอท MEV ของเรา ต้องกรองโทเซนพิษเหล่านี้ออก นี้จะถูกพูดถึงในบทความในอนาคต
หากเรากรองออกด้วยมือโดยเฉพาะโทเค็นที่เป็นพิษอย่างชัดเจนเราจะเหลือ 42 โอกาสต่อไปนี้:
กำไร: 0.004126583158496902 ETH ($7.840508001144114)
Input: 0.008369804833786892 ETH ($15.902629184195094)
Pool A: 0xdF42388059692150d0A9De836E4171c7B9c09CBf
Pool B: 0xf98fCEB2DC0Fa2B3f32ABccc5e8495E961370B23
{‘profit’: 0.005565066327755902, (…)
กำไร: 0.004092580415474992 ETH ($7.775902789402485)
Input: 0.014696360216108083 ETH ($27.92308441060536)
Pool A: 0xfDBFb4239935A15C2C348400570E34De3b044c5F
Pool B: 0x0F15d69a7E5998252ccC39Ad239Cef67fa2a9369
{‘กำไร’: 0.005531063584733992, (…)
กำไร: 0.003693235163284344 ETH ($7.017146810240254)
Input: 0.1392339178514088 ETH ($264.5444439176767)
Pool A: 0x2957215d0473d2c811A075725Da3C31D2af075F1
Pool B: 0xF110783EbD020DCFBA91Cd1976b79a6E510846AA
{‘profit’: 0.005131718332543344, (...)
กำไร: 0.003674128918827048 ETH ($6.980844945771391)
Input: 0.2719041848570484 ETH ($516.617951228392)
Pool A: 0xBa19343ff3E9f496F17C7333cdeeD212D65A8425
Pool B: 0xD30567f1d084f411572f202ebb13261CE9F46325
{‘profit’: 0.005112612088086048, (…)
(…)
โปรดทราบว่าโดยทั่วไปกำไรจะต่ำกว่าจำนวนเงินที่จำเป็นสำหรับการดำเนินการธุรกรรม
กำไรเหล่านี้มีความเหมาะสมมากขึ้น แต่จำไว้ว่าเหล่านั้นยังคงเป็นกำไรจากสถานการณ์ที่ดีที่สุดเท่านั้น เนื่องจากเราได้ใช้การประมาณต้นทุนแก๊สที่อยู่ในระดับพื้นฐานมาก
ในบทความในอนาคต เราจะจำลองการดำเนินการซื้อขายของเราเพื่อที่จะได้ค่าที่แม่นยำของค่าใช้จ่ายในการใช้ก๊าสของแต่ละโอกาส
เพื่อจำลองการดำเนินการ เราต้องพัฒนาสัญญาฉลาดที่จะดำเนินการซื้อขายก่อน นี่คือหัวข้อของบทความถัดไป
เรามีคำจำกัดความชัดเจนของขอบเขตของบอท MEV arbitrage ของเราตอนนี้
เราได้สำรวจทฤษฎีคณิตศาสตร์ของกลยุทธ์อาร์บิเทรจและได้นำมันมาปฏิบัติใช้ใน Python
เรามีรายการโอกาสอาร์บิเทรจที่เป็นไปได้แล้ว และเราต้องการจำลองการดำเนินการของพวกเขาเพื่อให้ได้ค่ากำไรสุดท้าย โดยที่เราต้องมีสัญญาฉลากการซื้อขายของเราพร้อม
ในบทความถัดไป เราจะพัฒนาสัญญาอัจฉริยะแบบนี้ใน Solidity และจำลองการซื้อขายอะบิทราจครั้งแรกของเรา
คุณสามารถค้นหาโค้ดทั้งหมดใน GitHub repo ที่เกี่ยวข้องกับบทความนี้. สคริปต์ดีที่สุดในการเรียกใช้ใน Jupyter notebook
ถ้าการตั้งค่า MEV ของคุณไม่เหมือนนี้ คุณกำลังหมดทาง
บทความนี้เป็นส่วนหนึ่งของชุดบทความเกี่ยวกับการสร้างบอตอาร์บิทราจ จุดมุ่งหมายของชุดบทความนี้คือการให้คำแนะนำขั้นตอนตามขั้นตอนในการสร้างหุ่นยนต์การซื้อขาย MEV อัตโนมัติซึ่งสามารถค้นหาและดำเนินการเออบิทราจบนตลาดแบบไม่มีกฎเกณฑ์ที่นิยม
ในบทความนี้ เราจะดำเนินการเลือกตัวเลือกก่อนหน้าของคู่โทเค็นที่น่าสนใจ จากนั้นเราจะนำสูตรทางคณิตศาสตร์ในการค้นหาการอาร์บิเทรจที่เหมาะที่สุดระหว่างสองพูลของคู่โทเค็นเดียวกัน ในที่สุด เราจะนำสูตรมาใช้ในรหัสและส่งกลับรายการของโอกาสการอาร์บิเทรจที่เป็นไปได้
ก่อนที่เราจะเริ่มมองหาโอกาสทางการตรวจสอบราคา ที่เราต้องกำหนดขอบเขตของบอทการตรวจสอบราคาของเราอย่างชัดเจน โดยเฉพาะอย่างยิ่งว่าประเภทของการตรวจสอบราคาที่เราต้องการดำเนินการ การตรวจสอบราคาชนิดที่ปลอดภัยที่สุดคือระหว่างพูลที่เกี่ยวข้องกับ ETH โดยเนื่องจาก ETH เป็นสินทรัพย์ที่ใช้จ่ายเรื่องก๊าซในธุรกรรมของเรา มันเป็นเรื่องธรรมชาติที่เราต้องการจะจบลงด้วย ETH เสมอหลังจากการตรวจสอบราคา แต่ทุกคนก็จะกระตือรือร้นที่จะคิดอย่างนี้ จำไว้ว่าในการซื้อขาย โอกาสแบบตรงเวลาน้อยลงเรื่อย ๆ ตามที่มีคนกระทำตามมัน
เพื่อความง่าย เราจะมุ่งเน้นที่โอกาสในการอาร์บิเทรจที่เกี่ยวข้องกับ ETH เราจะมองหาโอกาสเฉพาะระหว่างสองพูลของคู่เหรียญเดียวกันเท่านั้น เราจะไม่ซื้อขายในโอกาสที่เกี่ยวข้องกับพูลมากกว่า 2 ในเส้นทางการซื้อขาย (โอกาส multi-hop) โปรดทราบว่าการอัปเกรดกลยุทธ์นี้ให้เป็นกลยุทธ์ที่เสี่ยงกว่าคือขั้นตอนแรกที่คุณควรทำเพื่อเพิ่มผลกำไรของบอทของคุณ
เพื่อปรับปรุงกลยุทธ์นี้ คุณสามารถเก็บสินค้าบางส่วนใน stablecoins และกระทำตามโอกาส arbitrage ที่ให้ผลตอบแทนในรูปแบบ stablecoins ได้ เช่นกัน สิ่งเดียวกันสามารถทำได้กับสินทรัพย์ที่เสี่ยงมากมาย เช่น shitcoins (โดยทำการระวังตามที่จำเป็น) และสม่ำเสมอปรับนโยบายการลงทุนของคุณเข้า ETH เพื่อชำระค่า gas
ทิศทางอื่น ๆ คือการละทิ้งการสมมติฐานที่ถือว่ามีความแข็งแกร่งที่เราทำไว้ และนำเสนอตัวอย่างทางสถิติในกลยุทธ์ของเรา ตัวอย่างเช่น โดยการซื้อโทเค็นหนึ่งในสระเวลาที่ราคาเคลื่อนไหวไปในทิศทางที่ดีกว่ามากกว่าปริมาณของการแจกแจงมาตรฐานบางส่วน และขายมันภายหลัง (กลยุทธ์การกลับสู่ค่าเฉลี่ย) นี่จะเป็นสิ่งที่เหมาะสมสำหรับ shitcoins ที่ไม่ได้รับการจดทะเบียนบนตลาดให้มีประสิทธิภาพมากกว่า และอีกอย่างคือ ราคาที่ไม่ได้ติดตามอย่างถูกต้องบน blockchain นี้เกี่ยวข้องกับส่วนที่เคลื่อนไหวมากมายมากขึ้นและอยู่นอกขอบเขตของซีรีส์นี้
ตอนนี้เราได้กำหนดขอบเขตของบอท Arbitrage ของเราแล้ว ตอนนี้เราต้องเลือกคู่โทเค็นที่เราต้องการเทรด นี่คือเกณฑ์การเลือก 2 อันที่เราจะใช้
การใช้โค้ดจาก บทความ 2: การอ่านราคาสระว่ายน้ำอย่างมีประสิทธิภาพ, เรามีรหัสต่อไปนี้ซึ่งรายการทุกคู่โทเค็นที่ถูกวางไว้โดยสัญญาโรงงานที่ให้มา:
# [...]# โหลดที่อยู่ของสัญญาโรงงานด้วย open("FactoriesV2.json", "r") as f:factories = json.load(f)# [...]# เรียกข้อมูลรายการสระว่ายน้ำสำหรับแต่ละสัญญาโรงงานpairDataList = []for factoryName, factoryData in factories.items():events = getPairEvents(w3.eth.contract(address=factoryData['factory'], abi=factory_abi), 0, w3.eth.block_number)print(f'พบ {len(events)} สระว่ายน้ำสำหรับ {factoryName}')for e in events: pairDataList.append({ "token0": e["args"]["token0"], "token1": e["args"]["token1"], "pair": e["args"]["pair"], "factory": factoryName })
เราจะเพียงแค่กลับค่า pairDataList เป็นพจนานุกรมที่มี key เป็น token pairs และ value เป็นรายการของ pools ที่ซื้อขายคู่นี้ เมื่อวน loop ผ่านรายการ เราจะละเว้นคู่ที่ไม่เกี่ยวข้องกับ ETH เมื่อ loop สิ้นสุด คู่ที่มี pools อย่างน้อย 2 แหล่งจะถูกเลือกไว้ในรายการที่มีอย่างน้อย 2 องค์ประกอบ:
# [...]WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"pair_pool_dict = {}for pair_object in pairDataList:# Check for ETH (WETH) in the pair.pair = (pair_object['token0'], pair_object['token1'])if WETH not in pair: continue# Make sure the pair is referenced in the dictionary. if pair not in pair_pool_dict: pair_pool_dict[pair] = []# Add the pool to the list of pools that trade this pair.pair_pool_dict[pair].append(pair_object)# Create the final dictionnary of pools that will be traded on.pool_dict = {}for pair, pool_list in pair_pool_dict.items():if len(pool_list) >= 2: pool_dict[pair] = pool_list
บางสถิติควรถูกพิมพ์เพื่อมีการควบคุมที่ดีกับข้อมูลที่เรากำลังทำงาน
# จำนวนคู่ที่แตกต่างกันprint(f'เรามี {len(pool_dict)} คู่ที่แตกต่างกัน')# จำนวนรวมของสระว่ายน้ำprint(f'เรามี {sum([len(pool_list) for pool_list in pool_dict.values()])} สระว่ายน้ำทั้งหมด')# คู่ที่มีสระว่ายน้ำมากที่สุดprint(f'คู่ที่มีสระว่ายน้ำมากที่สุดคือ {max(pool_dict, key=lambda k: len(pool_dict[k]))} มี {len(max(pool_dict.values(), key=len))} สระว่ายน้ำ')# การกระจายของจำนวนสระว่ายน้ำต่อคู่ เป็นเดซิล pool_count_list = [len(pool_list) for pool_list in pool_dict.values()]pool_count_list.sort(reverse=True)print(f'จำนวนสระว่ายน้ำต่อคู่, เป็นเดซิล: {pool_count_list[::int(len(pool_count_list)/10)]}')# การกระจายของจำนวนสระว่ายน้ำต่อคู่ เป็นเพอร์เซ็นไทล์ (เดซิลของเดซิลแรก)pool_count_list.sort(reverse=True)print(f'จำนวนสระว่ายน้ำต่อคู่, เป็นเพอร์เซ็นไทล์: {pool_count_list[::int(len(pool_count_list)/100)][:10]}')
ขณะที่เขียนอยู่ ส่งออกต่อไปนี้
เรามีคู่ที่แตกต่างกัน 1431 คู่
เรามีพูลทั้งหมด 3081 พูล
คู่ที่มีสระว่ายน้ำมากที่สุดคือ ('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', '0xdAC17F958D2ee523a2206206994597C13D831ec7') มี 16 สระว่ายน้ำ
จำนวนของพูลต่อคู่ ในระดับทศนิยม: [16, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
จำนวนของสระว่ายน้ำต่อคู่ ในเปอร์เซ็นไทล์: [16, 5, 4, 3, 3, 3, 3, 3, 3, 3]
การเรียกข้อมูลสำรองสำหรับ 3000 สระว่ายน้ำสามารถทำได้ในเวลาน้อยกว่า 1 วินาทีด้วยโหนด RPC สาธารณะ นี่คือเวลาที่เหมาะสม
ตอนนี้เรามีข้อมูลทั้งหมดที่เราต้องการแล้ว ต้องเริ่มการค้นหาโอกาสในการทำอาร์บิทราช
มีโอกาสในการล้วงกระแสเมื่อมีความไม่สมดุลในราคาระหว่างสองพูลที่ซื้อขายคู่เดียวกัน อย่างไรก็ตาม ความแตกต่างในราคาไม่ใช่ทุกกรณีที่สามารถใช้ประโยชน์ได้: ค่าแก๊สของธุรกรรมกำหนดค่าขั้นต่ำที่ต้องได้รับกลับมาจากการซื้อขายและสภาพ Likuidity ในแต่ละ พูล จำกัด ค่าที่สามารถถอดได้จากความแตกต่างในราคาที่โดยประมาณ
เพื่อหาโอกาส Arbitrage ที่มีกำไรมากที่สุดที่สามารถเข้าถึงได้ เราจะต้องคำนวณมูลค่าที่สามารถสกัดได้จากความแตกต่างของราคาแต่ละราคา โดยพิจารณาเก็บสำรอง/ Likuiditi ในแต่ละสระ และประมาณค่า Gas ของธุรกรรม
เมื่อมีโอกาสในการอาร์บิเทรจ ราคาของพูลที่ซื้อโทเค็นนำเข้าจะลดลง และราคาของพูลที่ขายจะเพิ่มขึ้น การเคลื่อนไหวของราคาถูกอธิบายโดยสูตรผลคูณคงที่
เราเห็นแล้วใน@emileamajar/building-an-arbitrage-bot-automated-market-makers-and-uniswap-2d208215d8c2">article 1 how to compute the output of a swap through a pool, given the reserves of that pool and the input amount.
เพื่อหาขนาดการซื้อขายที่เหมาะสม เราจะเริ่มจากการหาสูตรสำหรับผลลัพธ์ของการแลกเปลี่ยนสองครั้งติดต่อกัน โดยให้ปริมาณนำเข้าบางส่วน และสำรองของสองสระที่เกี่ยวข้องในการแลกเปลี่ยน
เราสมมติว่าอินพุตของสวาพแรกอยู่ในโทเค็น0 และอินพุตของสวาพที่สองอยู่ในโทเค็น1 ซึ่งในที่สุดได้ผลลัพธ์เป็นเหรียญโทเค็น0
Let x เป็นปริมาณข้อมูลนำเข้า (a1, b1) คงเหลือของสระว่ายน้ำแรก และ (a2, b2) คงเหลือของสระว่ายน้ำที่สอง ค่าธรรมเนียมคือค่าธรรมเนียมที่สระว่ายน้ำเรียกเก็บ และถือว่าเป็นเท่ากันสำหรับทั้งสองสระว่ายน้ำ (0.3% ตลอดเวลาส่วนใหญ่)
เรากำหนดฟังก์ชันที่คำนวณผลลัพธ์ของสวอพ โดยให้ข้อมูลนำเข้า x และสำรอง (a, b):
f(x, a, b) = b (1 - a/(a + x(1-fee)))
เราจึงทราบว่าผลลัพธ์ของการสลับครั้งแรกคือ:
out1(x) = f(x, a1, b1)
out1(x) = b1 (1 - a1/(a1 + x(1-fee)))
ผลลัพธ์ของสวอพครั้งที่สองคือ: (โปรดสังเกตตัวแปรสำรองที่สลับ)
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)))
เราสามารถพล็อตฟังก์ชันนี้โดยใช้desmos. โดยการเลือกค่าสำรองเพื่อจำลองพูลแรกที่มี 1 ETH และ 1750 USDC และพูลที่สองมี 1340 USDC และ 1 ETH เราจะได้กราฟต่อไปนี้:
กราฟกำไรขั้นต้นของการค้าเป็นฟังก์ชันของค่านำเข้า
โปรดทราบว่าเราได้วาดเส้นกราฟของ out2(x) - x ซึ่งเป็นกำไรจากการซื้อขาย ลบด้วยจำนวนเงินที่นำเข้า
กราฟฟิกแสดงให้เห็นว่าขนาดการซื้อขายที่เหมาะสมคือที่ 0.0607 ETH ของเงินทุนเข้า ซึ่งทำให้ได้กำไร 0.0085 ETH สัญญาต้องมีอย่างน้อย 0.0607 ETH ของ Likuidity ใน WETH เพื่อที่จะสามารถใช้โอกาสนี้ได้
ค่ากำไรนี้มูลค่า 0.0085 ETH (~$16 เมื่อเขียนบทความนี้) ไม่ใช่กำไรสุทธิของการเทรด โดยเรายังต้องคำนึงถึงค่า gas ของธุรกรรมด้วย สิ่งนี้จะถูกพูดถึงในบทความถัดไป
เราต้องการคำนวณขนาดซื้อขายที่เหมาะสมนี้โดยอัตโนมัติสำหรับบอท MEV ของเรา สามารถทำได้ผ่านการคณิตศาสตร์พื้นฐาน เรามีฟังก์ชันของตัวแปรเดียว x ที่เราต้องการเพิ่มขึ้น ฟังก์ชันนั้นมีค่าสูงสุดสำหรับค่า x ที่อนุพันธ์ของฟังก์ชันเป็น 0
เครื่องมือฟรีและออนไลน์ต่าง ๆ สามารถใช้คำนวณอนุพันธ์ของฟังก์ชันในรูปสัญลักษณ์ได้ เช่น wolfram alpha.
การหาอนุพันธ์ของฟังก์ชันกำไรขั้นต้นของเรา
การค้นหาผลลัพธ์ดัชนีเชิงอนุพันธ์นั้นง่ายมากๆ ด้วย Wolfram Alpha คุณ c็าทำได้ด้วยมือได้ถ้าคุณไม่มั่นใจในทักษะคณิตศาสตร์ของคุณ
Wolfram Alpha ได้ผลเดิมพันดังต่อไปนี้:
dout2(x)/dx = (a1b1a2b2(1-ค่าธรรมเนียม)^2)/(a1b2 + (1-fee)x(b1(1-fee)+b2))^2
เนื่องจากเราต้องการหาค่า x ที่ทำให้กำไรเยอะที่สุด (ซึ่งคือ out2(x) - x) เราต้องหาค่า x ที่อนุพันธ์เท่ากับ 1 (และไม่ใช่ 0)
Wolfram Alpha ให้ผลลัพธ์ต่อไปนี้สำหรับ x ในสมการ dout2(x)/dx = 1:
x = (sqrt(a1b1a2b2(1-fee)^4 (b1(1-fee)+b2)^2) - a1b2(1-fee)(b1(1-fee)+b2)) / ((1-fee) (b1(1-fee) + b2))^2
ด้วยค่าของสำรองที่เราใช้ในกราฟด้านบน เราได้ x_optimal = 0.0607203782551 ซึ่งยืนยันสูตรของเรา (เปรียบเทียบกับค่ากราฟของ 0.0607)
แม้ว่าสูตรนี้จะไม่อ่านง่ายมาก แต่ง่ายต่อการนำมาใช้ในรหัส นี่คือการนำสูตร python มาประยุกต์ใช้เพื่อคำนวณผลลัพธ์ของการสวิทช์ 2 และขนาดการซื้อขายที่เหมาะสม
# ฟังก์ชั่นตัวช่วยสําหรับการคํานวณขนาดการซื้อขายที่เหมาะสมที่สุด # เอาต์พุตของ swapdef เดียว swap_output(x, a, b, fee=0.003):return b * (1 - a/(a + x*(1-fee)))# กําไรขั้นต้นของ swapsdef สองตัวติดต่อกัน trade_profit(x, reserves1, reserves2, fee=0.003): a1, b1 = reserves1a2, b2 = reserves2return swap_output(swap_output(x, a1, b1, ค่าธรรมเนียม), b2, a2, ค่าธรรมเนียม) - x# จํานวนอินพุตที่เหมาะสม def 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
ตอนนี้ที่เราทราบวิธีการคำนวณกำไรขั้นต้นจากโอกาสทางการค้าระหว่างสองพูลใดๆของคู่สกุลเหรียญเดียวกันเราเพียงต้องทำการลูปผ่านคู่สกุลเหรียญทั้งหมดและทดสอบทีละคู่สองสองกับทุกพูลที่มีคู่สกุลเหรียญเดียวกัน สิ่งนี้จะให้เราทราบกำไรขั้นต้นของโอกาสทางการค้าที่เป็นไปได้ทั้งหมดภายในขอบเขตของกลยุทธ์ของเรา
เพื่อประมาณกำไรสุทธิของการเทรด เราต้องประมาณค่า gas ในการใช้โอกาสที่กำหนดไว้ สามารถทำได้โดยเจาะจงโดยการจำลองธุรกรรมผ่าน eth_call ไปยังโหนด RPC แต่ใช้เวลามากและสามารถทำได้เฉพาะสำหรับโอกาสไม่กี่โอกาสต่อบล็อก
เราจะทำการประมาณการค่าใช้จ่ายในการใช้ก๊าซในขั้นต้นโดยการสมมติค่าใช้จ่ายในการทำธุรกรรมที่คงที่ (ที่ต่ำกว่าจริง), และตัดสิทธิ์ของโอกาสที่ไม่ได้กำไรมากพอที่จะปิดค่าใช้จ่ายในการใช้ก๊าซ จากนั้นเราจะทำการประมาณการค่าใช้จ่ายในการใช้ก๊าซอย่างแม่นยำสำหรับโอกาสที่เหลือ
นี่คือโค้ดที่ผ่านไปทุกคู่และทุกพูลและเรียงโอกาสทั้งหมดตามกำไร:
# [...] # ดึงเงินสํารองของแต่ละพูลใน pool_dictto_fetch = [] # รายการที่อยู่พูลที่ต้องดึงข้อมูลสํารองสําหรับคู่ pool_list ใน pool_dict.items():for pair_object in pool_list: to_fetch.append(pair_object["คู่"]) # เพิ่มที่อยู่ของ poolprint(f"Fetching reserves of {len(to_fetch)} pools...")# getReservesParallel() มาจากบทความ 2 ใน MEV bot seriesreserveList = asyncio.get_event_loop().run_until_complete(getReservesParallel(to_fetch, providersAsync))# สร้างรายการโอกาสในการซื้อขาย index = 0opps = []สําหรับคู่ pool_list ใน pool_dict.items():# เก็บเงินสํารองไว้ในออบเจ็กต์พูลเพื่อใช้ในภายหลังสําหรับ pair_object ใน pool_list: pair_object["reserves"] = reserveList[index] index += 1# ทําซ้ําพูลทั้งหมดของ pairfor poolA ใน pool_list: สําหรับ poolB ใน pool_list: # ข้ามถ้าเป็นพูลเดียวกันถ้า poolA["pair"] == poolB["pair"]: ดําเนินการต่อ # ข้ามถ้าหนึ่งในทุนสํารองเป็น 0 (หารด้วย 0) ถ้า 0 ใน poolA["reserves"] หรือ 0 ใน poolB["reserves"]: ดําเนินการต่อ # จัดลําดับเงินสํารองใหม่เพื่อให้ WETH เป็นโทเค็นแรกเสมอหาก poolA["token0"] == WETH: res_A = (poolA["reserves"][0], poolA["reserves"][1]) res_B = (poolB["reserves"][0], poolB["reserves"][1]) else: res_A = (poolA["reserves"][1], poolA["สํารอง"][0]) res_B = (poolB["reserves"][1], poolB["reserves"][0]) # คํานวณค่าอินพุตที่เหมาะสมผ่านสูตร x = optimal_trade_size(res_A, res_B) # ข้ามถ้าอินพุตที่เหมาะสมเป็นลบ (ลําดับของพูลกลับด้าน) ถ้า x < 0: ดําเนินการต่อ # คํานวณกําไรขั้นต้นใน Wei (ก่อนต้นทุนก๊าซ) กําไร = trade_profit(x, res_A, res_B) # รายละเอียดร้านค้าของโอกาส ค่าอยู่ใน ETH (1e18 Wei = 1 ETH) opps.append({ "กําไร": กําไร / 1e18, "อินพุต": x / 1e18, "คู่": คู่, "poolA": poolA, "poolB": poolB, })print(f"พบ {len(opps)} โอกาส")
ซึ่งก่อให้เกิดผลลัพธ์ต่อไปนี้:
การเรียกข้อมูลสำรองของ 3081 สระว่ายน้ำ
พบโอกาส 1791 โอกาส
เรามีรายการของโอกาสทั้งหมดแล้ว ขณะนี้เราต้องประเมินกำไรของพวกเขาเท่านั้น ขณะนี้เราจะสมมติว่าค่าก๊าซในการซื้อขายในโอกาสเป็นคงที่
เราต้องใช้ lower bound สำหรับค่า gas ของการสลับบน Uniswap V2 ทดลองดูแล้วพบว่าค่านี้เป็นประมาณ 43k gas
การใช้โอกาสต้องการการแลกเปลี่ยน 2 ครั้ง และการดำเนินการทางธุรกรรมบน Ethereum มีค่าใช้จ่ายแบบคงที่ 21k gas สำหรับรวมเป็น 107k gas ต่อโอกาส
นี่คือรหัสที่คำนวณกำไรสุทธิโดยประมาณของแต่ละโอกาส:
# [...]# ใช้ค่า gas โค้ดแฮร์ดแคสต์ของ 107k gas ต่อโอกาส gp = w3.eth.gas_pricefor opp in opps:opp["net_profit"] = opp["profit"] - 107000 * gp / 1e18# เรียงลำดับตามกำไรสุทธิโดยประมาณopps.sort(key=lambda x: x["net_profit"], reverse=True)# รักษาโอกาสบวกpositive_opps = [opp for opp in opps if opp["net_profit"] > 0]
# โอกาสที่เชิงบวก countprint(f"พบ {len(positive_opps)} โอกาสเชิงบวก")# รายละเอียดเกี่ยวกับแต่ละโอกาส ETH_PRICE = 1900 # คุณควรดึงราคา ETH ไดนามิกสำหรับ opp in positive_opps:print(f"กำไร: {opp['net_profit']} ETH (${opp['net_profit'] * ETH_PRICE})")print(f"อินพุท: {opp['input']} ETH (${opp['input'] * ETH_PRICE})")print(f"พูล A: {opp['poolA']['pair']}")print(f"พูล B: {opp['poolB']['pair']}")print()
นี่คือผลลัพธ์ของสคริปต์:
พบโอกาสบวก 57 โอกาส
กำไร: 4.936025725859028 ETH ($9378.448879132153)
Input: 1.7958289984719014 ETH ($3412.075097096613)
Pool A: 0x1498bd576454159Bb81B5Ce532692a8752D163e8
Pool B: 0x7D7E813082eF6c143277c71786e5bE626ec77b20
{‘profit’: 4.9374642090282865, ‘input’: 1.7958(…)
กำไร: 4.756587769768892 ETH ($9037.516762560894)
Input: 0.32908348765283796 ETH ($625.2586265403921)
Pool A: 0x486c1609f9605fA14C28E311b7D708B0541cd2f5
Pool B: 0x5e81b946b61F3C7F73Bf84dd961dE3A0A78E8c33
{‘profit’: 4.7580262529381505, ‘input’: 0.329(…)
กำไร: 0.8147203063054365 ETH ($1547.9685819803292)
Input: 0.6715171730669338 ETH ($1275.8826288271744)
Pool A: 0x1f1B4836Dde1859e2edE1C6155140318EF5931C2
Pool B: 0x1f7efDcD748F43Fc4BeAe6897e5a6DDd865DcceA
{‘profit’: 0.8161587894746954, ‘input’: 0.671(…)
(…)
ซึ่งมีกำไรสูงอย่างน่าสงสัย เป็นขั้นตอนแรกที่ควรทำคือการตรวจสอบว่าโค้ดถูกต้องหรือไม่ หลังจากตรวจสอบโค้ดอย่างระมัดระวัง เราพบว่าโค้ดถูกต้อง
พวกกำไรเหล่านี้จริงหรือไม่? พบว่าไม่ เมื่อเราเลือกว่าจะพิจารณากลุ่มพูลในกลยุทธ์ของเราเกินไป และได้เข้าไปในกลุ่มพูลของโทเซียส
มาตรฐานโทเค็น ERC20 กล่าวถึงอินเทอร์เฟซสำหรับการทำงานร่วมกันเท่านั้น ผู้ใดก็สามารถจัดการโทเค็นที่ใช้อินเทอร์เฟซนี้และเลือกที่จะใช้พฤติกรรมที่ไม่เป็นทางการ ซึ่งเป็นสิ่งที่เกิดขึ้นในที่นี้
บางผู้สร้างโทเค็นออกแบบ ERC20 ของตนให้สระที่ใช้เทรดไม่สามารถขายได้ แต่เฉพาะการซื้อโทเค็น บางสัญญาโทเค็นยังมีกลไก kill-switch ที่อนุญาตให้ผู้สร้าง pull ผู้ใช้ทั้งหมดของมัน
ในบอท MEV ของเรา ต้องกรองโทเซนพิษเหล่านี้ออก นี้จะถูกพูดถึงในบทความในอนาคต
หากเรากรองออกด้วยมือโดยเฉพาะโทเค็นที่เป็นพิษอย่างชัดเจนเราจะเหลือ 42 โอกาสต่อไปนี้:
กำไร: 0.004126583158496902 ETH ($7.840508001144114)
Input: 0.008369804833786892 ETH ($15.902629184195094)
Pool A: 0xdF42388059692150d0A9De836E4171c7B9c09CBf
Pool B: 0xf98fCEB2DC0Fa2B3f32ABccc5e8495E961370B23
{‘profit’: 0.005565066327755902, (…)
กำไร: 0.004092580415474992 ETH ($7.775902789402485)
Input: 0.014696360216108083 ETH ($27.92308441060536)
Pool A: 0xfDBFb4239935A15C2C348400570E34De3b044c5F
Pool B: 0x0F15d69a7E5998252ccC39Ad239Cef67fa2a9369
{‘กำไร’: 0.005531063584733992, (…)
กำไร: 0.003693235163284344 ETH ($7.017146810240254)
Input: 0.1392339178514088 ETH ($264.5444439176767)
Pool A: 0x2957215d0473d2c811A075725Da3C31D2af075F1
Pool B: 0xF110783EbD020DCFBA91Cd1976b79a6E510846AA
{‘profit’: 0.005131718332543344, (...)
กำไร: 0.003674128918827048 ETH ($6.980844945771391)
Input: 0.2719041848570484 ETH ($516.617951228392)
Pool A: 0xBa19343ff3E9f496F17C7333cdeeD212D65A8425
Pool B: 0xD30567f1d084f411572f202ebb13261CE9F46325
{‘profit’: 0.005112612088086048, (…)
(…)
โปรดทราบว่าโดยทั่วไปกำไรจะต่ำกว่าจำนวนเงินที่จำเป็นสำหรับการดำเนินการธุรกรรม
กำไรเหล่านี้มีความเหมาะสมมากขึ้น แต่จำไว้ว่าเหล่านั้นยังคงเป็นกำไรจากสถานการณ์ที่ดีที่สุดเท่านั้น เนื่องจากเราได้ใช้การประมาณต้นทุนแก๊สที่อยู่ในระดับพื้นฐานมาก
ในบทความในอนาคต เราจะจำลองการดำเนินการซื้อขายของเราเพื่อที่จะได้ค่าที่แม่นยำของค่าใช้จ่ายในการใช้ก๊าสของแต่ละโอกาส
เพื่อจำลองการดำเนินการ เราต้องพัฒนาสัญญาฉลาดที่จะดำเนินการซื้อขายก่อน นี่คือหัวข้อของบทความถัดไป
เรามีคำจำกัดความชัดเจนของขอบเขตของบอท MEV arbitrage ของเราตอนนี้
เราได้สำรวจทฤษฎีคณิตศาสตร์ของกลยุทธ์อาร์บิเทรจและได้นำมันมาปฏิบัติใช้ใน Python
เรามีรายการโอกาสอาร์บิเทรจที่เป็นไปได้แล้ว และเราต้องการจำลองการดำเนินการของพวกเขาเพื่อให้ได้ค่ากำไรสุดท้าย โดยที่เราต้องมีสัญญาฉลากการซื้อขายของเราพร้อม
ในบทความถัดไป เราจะพัฒนาสัญญาอัจฉริยะแบบนี้ใน Solidity และจำลองการซื้อขายอะบิทราจครั้งแรกของเรา
คุณสามารถค้นหาโค้ดทั้งหมดใน GitHub repo ที่เกี่ยวข้องกับบทความนี้. สคริปต์ดีที่สุดในการเรียกใช้ใน Jupyter notebook