Lanjut ke konten utama

Buat agen perdagangan AI Anda sendiri di Ethereum

AI
perdagangan
agen
python
Tingkat menengah
Ori Pomerantz
13 Februari 2026
22 bacaan singkat

Dalam tutorial ini Anda akan belajar cara membangun agen perdagangan AI sederhana. Agen ini bekerja menggunakan langkah-langkah berikut:

  1. Baca harga token saat ini dan yang lalu, serta informasi lain yang berpotensi relevan
  2. Buat kueri dengan informasi ini, beserta informasi latar belakang untuk menjelaskan bagaimana informasi tersebut mungkin relevan
  3. Kirimkan kueri dan terima kembali harga yang diproyeksikan
  4. Perdagangkan berdasarkan rekomendasi
  5. Tunggu dan ulangi

Agen ini menunjukkan cara membaca informasi, menerjemahkannya ke dalam kueri yang menghasilkan jawaban yang dapat digunakan, dan menggunakan jawaban tersebut. Semua ini adalah langkah-langkah yang diperlukan untuk agen AI. Agen ini diimplementasikan dalam Python karena ini adalah bahasa yang paling umum digunakan dalam AI.

Mengapa melakukan ini?

Agen perdagangan otomatis memungkinkan pengembang untuk memilih dan menjalankan strategi perdagangan. Agen AI memungkinkan strategi perdagangan yang lebih kompleks dan dinamis, berpotensi menggunakan informasi dan algoritma yang bahkan belum dipertimbangkan oleh pengembang untuk digunakan.

Perangkat

Tutorial ini menggunakan Python (opens in a new tab), pustaka Web3 (opens in a new tab), dan Uniswap v3 (opens in a new tab) untuk kuotasi dan perdagangan.

Mengapa Python?

Bahasa yang paling banyak digunakan untuk AI adalah Python (opens in a new tab), jadi kami menggunakannya di sini. Jangan khawatir jika Anda tidak tahu Python. Bahasa ini sangat jelas, dan saya akan menjelaskan dengan tepat apa yang dilakukannya.

Pustaka Web3 (opens in a new tab) adalah API Python Ethereum yang paling umum. Cukup mudah digunakan.

Berdagang di rantai blok

Ada banyak bursa terdesentralisasi (DEX) yang memungkinkan Anda memperdagangkan token di Ethereum. Namun, mereka cenderung memiliki nilai tukar yang serupa karena arbitrase.

Uniswap (opens in a new tab) adalah DEX yang banyak digunakan yang dapat kita gunakan untuk kuotasi (untuk melihat nilai relatif token) dan perdagangan.

OpenAI

Untuk model bahasa besar, saya memilih untuk memulai dengan OpenAI (opens in a new tab). Untuk menjalankan aplikasi dalam tutorial ini, Anda harus membayar untuk akses API. Pembayaran minimum sebesar $5 sudah lebih dari cukup.

Pengembangan, langkah demi langkah

Untuk menyederhanakan pengembangan, kita akan melanjutkannya secara bertahap. Setiap langkah adalah sebuah cabang di GitHub.

Memulai

Ada langkah-langkah untuk memulai di bawah UNIX atau Linux (termasuk WSL (opens in a new tab))

  1. Jika Anda belum memilikinya, unduh dan instal Python (opens in a new tab).

  2. Kloning repositori GitHub.

    1git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started
    2cd 260215-ai-agent
  3. Instal uv (opens in a new tab). Perintah di sistem Anda mungkin berbeda.

    1pipx install uv
  4. Unduh pustaka.

    1uv sync
  5. Aktifkan lingkungan virtual.

    1source .venv/bin/activate
  6. Untuk memverifikasi Python dan Web3 berfungsi dengan benar, jalankan python3 dan berikan program ini. Anda dapat memasukkannya di prompt >>>; tidak perlu membuat file.

    1from web3 import Web3
    2MAINNET_URL = "https://eth.drpc.org"
    3w3 = Web3(Web3.HTTPProvider(MAINNET_URL))
    4w3.eth.block_number
    5quit()

Membaca dari rantai blok

Langkah selanjutnya adalah membaca dari rantai blok. Untuk melakukannya, Anda perlu beralih ke cabang 02-read-quote lalu menggunakan uv untuk menjalankan program.

1git checkout 02-read-quote
2uv run agent.py

Anda akan menerima daftar objek Quote, masing-masing dengan stempel waktu, harga, dan aset (saat ini selalu WETH/USDC).

Berikut adalah penjelasan baris demi baris.

1from web3 import Web3
2from web3.contract import Contract
3from decimal import Decimal, ROUND_HALF_UP
4from dataclasses import dataclass
5from datetime import datetime, timezone
6from pprint import pprint
7import time
8import functools
9import sys
Tampilkan semua

Impor pustaka yang kita butuhkan. Mereka dijelaskan di bawah ini saat digunakan.

1print = functools.partial(print, flush=True)

Mengganti print Python dengan versi yang selalu membersihkan keluaran dengan segera. Ini berguna dalam skrip yang berjalan lama karena kita tidak ingin menunggu pembaruan status atau keluaran debugging.

1MAINNET_URL = "https://eth.drpc.org"

Sebuah URL untuk menuju ke Jaringan Utama. Anda bisa mendapatkan satu dari Simpul sebagai layanan atau menggunakan salah satu yang diiklankan di Chainlist (opens in a new tab).

