สร้างบอตอาร์บิเทรจ: การค้นหาโอกาสทางอาร์บิเทรจ

ในบทความนี้ เราดำเนินการเลือกเซ็คชันที่พิจารณาในการทำการค้าคู่โทเคน จากนั้นเราจะได้สูตรคณิตศาสตร์สำหรับการค้นหาการอาร์บิเทรจที่เหมาะสมระหว่างสองพูลของคู่โทเคนเดียวกัน

ถ้าการตั้งค่า MEV ของคุณไม่เหมือนนี้ คุณกำลังหมดทาง

บทความนี้เป็นส่วนหนึ่งของชุดบทความเกี่ยวกับการสร้างบอตอาร์บิทราจ จุดมุ่งหมายของชุดบทความนี้คือการให้คำแนะนำขั้นตอนตามขั้นตอนในการสร้างหุ่นยนต์การซื้อขาย MEV อัตโนมัติซึ่งสามารถค้นหาและดำเนินการเออบิทราจบนตลาดแบบไม่มีกฎเกณฑ์ที่นิยม

ในบทความนี้ เราจะดำเนินการเลือกตัวเลือกก่อนหน้าของคู่โทเค็นที่น่าสนใจ จากนั้นเราจะนำสูตรทางคณิตศาสตร์ในการค้นหาการอาร์บิเทรจที่เหมาะที่สุดระหว่างสองพูลของคู่โทเค็นเดียวกัน ในที่สุด เราจะนำสูตรมาใช้ในรหัสและส่งกลับรายการของโอกาสการอาร์บิเทรจที่เป็นไปได้

การเลือกคู่โทเค็น

ความแม่นยำเกี่ยวกับกลยุทธ์อาร์บิเทรจ

ก่อนที่เราจะเริ่มมองหาโอกาสทางการตรวจสอบราคา ที่เราต้องกำหนดขอบเขตของบอทการตรวจสอบราคาของเราอย่างชัดเจน โดยเฉพาะอย่างยิ่งว่าประเภทของการตรวจสอบราคาที่เราต้องการดำเนินการ การตรวจสอบราคาชนิดที่ปลอดภัยที่สุดคือระหว่างพูลที่เกี่ยวข้องกับ ETH โดยเนื่องจาก ETH เป็นสินทรัพย์ที่ใช้จ่ายเรื่องก๊าซในธุรกรรมของเรา มันเป็นเรื่องธรรมชาติที่เราต้องการจะจบลงด้วย ETH เสมอหลังจากการตรวจสอบราคา แต่ทุกคนก็จะกระตือรือร้นที่จะคิดอย่างนี้ จำไว้ว่าในการซื้อขาย โอกาสแบบตรงเวลาน้อยลงเรื่อย ๆ ตามที่มีคนกระทำตามมัน

เพื่อความง่าย เราจะมุ่งเน้นที่โอกาสในการอาร์บิเทรจที่เกี่ยวข้องกับ ETH เราจะมองหาโอกาสเฉพาะระหว่างสองพูลของคู่เหรียญเดียวกันเท่านั้น เราจะไม่ซื้อขายในโอกาสที่เกี่ยวข้องกับพูลมากกว่า 2 ในเส้นทางการซื้อขาย (โอกาส multi-hop) โปรดทราบว่าการอัปเกรดกลยุทธ์นี้ให้เป็นกลยุทธ์ที่เสี่ยงกว่าคือขั้นตอนแรกที่คุณควรทำเพื่อเพิ่มผลกำไรของบอทของคุณ

เพื่อปรับปรุงกลยุทธ์นี้ คุณสามารถเก็บสินค้าบางส่วนใน stablecoins และกระทำตามโอกาส arbitrage ที่ให้ผลตอบแทนในรูปแบบ stablecoins ได้ เช่นกัน สิ่งเดียวกันสามารถทำได้กับสินทรัพย์ที่เสี่ยงมากมาย เช่น shitcoins (โดยทำการระวังตามที่จำเป็น) และสม่ำเสมอปรับนโยบายการลงทุนของคุณเข้า ETH เพื่อชำระค่า gas

ทิศทางอื่น ๆ คือการละทิ้งการสมมติฐานที่ถือว่ามีความแข็งแกร่งที่เราทำไว้ และนำเสนอตัวอย่างทางสถิติในกลยุทธ์ของเรา ตัวอย่างเช่น โดยการซื้อโทเค็นหนึ่งในสระเวลาที่ราคาเคลื่อนไหวไปในทิศทางที่ดีกว่ามากกว่าปริมาณของการแจกแจงมาตรฐานบางส่วน และขายมันภายหลัง (กลยุทธ์การกลับสู่ค่าเฉลี่ย) นี่จะเป็นสิ่งที่เหมาะสมสำหรับ shitcoins ที่ไม่ได้รับการจดทะเบียนบนตลาดให้มีประสิทธิภาพมากกว่า และอีกอย่างคือ ราคาที่ไม่ได้ติดตามอย่างถูกต้องบน blockchain นี้เกี่ยวข้องกับส่วนที่เคลื่อนไหวมากมายมากขึ้นและอยู่นอกขอบเขตของซีรีส์นี้