1BLOCK_TIME_SECONDS = 12
2MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS)
3HOUR_BLOCKS = MINUTE_BLOCKS * 60
4DAY_BLOCKS = HOUR_BLOCKS * 24

Sebuah blok Jaringan Utama Ethereum biasanya terjadi setiap dua belas detik, jadi ini adalah jumlah blok yang kita harapkan terjadi dalam suatu periode waktu. Perhatikan bahwa ini bukan angka yang pasti. Ketika pengusul blok tidak berfungsi, blok tersebut dilewati, dan waktu untuk blok berikutnya adalah 24 detik. Jika kita ingin mendapatkan blok yang tepat untuk stempel waktu, kita akan menggunakan pencarian biner (opens in a new tab). Namun, ini sudah cukup dekat untuk tujuan kita. Memprediksi masa depan bukanlah ilmu pasti.

1CYCLE_BLOCKS = DAY_BLOCKS

Ukuran siklus. Kami meninjau kuotasi sekali per siklus dan mencoba memperkirakan nilai di akhir siklus berikutnya.

1# Alamat pool yang kita baca
2WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")

Nilai kuotasi diambil dari pool Uniswap 3 USDC/WETH di alamat 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 (opens in a new tab). Alamat ini sudah dalam bentuk checksum, tetapi lebih baik menggunakan Web3.to_checksum_address (opens in a new tab) untuk membuat kode dapat digunakan kembali.

1POOL_ABI = [
2 { "name": "slot0", ... },
3 { "name": "token0", ... },
4 { "name": "token1", ... },
5]
6
7ERC20_ABI = [
8 { "name": "symbol", ... },
9 { "name": "decimals", ... }
10]
Tampilkan semua

Ini adalah ABI (opens in a new tab) untuk dua kontrak yang perlu kita hubungi. Untuk menjaga agar kode tetap ringkas, kami hanya menyertakan fungsi yang perlu kami panggil.

1w3 = Web3(Web3.HTTPProvider(MAINNET_URL))

Mulai pustaka Web3 (opens in a new tab) dan sambungkan ke simpul Ethereum.

1@dataclass(frozen=True)
2class ERC20Token:
3 address: str
4 symbol: str
5 decimals: int
6 contract: Contract

Ini adalah salah satu cara untuk membuat kelas data di Python. Tipe data Contract (opens in a new tab) digunakan untuk terhubung ke kontrak. Perhatikan (frozen=True). Dalam Python, boolean (opens in a new tab) didefinisikan sebagai True atau False, dengan huruf besar. Kelas data ini beku (frozen), artinya bidang-bidangnya tidak dapat dimodifikasi.

Perhatikan indentasi. Berbeda dengan bahasa turunan C (opens in a new tab), Python menggunakan indentasi untuk menunjukkan blok. Interpreter Python tahu bahwa definisi berikut bukan bagian dari kelas data ini karena tidak dimulai pada indentasi yang sama dengan bidang kelas data.

1@dataclass(frozen=True)
2class PoolInfo:
3 address: str
4 token0: ERC20Token
5 token1: ERC20Token
6 contract: Contract
7 asset: str
8 decimal_factor: Decimal = 1

Tipe Decimal (opens in a new tab) digunakan untuk menangani pecahan desimal secara akurat.

1 def get_price(self, block: int) -> Decimal:

Ini adalah cara untuk mendefinisikan sebuah fungsi di Python. Definisi tersebut diberi indentasi untuk menunjukkan bahwa ia masih merupakan bagian dari PoolInfo.

Dalam sebuah fungsi yang merupakan bagian dari kelas data, parameter pertama selalu self, yaitu instance kelas data yang memanggilnya. Di sini ada parameter lain, yaitu nomor blok.

1 assert block <= w3.eth.block_number, "Blok ada di masa depan"

Jika kita bisa membaca masa depan, kita tidak akan memerlukan AI untuk berdagang.

1 sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0])

Sintaks untuk memanggil fungsi di EVM dari Web3 adalah ini: <objek kontrak>.functions.<nama fungsi>().call(<parameter>). Parameter dapat berupa parameter fungsi EVM (jika ada; di sini tidak ada) atau [parameter bernama](https://en.wikipedia.org/wiki/Named_parameter) untuk mengubah perilaku rantai blok. Di sini kita menggunakan satu, block_identifier`, untuk menentukan nomor blok yang ingin kita jalankan.

Hasilnya adalah struct ini, dalam bentuk array (opens in a new tab). Nilai pertama adalah fungsi dari nilai tukar antara dua token.

1 raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2

Untuk mengurangi kalkulasi di dalam rantai, Uniswap v3 tidak menyimpan faktor pertukaran aktual tetapi akar kuadratnya. Karena EVM tidak mendukung matematika titik mengambang atau pecahan, alih-alih nilai aktual, responsnya adalah harga&#x22C5296

1 # (token1 per token0)
2 return 1/(raw_price * self.decimal_factor)

Harga mentah yang kita dapatkan adalah jumlah token0 yang kita peroleh untuk setiap token1. Di pool kami, token0 adalah USDC (Koin Stabil dengan nilai yang sama dengan dolar AS) dan token1 adalah WETH (opens in a new tab). Nilai yang sebenarnya kita inginkan adalah jumlah dolar per WETH, bukan kebalikannya.

Faktor desimal adalah rasio antara faktor desimal (opens in a new tab) untuk kedua token tersebut.

1@dataclass(frozen=True)
2class Quote:
3 timestamp: str
4 price: Decimal
5 asset: str

Kelas data ini mewakili sebuah kuotasi: harga aset tertentu pada titik waktu tertentu. Pada titik ini, bidang asset tidak relevan karena kita menggunakan satu pool dan oleh karena itu hanya memiliki satu aset. Namun, kami akan menambahkan lebih banyak aset nanti.

1def read_token(address: str) -> ERC20Token:
2 token = w3.eth.contract(address=address, abi=ERC20_ABI)
3 symbol = token.functions.symbol().call()
4 decimals = token.functions.decimals().call()
5
6 return ERC20Token(
7 address=address,
8 symbol=symbol,
9 decimals=decimals,
10 contract=token
11 )
Tampilkan semua

Fungsi ini mengambil sebuah alamat dan mengembalikan informasi tentang kontrak token di alamat tersebut. Untuk membuat Kontrak Web3 (opens in a new tab) yang baru, kita memberikan alamat dan ABI ke w3.eth.contract.

1def read_pool(address: str) -> PoolInfo:
2 pool_contract = w3.eth.contract(address=address, abi=POOL_ABI)
3 token0Address = pool_contract.functions.token0().call()
4 token1Address = pool_contract.functions.token1().call()
5 token0 = read_token(token0Address)
6 token1 = read_token(token1Address)
7
8 return PoolInfo(
9 address=address,
10 asset=f"{token1.symbol}/{token0.symbol}",
11 token0=token0,
12 token1=token1,
13 contract=pool_contract,
14 decimal_factor=Decimal(10) ** Decimal(token0.decimals - token1.decimals)
15 )
Tampilkan semua

Fungsi ini mengembalikan semua yang kita butuhkan tentang pool tertentu (opens in a new tab). Sintaks f"<string>" adalah string yang diformat (opens in a new tab).

1def get_quote(pool: PoolInfo, block_number: int = None) -> Quote:

Dapatkan objek Quote. Nilai default untuk block_number adalah None (tidak ada nilai).

1 if block_number is None:
2 block_number = w3.eth.block_number

Jika nomor blok tidak ditentukan, gunakan w3.eth.block_number, yang merupakan nomor blok terbaru. Ini adalah sintaks untuk pernyataan if (opens in a new tab).

Mungkin terlihat seolah-olah akan lebih baik jika hanya mengatur default ke w3.eth.block_number, tetapi itu tidak bekerja dengan baik karena itu akan menjadi nomor blok pada saat fungsi didefinisikan. Dalam agen yang berjalan lama, ini akan menjadi masalah.

1 block = w3.eth.get_block(block_number)
2 price = pool.get_price(block_number)
3 return Quote(
4 timestamp=datetime.fromtimestamp(block.timestamp, timezone.utc).isoformat(),
5 price=price.quantize(Decimal("0.01")),
6 asset=pool.asset
7 )

Gunakan pustaka datetime (opens in a new tab) untuk memformatnya ke format yang dapat dibaca oleh manusia dan model bahasa besar (LLM). Gunakan Decimal.quantize (opens in a new tab) untuk membulatkan nilai ke dua tempat desimal.

1def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]:

Di Python Anda mendefinisikan sebuah daftar (opens in a new tab) yang hanya dapat berisi tipe tertentu menggunakan list[<type>].

1 quotes = []
2 for block in range(start_block, end_block + 1, step):

Di Python sebuah for loop (opens in a new tab) biasanya melakukan iterasi pada sebuah daftar. Daftar nomor blok untuk menemukan kutipan berasal dari range (opens in a new tab).

1 quote = get_quote(pool, block)
2 quotes.append(quote)
3 return quotes

Untuk setiap nomor blok, dapatkan objek Quote dan tambahkan ke daftar quotes. Kemudian kembalikan daftar itu.

1pool = read_pool(WETHUSDC_ADDRESS)
2quotes = get_quotes(
3 pool,
4 w3.eth.block_number - 12*CYCLE_BLOCKS,
5 w3.eth.block_number,
6 CYCLE_BLOCKS
7)
8
9pprint(quotes)
Tampilkan semua

Ini adalah kode utama dari skrip. Baca informasi pool, dapatkan dua belas kutipan, dan pprint (opens in a new tab) mereka.

Membuat prompt

Selanjutnya, kita perlu mengubah daftar kutipan ini menjadi sebuah prompt untuk LLM dan mendapatkan nilai masa depan yang diharapkan.

1git checkout 03-create-prompt
2uv run agent.py

Outputnya sekarang akan menjadi prompt untuk LLM, mirip dengan:

1Mengingat kutipan ini:
2Aset: WETH/USDC
3 2026-01-20T16:34 3016.21
4 .
5 .
6 .
7 2026-02-01T17:49 2299.10
8
9Aset: WBTC/WETH
10 2026-01-20T16:34 29.84
11 .
12 .
13 .
14 2026-02-01T17:50 33.46
15
16
17Berapa nilai yang Anda harapkan untuk WETH/USDC pada waktu 2026-02-02T17:56?
18
19Berikan jawaban Anda sebagai satu angka yang dibulatkan ke dua tempat desimal,
20tanpa teks lain.
Tampilkan semua