เลือกคู่โทเค็น

ตอนนี้เราได้กำหนดขอบเขตของบอท Arbitrage ของเราแล้ว ตอนนี้เราต้องเลือกคู่โทเค็นที่เราต้องการเทรด นี่คือเกณฑ์การเลือก 2 อันที่เราจะใช้

  • คู่ความสัมพันธ์ที่ถูกเลือกต้องเกี่ยวข้องกับ ETH
  • คู่ความต้องการถูกซื้อขายในสระว่ายน้ำอย่างน้อย 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

Arbitrage opportunity finder

ตอนนี้ที่เราทราบวิธีการคำนวณกำไรขั้นต้นจากโอกาสทางการค้าระหว่างสองพูลใดๆของคู่สกุลเหรียญเดียวกันเราเพียงต้องทำการลูปผ่านคู่สกุลเหรียญทั้งหมดและทดสอบทีละคู่สองสองกับทุกพูลที่มีคู่สกุลเหรียญเดียวกัน สิ่งนี้จะให้เราทราบกำไรขั้นต้นของโอกาสทางการค้าที่เป็นไปได้ทั้งหมดภายในขอบเขตของกลยุทธ์ของเรา

เพื่อประมาณกำไรสุทธิของการเทรด เราต้องประมาณค่า 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

คำปฏิเสธ:

  1. บทความนี้ถูกพิมพ์ใหม่จาก [ กลาง] ลิขสิทธิ์ทั้งหมดเป็นของผู้เขียนต้นฉบับ [Emile Amajar]. หัวข้อบทความต้นฉบับ”สร้างบอทอาร์บิทราจ: การค้นหาโอกาสอาร์บิทราจ (บทความ 3/n)”, หากมีข้อตรงข้อบกพระคุณติดต่อGate Learnทีม และพวกเขาจะจัดการกับมันโดยเร็ว
  2. คำโฆษณาความรับผิด: มุมมองและความคิดเห็นที่แสดงในบทความนี้เป็นเพียงของผู้เขียนเท่านั้น และไม่เป็นการให้คำแนะนำในการลงทุนใด ๆ
  3. การแปลบทความเป็นภาษาอื่น ๆ ทำโดยทีม Gate Learn หากไม่ได้กล่าวถึง การคัดลอก การแจกจ่าย หรือการลอกเลียนบทความที่ถูกแปล นั้นถือเป็นการละเมิด

สร้างบอตอาร์บิเทรจ: การค้นหาโอกาสทางอาร์บิเทรจ

กลาง4/9/2024, 2:29:22 PM
ในบทความนี้ เราดำเนินการเลือกเซ็คชันที่พิจารณาในการทำการค้าคู่โทเคน จากนั้นเราจะได้สูตรคณิตศาสตร์สำหรับการค้นหาการอาร์บิเทรจที่เหมาะสมระหว่างสองพูลของคู่โทเคนเดียวกัน

ถ้าการตั้งค่า MEV ของคุณไม่เหมือนนี้ คุณกำลังหมดทาง

บทความนี้เป็นส่วนหนึ่งของชุดบทความเกี่ยวกับการสร้างบอตอาร์บิทราจ จุดมุ่งหมายของชุดบทความนี้คือการให้คำแนะนำขั้นตอนตามขั้นตอนในการสร้างหุ่นยนต์การซื้อขาย MEV อัตโนมัติซึ่งสามารถค้นหาและดำเนินการเออบิทราจบนตลาดแบบไม่มีกฎเกณฑ์ที่นิยม

ในบทความนี้ เราจะดำเนินการเลือกตัวเลือกก่อนหน้าของคู่โทเค็นที่น่าสนใจ จากนั้นเราจะนำสูตรทางคณิตศาสตร์ในการค้นหาการอาร์บิเทรจที่เหมาะที่สุดระหว่างสองพูลของคู่โทเค็นเดียวกัน ในที่สุด เราจะนำสูตรมาใช้ในรหัสและส่งกลับรายการของโอกาสการอาร์บิเทรจที่เป็นไปได้

การเลือกคู่โทเค็น

ความแม่นยำเกี่ยวกับกลยุทธ์อาร์บิเทรจ

ก่อนที่เราจะเริ่มมองหาโอกาสทางการตรวจสอบราคา ที่เราต้องกำหนดขอบเขตของบอทการตรวจสอบราคาของเราอย่างชัดเจน โดยเฉพาะอย่างยิ่งว่าประเภทของการตรวจสอบราคาที่เราต้องการดำเนินการ การตรวจสอบราคาชนิดที่ปลอดภัยที่สุดคือระหว่างพูลที่เกี่ยวข้องกับ ETH โดยเนื่องจาก ETH เป็นสินทรัพย์ที่ใช้จ่ายเรื่องก๊าซในธุรกรรมของเรา มันเป็นเรื่องธรรมชาติที่เราต้องการจะจบลงด้วย ETH เสมอหลังจากการตรวจสอบราคา แต่ทุกคนก็จะกระตือรือร้นที่จะคิดอย่างนี้ จำไว้ว่าในการซื้อขาย โอกาสแบบตรงเวลาน้อยลงเรื่อย ๆ ตามที่มีคนกระทำตามมัน