Perhatikan bahwa ada kutipan untuk dua aset di sini, WETH/USDC dan WBTC/WETH. Menambahkan kutipan dari aset lain mungkin meningkatkan akurasi prediksi.

Seperti apa bentuk sebuah prompt

Prompt ini berisi tiga bagian, yang cukup umum dalam prompt LLM.

  1. Informasi. LLM memiliki banyak informasi dari pelatihan mereka, tetapi biasanya mereka tidak memiliki yang terbaru. Inilah alasan kita perlu mengambil kutipan terbaru di sini. Menambahkan informasi ke prompt disebut retrieval augmented generation (RAG) (opens in a new tab).

  2. Pertanyaan sebenarnya. Inilah yang ingin kita ketahui.

  3. Instruksi pemformatan output. Biasanya, LLM akan memberi kita perkiraan dengan penjelasan tentang bagaimana ia sampai pada perkiraan tersebut. Ini lebih baik untuk manusia, tetapi program komputer hanya membutuhkan hasil akhirnya.

Penjelasan kode

Berikut adalah kode barunya.

1from datetime import datetime, timezone, timedelta

Kita perlu memberikan LLM waktu yang kita inginkan untuk perkiraan. Untuk mendapatkan waktu "n menit/jam/hari" di masa depan, kita menggunakan kelas timedelta (opens in a new tab).

1# Alamat pool yang kita baca
2WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
3WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD")

Kita memiliki dua pool yang perlu dibaca.

1@dataclass(frozen=True)
2class PoolInfo:
3 .
4 .
5 .
6 reverse: bool = False
7
8 def get_price(self, block: int) -> Decimal:
9 assert block <= w3.eth.block_number, "Blok ada di masa depan"
10 sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0])
11 raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (token1 per token0)
12 if self.reverse:
13 return 1/(raw_price * self.decimal_factor)
14 else:
15 return raw_price * self.decimal_factor
Tampilkan semua

Di pool WETH/USDC, kami ingin tahu berapa banyak token0 (USDC) yang kami butuhkan untuk membeli satu token1 (WETH). Di pool WETH/WBTC, kami ingin tahu berapa banyak token1 (WETH) yang kami butuhkan untuk membeli satu token0 (WBTC, yaitu Bitcoin terbungkus). Kita perlu melacak apakah rasio pool perlu dibalik.

1def read_pool(address: str, reverse: bool = False) -> PoolInfo:
2 .
3 .
4 .
5
6 return PoolInfo(
7 .
8 .
9 .
10
11 asset= f"{token1.symbol}/{token0.symbol}" if reverse else f"{token0.symbol}/{token1.symbol}",
12 reverse=reverse
13 )
Tampilkan semua

Untuk mengetahui apakah sebuah pool perlu dibalik, kita harus mendapatkannya sebagai input untuk read_pool. Selain itu, simbol aset perlu diatur dengan benar.

Sintaks <a> if <b> else <c> adalah padanan Python dari operator kondisional terner (opens in a new tab), yang dalam bahasa turunan C adalah <b> ? <a> : <c>.

1def format_quotes(quotes: list[Quote]) -> str:
2 result = f"Asset: {quotes[0].asset}\n"
3 for quote in quotes:
4 result += f"\t{quote.timestamp[0:16]} {quote.price.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)}\n"
5 return result

Fungsi ini membangun sebuah string yang memformat daftar objek Quote, dengan asumsi semuanya berlaku untuk aset yang sama.

1def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str:
2 return f"""

Di Python, literal string multibaris (opens in a new tab) ditulis sebagai """ .... """.

1Mengingat kutipan ini:
2{
3 functools.reduce(lambda acc, q: acc + '\n' + q,
4 map(lambda q: format_quotes(q), quotes))
5}

Di sini, kami menggunakan pola MapReduce (opens in a new tab) untuk menghasilkan string untuk setiap daftar kutipan dengan format_quotes, lalu mereduksinya menjadi satu string untuk digunakan dalam prompt.

1Berapa nilai yang Anda harapkan untuk {asset} pada waktu {expected_time}?
2
3Berikan jawaban Anda sebagai satu angka yang dibulatkan ke dua tempat desimal,
4tanpa teks lain.
5 """

Sisa dari prompt adalah seperti yang diharapkan.

1wethusdc_pool = read_pool(WETHUSDC_ADDRESS, True)
2wethusdc_quotes = get_quotes(
3 wethusdc_pool,
4 w3.eth.block_number - 12*CYCLE_BLOCKS,
5 w3.eth.block_number,
6 CYCLE_BLOCKS,
7)
8
9wethwbtc_pool = read_pool(WETHWBTC_ADDRESS)
10wethwbtc_quotes = get_quotes(
11 wethwbtc_pool,
12 w3.eth.block_number - 12*CYCLE_BLOCKS,
13 w3.eth.block_number,
14 CYCLE_BLOCKS
15)
Tampilkan semua

Tinjau kedua pool dan dapatkan kuotasi dari keduanya.

1future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16]
2
3print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset))

Tentukan titik waktu masa depan yang kita inginkan estimasinya, dan buat prompt.

Berinteraksi dengan LLM

Selanjutnya, kita akan meminta LLM yang sebenarnya dan menerima nilai masa depan yang diharapkan. Saya menulis program ini menggunakan OpenAI, jadi jika Anda ingin menggunakan penyedia yang berbeda, Anda harus menyesuaikannya.

  1. Dapatkan akun OpenAI (opens in a new tab)

  2. Danai akun (opens in a new tab)—jumlah minimum pada saat penulisan adalah $5

  3. Buat kunci API (opens in a new tab)

  4. Di baris perintah, ekspor kunci API agar program Anda dapat menggunakannya

    1export OPENAI_API_KEY=sk-<sisa kunci ada di sini>
  5. Checkout dan jalankan agen

    1git checkout 04-interface-llm
    2uv run agent.py

Berikut adalah kode barunya.

1from openai import OpenAI
2
3open_ai = OpenAI() # Klien membaca variabel lingkungan OPENAI_API_KEY

Impor dan buat instance API OpenAI.

1response = open_ai.chat.completions.create(
2 model="gpt-4-turbo",
3 messages=[
4 {"role": "user", "content": prompt}
5 ],
6 temperature=0.0,
7 max_tokens=16,
8)

Panggil API OpenAI (open_ai.chat.completions.create) untuk membuat respons.

1expected_price = Decimal(response.choices[0].message.content.strip())
2current_price = wethusdc_quotes[-1].price
3
4print ("Harga saat ini:", wethusdc_quotes[-1].price)
5print(f"Pada {future_time}, harga yang diharapkan: {expected_price} USD")
6
7if (expected_price > current_price):
8 print(f"Beli, saya perkirakan harga akan naik sebesar {expected_price - current_price} USD")
9else:
10 print(f"Jual, saya perkirakan harga akan turun sebesar {current_price - expected_price} USD")
Tampilkan semua

Keluarkan harga dan berikan rekomendasi beli atau jual.

Menguji prediksi

Sekarang setelah kita dapat menghasilkan prediksi, kita juga dapat menggunakan data historis untuk menilai apakah kita menghasilkan prediksi yang berguna.

1uv run test-predictor.py

Hasil yang diharapkan serupa dengan:

1Prediksi untuk 2026-01-05T19:50: diprediksi 3138.93 USD, riil 3218.92 USD, kesalahan 79.99 USD
2Prediksi untuk 2026-01-06T19:56: diprediksi 3243.39 USD, riil 3221.08 USD, kesalahan 22.31 USD
3Prediksi untuk 2026-01-07T20:02: diprediksi 3223.24 USD, riil 3146.89 USD, kesalahan 76.35 USD
4Prediksi untuk 2026-01-08T20:11: diprediksi 3150.47 USD, riil 3092.04 USD, kesalahan 58.43 USD
5.
6.
7.
8Prediksi untuk 2026-01-31T22:33: diprediksi 2637.73 USD, riil 2417.77 USD, kesalahan 219.96 USD
9Prediksi untuk 2026-02-01T22:41: diprediksi 2381.70 USD, riil 2318.84 USD, kesalahan 62.86 USD
10Prediksi untuk 2026-02-02T22:49: diprediksi 2234.91 USD, riil 2349.28 USD, kesalahan 114.37 USD
11Kesalahan prediksi rata-rata selama 29 prediksi: 83.87103448275862068965517241 USD
12Perubahan rata-rata per rekomendasi: 4.787931034482758620689655172 USD
13Varians standar perubahan: 104.42 USD
14Hari menguntungkan: 51.72%
15Hari merugi: 48.28%
Tampilkan semua

Sebagian besar penguji identik dengan agen, tetapi berikut adalah bagian-bagian yang baru atau dimodifikasi.

1CYCLES_FOR_TEST = 40 # Untuk backtest, berapa banyak siklus yang kita uji
2
3# Dapatkan banyak kutipan
4wethusdc_pool = read_pool(WETHUSDC_ADDRESS, True)
5wethusdc_quotes = get_quotes(
6 wethusdc_pool,
7 w3.eth.block_number - CYCLE_BLOCKS*CYCLES_FOR_TEST,
8 w3.eth.block_number,
9 CYCLE_BLOCKS,
10)
11
12wethwbtc_pool = read_pool(WETHWBTC_ADDRESS)
13wethwbtc_quotes = get_quotes(
14 wethwbtc_pool,
15 w3.eth.block_number - CYCLE_BLOCKS*CYCLES_FOR_TEST,
16 w3.eth.block_number,
17 CYCLE_BLOCKS
18)
Tampilkan semua

Kami melihat ke belakang sebanyak CYCLES_FOR_TEST (ditentukan sebagai 40 di sini) hari.

1# Buat prediksi dan periksa terhadap riwayat nyata
2
3total_error = Decimal(0)
4changes = []

Ada dua jenis kesalahan yang kami minati. Yang pertama, total_error, hanyalah jumlah kesalahan yang dibuat oleh prediktor.

Untuk memahami yang kedua, changes, kita perlu mengingat tujuan agen. Tujuannya bukan untuk memprediksi rasio WETH/USDC (harga ETH). Ini untuk mengeluarkan rekomendasi jual dan beli. Jika harga saat ini adalah $2000 dan memprediksi $2010 besok, kami tidak keberatan jika hasil sebenarnya adalah $2020 dan kami mendapatkan uang tambahan. Namun, kita keberatan jika diprediksi $2010, dan membeli ETH berdasarkan rekomendasi tersebut, dan harganya turun menjadi $1990.

1for index in range(0,len(wethusdc_quotes)-CYCLES_BACK):