เพื่อความง่าย เราจะมุ่งเน้นที่โอกาสในการอาร์บิเทรจที่เกี่ยวข้องกับ ETH เราจะมองหาโอกาสเฉพาะระหว่างสองพูลของคู่เหรียญเดียวกันเท่านั้น เราจะไม่ซื้อขายในโอกาสที่เกี่ยวข้องกับพูลมากกว่า 2 ในเส้นทางการซื้อขาย (โอกาส multi-hop) โปรดทราบว่าการอัปเกรดกลยุทธ์นี้ให้เป็นกลยุทธ์ที่เสี่ยงกว่าคือขั้นตอนแรกที่คุณควรทำเพื่อเพิ่มผลกำไรของบอทของคุณ

เพื่อปรับปรุงกลยุทธ์นี้ คุณสามารถเก็บสินค้าบางส่วนใน stablecoins และกระทำตามโอกาส arbitrage ที่ให้ผลตอบแทนในรูปแบบ stablecoins ได้ เช่นกัน สิ่งเดียวกันสามารถทำได้กับสินทรัพย์ที่เสี่ยงมากมาย เช่น shitcoins (โดยทำการระวังตามที่จำเป็น) และสม่ำเสมอปรับนโยบายการลงทุนของคุณเข้า ETH เพื่อชำระค่า gas

ทิศทางอื่น ๆ คือการละทิ้งการสมมติฐานที่ถือว่ามีความแข็งแกร่งที่เราทำไว้ และนำเสนอตัวอย่างทางสถิติในกลยุทธ์ของเรา ตัวอย่างเช่น โดยการซื้อโทเค็นหนึ่งในสระเวลาที่ราคาเคลื่อนไหวไปในทิศทางที่ดีกว่ามากกว่าปริมาณของการแจกแจงมาตรฐานบางส่วน และขายมันภายหลัง (กลยุทธ์การกลับสู่ค่าเฉลี่ย) นี่จะเป็นสิ่งที่เหมาะสมสำหรับ shitcoins ที่ไม่ได้รับการจดทะเบียนบนตลาดให้มีประสิทธิภาพมากกว่า และอีกอย่างคือ ราคาที่ไม่ได้ติดตามอย่างถูกต้องบน blockchain นี้เกี่ยวข้องกับส่วนที่เคลื่อนไหวมากมายมากขึ้นและอยู่นอกขอบเขตของซีรีส์นี้

เลือกคู่โทเค็น

ตอนนี้เราได้กำหนดขอบเขตของบอท Arbitrage ของเราแล้ว ตอนนี้เราต้องเลือกคู่โทเค็นที่เราต้องการเทรด นี่คือเกณฑ์การเลือก 2 อันที่เราจะใช้

  • คู่ความสัมพันธ์ที่ถูกเลือกต้องเกี่ยวข้องกับ ETH
  • คู่ความต้องการถูกซื้อขายในสระว่ายน้ำอย่างน้อย 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

Arbitrage opportunity finder

ตอนนี้ที่เราทราบวิธีการคำนวณกำไรขั้นต้นจากโอกาสทางการค้าระหว่างสองพูลใดๆของคู่สกุลเหรียญเดียวกันเราเพียงต้องทำการลูปผ่านคู่สกุลเหรียญทั้งหมดและทดสอบทีละคู่สองสองกับทุกพูลที่มีคู่สกุลเหรียญเดียวกัน สิ่งนี้จะให้เราทราบกำไรขั้นต้นของโอกาสทางการค้าที่เป็นไปได้ทั้งหมดภายในขอบเขตของกลยุทธ์ของเรา

เพื่อประมาณกำไรสุทธิของการเทรด เราต้องประมาณค่า 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

คำปฏิเสธ:

  1. บทความนี้ถูกพิมพ์ใหม่จาก [ กลาง] ลิขสิทธิ์ทั้งหมดเป็นของผู้เขียนต้นฉบับ [Emile Amajar]. หัวข้อบทความต้นฉบับ”สร้างบอทอาร์บิทราจ: การค้นหาโอกาสอาร์บิทราจ (บทความ 3/n)”, หากมีข้อตรงข้อบกพระคุณติดต่อGate Learnทีม และพวกเขาจะจัดการกับมันโดยเร็ว
  2. คำโฆษณาความรับผิด: มุมมองและความคิดเห็นที่แสดงในบทความนี้เป็นเพียงของผู้เขียนเท่านั้น และไม่เป็นการให้คำแนะนำในการลงทุนใด ๆ
  3. การแปลบทความเป็นภาษาอื่น ๆ ทำโดยทีม Gate Learn หากไม่ได้กล่าวถึง การคัดลอก การแจกจ่าย หรือการลอกเลียนบทความที่ถูกแปล นั้นถือเป็นการละเมิด
即刻開始交易
註冊並交易即可獲得
$100
和價值
$5500
理財體驗金獎勵!