Kami hanya dapat melihat kasus di mana riwayat lengkap (nilai yang digunakan untuk prediksi dan nilai dunia nyata untuk membandingkannya) tersedia. Ini berarti kasus terbaru haruslah yang dimulai CYCLES_BACK yang lalu.

1 wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK]
2 wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK]

Gunakan irisan (opens in a new tab) untuk mendapatkan jumlah sampel yang sama dengan jumlah yang digunakan agen. Kode antara di sini dan segmen berikutnya adalah kode get-a-prediction yang sama yang kami miliki di agen.

1 predicted_price = Decimal(response.choices[0].message.content.strip())
2 real_price = wethusdc_quotes[index+CYCLES_BACK].price
3 prediction_time_price = wethusdc_quotes[index+CYCLES_BACK-1].price

Dapatkan harga yang diprediksi, harga riil, dan harga pada saat prediksi. Kita memerlukan harga pada saat prediksi untuk menentukan apakah rekomendasi tersebut untuk membeli atau menjual.

1 error = abs(predicted_price - real_price)
2 total_error += error
3 print (f"Prediksi untuk {prediction_time}: diprediksi {predicted_price} USD, riil {real_price} USD, kesalahan {error} USD")

Hitung kesalahannya, dan tambahkan ke total.

1 recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell'
2 price_increase = real_price - prediction_time_price
3 changes.append(price_increase if recomended_action == 'buy' else -price_increase)

Untuk changes, kami menginginkan dampak moneter dari membeli atau menjual satu ETH. Jadi pertama, kita perlu menentukan rekomendasi, kemudian menilai bagaimana harga sebenarnya berubah, dan apakah rekomendasi tersebut menghasilkan uang (perubahan positif) atau merugikan uang (perubahan negatif).

1print (f"Kesalahan prediksi rata-rata selama {len(wethusdc_quotes)-CYCLES_BACK} prediksi: {total_error / Decimal(len(wethusdc_quotes)-CYCLES_BACK)} USD")
2
3length_changes = Decimal(len(changes))
4mean_change = sum(changes, Decimal(0)) / length_changes
5print (f"Perubahan rata-rata per rekomendasi: {mean_change} USD")
6var = sum((x - mean_change) ** 2 for x in changes) / length_changes
7print (f"Varians standar perubahan: {var.sqrt().quantize(Decimal("0.01"))} USD")

Laporkan hasilnya.

1print (f"Hari menguntungkan: {len(list(filter(lambda x: x > 0, changes)))/length_changes:.2%}")
2print (f"Hari merugi: {len(list(filter(lambda x: x < 0, changes)))/length_changes:.2%}")

Gunakan filter (opens in a new tab) untuk menghitung jumlah hari yang menguntungkan dan jumlah hari yang merugikan. Hasilnya adalah objek filter, yang perlu kita ubah menjadi daftar untuk mendapatkan panjangnya.

Mengirimkan transaksi

Sekarang kita perlu benar-benar mengirimkan transaksi. Namun, saya tidak ingin menghabiskan uang sungguhan pada saat ini, sebelum sistem terbukti. Sebaliknya, kita akan membuat fork lokal dari Jaringan Utama, dan "berdagang" di jaringan itu.

Berikut adalah langkah-langkah untuk membuat fork lokal dan mengaktifkan perdagangan.

  1. Instal Foundry (opens in a new tab)

  2. Mulai anvil (opens in a new tab)

    1anvil --fork-url https://eth.drpc.org --block-time 12

    anvil mendengarkan di URL default untuk Foundry, http://localhost:8545 (opens in a new tab), jadi kita tidak perlu menentukan URL untuk perintah cast (opens in a new tab) yang kita gunakan untuk memanipulasi rantai blok.

  3. Saat berjalan di anvil, ada sepuluh akun uji yang memiliki ETH—atur variabel lingkungan untuk yang pertama

    1PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
    2ADDRESS=`cast wallet address $PRIVATE_KEY`
  4. Ini adalah kontrak yang perlu kita gunakan. SwapRouter (opens in a new tab) adalah kontrak Uniswap v3 yang kami gunakan untuk benar-benar berdagang. Kita bisa berdagang langsung melalui pool, tetapi ini jauh lebih mudah.

    Dua variabel terbawah adalah jalur Uniswap v3 yang diperlukan untuk menukar antara WETH dan USDC.

    1WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
    2USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
    3POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640
    4SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564
    5WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
    6USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
  5. Setiap akun uji memiliki 10.000 ETH. Gunakan kontrak WETH untuk membungkus 1000 ETH untuk mendapatkan 1000 WETH untuk perdagangan.

    1cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY
  6. Gunakan SwapRouter untuk memperdagangkan 500 WETH untuk USDC.

    1cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY
    2MAXINT=`cast max-int uint256`
    3cast send $SWAP_ROUTER \
    4 "exactInput((bytes,address,uint256,uint256,uint256))" \
    5 "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \
    6 --private-key $PRIVATE_KEY

    Panggilan approve membuat tunjangan yang memungkinkan SwapRouter untuk membelanjakan sebagian token kita. Kontrak tidak dapat memantau aksi, jadi jika kita mentransfer token langsung ke kontrak SwapRouter, kontrak tersebut tidak akan tahu bahwa ia telah dibayar. Sebaliknya, kami mengizinkan kontrak SwapRouter untuk membelanjakan sejumlah tertentu, dan kemudian SwapRouter melakukannya. Ini dilakukan melalui fungsi yang dipanggil oleh SwapRouter, sehingga ia tahu jika berhasil.

  7. Verifikasi Anda memiliki cukup kedua token.

    1cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei
    2echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc

Sekarang setelah kita memiliki WETH dan USDC, kita dapat benar-benar menjalankan agen.

1git checkout 05-trade
2uv run agent.py

Outputnya akan terlihat mirip dengan:

1(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py
2Harga saat ini: 1843.16
3Pada 2026-02-06T23:07, harga yang diharapkan: 1724.41 USD
4Saldo akun sebelum perdagangan:
5Saldo USDC: 927301.578272
6Saldo WETH: 500
7Jual, saya perkirakan harga akan turun sebesar 118.75 USD
8Transaksi approve terkirim: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f
9Transaksi approve ditambang.
10Transaksi jual terkirim: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf
11Transaksi jual ditambang.
12Saldo akun setelah perdagangan:
13Saldo USDC: 929143.797116
14Saldo WETH: 499
Tampilkan semua

Untuk benar-benar menggunakannya, Anda memerlukan beberapa perubahan kecil.

  • Di baris 14, ubah MAINNET_URL ke titik akses nyata, seperti https://eth.drpc.org
  • Pada baris 28, ubah PRIVATE_KEY ke kunci pribadi Anda sendiri
  • Kecuali Anda sangat kaya dan dapat membeli atau menjual 1 ETH setiap hari untuk agen yang belum terbukti, Anda mungkin ingin mengubah 29 untuk mengurangi WETH_TRADE_AMOUNT

Penjelasan kode

Berikut adalah kode barunya.

1SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564")
2WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
3USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
4PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"

Variabel yang sama yang kita gunakan di langkah 4.

1WETH_TRADE_AMOUNT=1

Jumlah yang akan diperdagangkan.

1ERC20_ABI = [
2 { "name": "symbol", ... },
3 { "name": "decimals", ... },
4 { "name": "balanceOf", ...},
5 { "name": "approve", ...}
6]

Untuk benar-benar berdagang, kita memerlukan fungsi approve. Kami juga ingin menampilkan saldo sebelum dan sesudah, jadi kami juga memerlukan balanceOf.

1SWAP_ROUTER_ABI = [
2 { "name": "exactInput", ...},
3]

Di SwapRouter ABI, kita hanya perlu exactInput. Ada fungsi terkait, exactOutput, yang bisa kita gunakan untuk membeli tepat satu WETH, tetapi untuk kesederhanaan kita hanya menggunakan exactInput dalam kedua kasus.

1account = w3.eth.account.from_key(PRIVATE_KEY)
2swap_router = w3.eth.contract(
3 address=SWAP_ROUTER_ADDRESS,
4 abi=SWAP_ROUTER_ABI
5)

Definisi Web3 untuk akun (opens in a new tab) dan kontrak SwapRouter.

1def txn_params() -> dict:
2 return {
3 "from": account.address,
4 "value": 0,
5 "gas": 300000,
6 "nonce": w3.eth.get_transaction_count(account.address),
7 }

Parameter transaksi. Kita memerlukan fungsi di sini karena nonce (opens in a new tab) harus berubah setiap saat.

1def approve_token(contract: Contract, amount: int):

Setujui alokasi token untuk SwapRouter.

1 txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params())
2 signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY)
3 tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)

Beginilah cara kami mengirim transaksi di Web3. Pertama kita menggunakan objek Kontrak (opens in a new tab) untuk membangun transaksi. Kemudian kita menggunakan web3.eth.account.sign_transaction (opens in a new tab) untuk menandatangani transaksi, menggunakan PRIVATE_KEY. Terakhir, kami menggunakan w3.eth.send_raw_transaction (opens in a new tab) untuk mengirim transaksi.

1 print(f"Transaksi approve terkirim: {tx_hash.hex()}")
2 w3.eth.wait_for_transaction_receipt(tx_hash)
3 print("Transaksi approve ditambang.")

w3.eth.wait_for_transaction_receipt (opens in a new tab) menunggu hingga transaksi ditambang. Ini mengembalikan tanda terima jika diperlukan.

1SELL_PARAMS = {
2 "path": WETH_TO_USDC,
3 "recipient": account.address,
4 "deadline": 2**256 - 1,
5 "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals,
6 "amountOutMinimum": 0,
7}

Ini adalah parameter saat menjual WETH.

1def make_buy_params(quote: Quote) -> dict:
2 return {
3 "path": USDC_TO_WETH,
4 "recipient": account.address,
5 "deadline": 2**256 - 1,
6 "amountIn": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals,
7 "amountOutMinimum": 0,
8 }

Berbeda dengan SELL_PARAMS, parameter pembelian bisa berubah. Jumlah masukan adalah biaya 1 WETH, seperti yang tersedia di quote.

1def buy(quote: Quote):
2 buy_params = make_buy_params(quote)
3 approve_token(wethusdc_pool.token0.contract, buy_params["amountIn"])
4 txn = swap_router.functions.exactInput(buy_params).build_transaction(txn_params())
5 signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY)
6 tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)
7 print(f"Transaksi beli terkirim: {tx_hash.hex()}")
8 w3.eth.wait_for_transaction_receipt(tx_hash)
9 print("Transaksi beli ditambang.")
10
11
12def sell():
13 approve_token(wethusdc_pool.token1.contract,
14 WETH_TRADE_AMOUNT * 10**wethusdc_pool.token1.decimals)
15 txn = swap_router.functions.exactInput(SELL_PARAMS).build_transaction(txn_params())
16 signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY)
17 tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)
18 print(f"Transaksi jual terkirim: {tx_hash.hex()}")
19 w3.eth.wait_for_transaction_receipt(tx_hash)
20 print("Transaksi jual ditambang.")
Tampilkan semua

Fungsi buy() dan sell() hampir identik. Pertama kita menyetujui alokasi yang cukup untuk SwapRouter, dan kemudian kita memanggilnya dengan jalur dan jumlah yang benar.

1def balances():
2 token0_balance = wethusdc_pool.token0.contract.functions.balanceOf(account.address).call()
3 token1_balance = wethusdc_pool.token1.contract.functions.balanceOf(account.address).call()
4
5 print(f"{wethusdc_pool.token0.symbol} Balance: {Decimal(token0_balance) / Decimal(10 ** wethusdc_pool.token0.decimals)}")
6 print(f"{wethusdc_pool.token1.symbol} Balance: {Decimal(token1_balance) / Decimal(10 ** wethusdc_pool.token1.decimals)}")

Laporkan saldo pengguna dalam kedua mata uang.

1print("Saldo akun sebelum perdagangan:")
2balances()
3
4if (expected_price > current_price):
5 print(f"Beli, saya perkirakan harga akan naik sebesar {expected_price - current_price} USD")
6 buy(wethusdc_quotes[-1])
7else:
8 print(f"Jual, saya perkirakan harga akan turun sebesar {current_price - expected_price} USD")
9 sell()
10
11print("Saldo akun setelah perdagangan:")
12balances()
Tampilkan semua

Agen ini saat ini hanya bekerja sekali. Namun, Anda dapat mengubahnya agar berfungsi secara terus-menerus baik dengan menjalankannya dari crontab (opens in a new tab) atau dengan membungkus baris 368-400 dalam sebuah loop dan menggunakan time.sleep (opens in a new tab) untuk menunggu hingga tiba waktunya untuk siklus berikutnya.

Kemungkinan perbaikan

Ini bukan versi produksi penuh; ini hanyalah contoh untuk mengajarkan dasar-dasarnya. Berikut adalah beberapa ide untuk perbaikan.

Perdagangan yang lebih cerdas

Ada dua fakta penting yang diabaikan agen saat memutuskan apa yang harus dilakukan.

  • Besarnya perubahan yang diantisipasi. Agen menjual sejumlah WETH yang tetap jika harga diperkirakan akan menurun, terlepas dari besarnya penurunan. Boleh dibilang, akan lebih baik untuk mengabaikan perubahan kecil dan menjual berdasarkan seberapa besar kita mengharapkan harga akan turun.
  • Portofolio saat ini. Jika 10% dari portofolio Anda ada di WETH dan Anda pikir harganya akan naik, mungkin masuk akal untuk membeli lebih banyak. Tetapi jika 90% dari portofolio Anda ada di WETH, Anda mungkin sudah cukup terekspos, dan tidak perlu membeli lebih banyak. Kebalikannya benar jika Anda mengharapkan harga akan turun.

Bagaimana jika Anda ingin merahasiakan strategi perdagangan Anda?

Vendor AI dapat melihat kueri yang Anda kirim ke LLM mereka, yang dapat mengekspos sistem perdagangan jenius yang Anda kembangkan dengan agen Anda. Sistem perdagangan yang digunakan terlalu banyak orang tidak ada gunanya karena terlalu banyak orang mencoba membeli saat Anda ingin membeli (dan harga naik) dan mencoba menjual saat Anda ingin menjual (dan harga turun).

Anda dapat menjalankan LLM secara lokal, misalnya, menggunakan LM-Studio (opens in a new tab), untuk menghindari masalah ini.

Dari bot AI ke agen AI

Anda dapat membuat argumen yang baik bahwa ini adalah bot AI, bukan agen AI. Ini mengimplementasikan strategi yang relatif sederhana yang mengandalkan informasi yang telah ditentukan sebelumnya. Kita dapat mengaktifkan perbaikan diri, misalnya, dengan menyediakan daftar pool Uniswap v3 dan nilai terbarunya dan menanyakan kombinasi mana yang memiliki nilai prediktif terbaik.

Perlindungan slippage

Saat ini tidak ada perlindungan slippage (opens in a new tab). Jika kutipan saat ini adalah $2000, dan harga yang diharapkan adalah $2100, agen akan membeli. Namun, jika sebelum agen membeli biayanya naik menjadi $2200, tidak masuk akal lagi untuk membeli.

Untuk menerapkan perlindungan slippage, tentukan nilai amountOutMinimum di baris 325 dan 334 dari agent.py (opens in a new tab).

Kesimpulan

Semoga, sekarang Anda cukup tahu untuk memulai dengan agen AI. Ini bukan gambaran umum yang komprehensif tentang subjek ini; ada seluruh buku yang didedikasikan untuk itu, tetapi ini cukup untuk membuat Anda memulai. Semoga beruntung!

Lihat di sini untuk lebih banyak pekerjaan saya (opens in a new tab).

Halaman pembaruan terakhir: 10 Februari 2026

Apakah tutorial ini membantu?