Ana içeriğe geç

Ethereum üzerinde kendi yapay zeka alım satım aracınızı oluşturun

Yapay Zeka
alım satım
aracı
python
Orta düzey
Ori Pomerantz
13 Şubat 2026
20 dakikalık okuma

Bu öğreticide, basit bir yapay zeka alım satım aracısı oluşturmayı öğreneceksiniz. Bu aracı şu adımları kullanarak çalışır:

  1. Bir jetonun mevcut ve geçmiş fiyatlarının yanı sıra diğer potansiyel olarak ilgili bilgileri okuyun
  2. Bu bilgilerle birlikte, nasıl ilgili olabileceğini açıklamak için arka plan bilgilerini de içeren bir sorgu oluşturun
  3. Sorguyu gönderin ve tahmini bir fiyat alın
  4. Tavsiyeye göre alım satım yapın
  5. Bekleyin ve tekrarlayın

Bu aracı, bilgilerin nasıl okunacağını, kullanılabilir bir yanıt veren bir sorguya nasıl çevrileceğini ve bu yanıtın nasıl kullanılacağını gösterir. Bunların hepsi bir yapay zeka aracısı için gerekli adımlardır. Bu aracı Python'da uygulanmıştır çünkü yapay zekada kullanılan en yaygın dildir.

Bunu neden yapmalı?

Otomatikleştirilmiş alım satım aracıları, geliştiricilerin bir alım satım stratejisi seçmelerine ve yürütmelerine olanak tanır. Yapay zeka aracıları, geliştiricinin kullanmayı hiç düşünmediği bilgileri ve algoritmaları potansiyel olarak kullanarak daha karmaşık ve dinamik alım satım stratejilerine olanak tanır.

Araçlar

Bu öğretici, teklifler ve alım satım için Python (opens in a new tab), Web3 kütüphanesi (opens in a new tab) ve Uniswap v3 (opens in a new tab) kullanır.

Neden Python?

Yapay zeka için en yaygın kullanılan dil Python (opens in a new tab) olduğundan, burada onu kullanıyoruz. Python bilmiyorsanız endişelenmeyin. Dil çok açıktır ve tam olarak ne yaptığını açıklayacağım.

Web3 kütüphanesi (opens in a new tab), en yaygın Python Ethereum API'sidir. Kullanımı oldukça kolaydır.

Blokzincirde alım satım

Ethereum'da jeton alım satımı yapmanızı sağlayan birçok merkeziyetsiz borsa (DEX) vardır. Ancak, arbitraj nedeniyle benzer döviz kurlarına sahip olma eğilimindedirler.

Uniswap (opens in a new tab), hem teklifler (jeton göreceli değerlerini görmek için) hem de alım satımlar için kullanabileceğimiz, yaygın olarak kullanılan bir merkeziyetsiz borsadır.

OpenAI

Büyük bir dil modeli için OpenAI (opens in a new tab) ile başlamayı seçtim. Bu öğreticideki uygulamayı çalıştırmak için API erişimi için ödeme yapmanız gerekecektir. 5 dolarlık minimum ödeme fazlasıyla yeterlidir.

Adım adım geliştirme

Geliştirmeyi basitleştirmek için aşamalar halinde ilerliyoruz. Her adım GitHub'da bir daldır.

Başlarken

UNIX veya Linux (WSL (opens in a new tab) dahil) altında başlamak için adımlar vardır

  1. Henüz sahip değilseniz, Python (opens in a new tab) indirip kurun.

  2. GitHub deposunu klonlayın.

    1git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started
    2cd 260215-ai-agent
  3. uv (opens in a new tab) yükleyin. Sisteminizdeki komut farklı olabilir.

    1pipx install uv
  4. Kütüphaneleri indirin.

    1uv sync
  5. Sanal ortamı etkinleştirin.

    1source .venv/bin/activate
  6. Python ve Web3'ün doğru çalıştığını doğrulamak için python3'ü çalıştırın ve ona bu programı sağlayın. Bunu >>> istemine girebilirsiniz; dosya oluşturmanıza gerek yoktur.

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

Blokzincirden okuma

Bir sonraki adım blokzincirden okuma yapmaktır. Bunu yapmak için, 02-read-quote dalına geçmeniz ve ardından programı çalıştırmak için uv kullanmanız gerekir.

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

Her biri bir zaman damgası, bir fiyat ve varlık (şu anda her zaman WETH/USDC) içeren bir Quote nesneleri listesi almalısınız.

İşte satır satır bir açıklama.

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
Tümünü göster

İhtiyacımız olan kütüphaneleri içe aktarın. Kullanıldıklarında aşağıda açıklanmıştır.

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

Python'un print fonksiyonunu, çıktıyı her zaman anında temizleyen bir sürümle değiştirir. Bu, uzun süre çalışan bir betikte kullanışlıdır çünkü durum güncellemelerini veya hata ayıklama çıktılarını beklemek istemeyiz.

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

Ana ağa ulaşmak için bir URL. Hizmet olarak düğüm sağlayıcısından bir tane alabilir veya Chainlist (opens in a new tab) içinde reklamı yapılanlardan birini kullanabilirsiniz.

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

Bir Ethereum ana ağ bloku genellikle on iki saniyede bir gerçekleşir, bu nedenle bunlar bir zaman diliminde gerçekleşmesini beklediğimiz blok sayısıdır. Bunun kesin bir rakam olmadığını unutmayın. Blok teklif edicisi kapalı olduğunda, o blok atlanır ve bir sonraki blok için süre 24 saniye olur. Bir zaman damgası için tam bloğu almak isteseydik, ikili arama (opens in a new tab) kullanırdık. Ancak, bu amaçlarımız için yeterince yakındır. Geleceği tahmin etmek kesin bir bilim değildir.

1CYCLE_BLOCKS = DAY_BLOCKS

Döngünün boyutu. Döngü başına bir kez teklifleri gözden geçiririz ve bir sonraki döngünün sonundaki değeri tahmin etmeye çalışırız.

1# Okuduğumuz havuzun adresi
2WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")

Teklif değerleri, 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 (opens in a new tab) adresindeki Uniswap 3 USDC/WETH havuzundan alınır. Bu adres zaten sağlama toplamı biçimindedir, ancak kodu yeniden kullanılabilir hale getirmek için Web3.to_checksum_address (opens in a new tab) kullanmak daha iyidir.

1POOL_ABI = [
2 { "name": "slot0", ... },
3 { "name": "token0", ... },
4 { "name": "token1", ... },
5]
6
7ERC20_ABI = [
8 { "name": "symbol", ... },
9 { "name": "decimals", ... }
10]
Tümünü göster

Bunlar, iletişim kurmamız gereken iki sözleşme için ABI'lerdir (opens in a new tab). Kodu kısa tutmak için yalnızca çağırmamız gereken işlevleri dahil ediyoruz.

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

Web3 (opens in a new tab) kütüphanesini başlatın ve bir Ethereum düğümüne bağlanın.

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

Bu, Python'da bir veri sınıfı oluşturmanın bir yoludur. Contract (opens in a new tab) veri türü, sözleşmeye bağlanmak için kullanılır. (frozen=True) ifadesine dikkat edin. Python'da booleanlar (opens in a new tab) büyük harfle True veya False olarak tanımlanır. Bu veri sınıfı frozen yani alanlar değiştirilemez.

Girintiye dikkat edin. C türevi dillerin (opens in a new tab) aksine, Python blokları belirtmek için girintiyi kullanır. Python yorumlayıcısı, aşağıdaki tanımın bu veri sınıfının bir parçası olmadığını bilir, çünkü veri sınıfı alanlarıyla aynı girintide başlamaz.

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

Decimal (opens in a new tab) türü, ondalık kesirleri doğru bir şekilde işlemek için kullanılır.

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

Bu, Python'da bir fonksiyon tanımlamanın yoludur. Tanım, hala PoolInfo'nun bir parçası olduğunu göstermek için girintilidir.

Bir veri sınıfının parçası olan bir fonksiyonda, ilk parametre her zaman burada çağrılan veri sınıfı örneği olan self'dir. Burada başka bir parametre var, blok numarası.

1 assert block <= w3.eth.block_number, "Blok gelecekte"

Geleceği okuyabilseydik, alım satım için yapay zekaya ihtiyacımız olmazdı.

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

Web3'ten EVM'de bir fonksiyon çağırmanın söz dizimi şudur: <sözleşme nesnesi>.functions.<fonksiyon adı>().call(<parametreler>). Parametreler EVM fonksiyonunun parametreleri (varsa; burada yok) veya blokzincir davranışını değiştirmek için adlandırılmış parametreler (opens in a new tab) olabilir. Burada, içinde çalışmak istediğimiz blok numarasını belirtmek için block_identifier'ı kullanıyoruz.

Sonuç bu yapıdır, dizi biçiminde (opens in a new tab). İlk değer, iki jeton arasındaki döviz kurunun bir fonksiyonudur.

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

Zincir üstü hesaplamaları azaltmak için Uniswap v3 gerçek değişim faktörünü değil, karekökünü saklar. EVM kayan noktalı matematiği veya kesirleri desteklemediğinden, gerçek değer yerine yanıt price&#x22C5296 olur

1 # (token0 başına token1)
2 return 1/(raw_price * self.decimal_factor)

Aldığımız ham fiyat, her bir token1 için aldığımız token0 sayısıdır. Havuzumuzda token0 USDC'dir (ABD doları ile aynı değere sahip bir sabit para) ve token1 WETH (opens in a new tab)dir. Gerçekten istediğimiz değer, tersi değil, WETH başına dolar sayısıdır.

Ondalık faktör, iki jeton için ondalık faktörler (opens in a new tab) arasındaki orandır.

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

Bu veri sınıfı bir teklifi temsil eder: belirli bir zamanda belirli bir varlığın fiyatı. Bu noktada, asset alanı ilgisizdir çünkü tek bir havuz kullanıyoruz ve bu nedenle tek bir varlığa sahibiz. Ancak, daha sonra daha fazla varlık ekleyeceğiz.

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 )
Tümünü göster

Bu işlev bir adresi alır ve o adresteki jeton sözleşmesi hakkında bilgi döndürür. Yeni bir Web3 Contract (opens in a new tab) oluşturmak için, adresi ve ABI'yi w3.eth.contract'a sağlarız.

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 )
Tümünü göster

Bu işlev, belirli bir havuz (opens in a new tab) hakkında ihtiyacımız olan her şeyi döndürür. f"<dize>" sözdizimi biçimlendirilmiş bir dizedir (opens in a new tab).

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

Bir Quote nesnesi alın. block_number için varsayılan değer None'dır (değer yok).

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

Bir blok numarası belirtilmemişse, en son blok numarası olan w3.eth.block_number'ı kullanın. Bu bir if ifadesinin (opens in a new tab) sözdizimidir.

Varsayılanı w3.eth.block_number olarak ayarlamak daha iyi olurmuş gibi görünebilir, ancak bu iyi çalışmaz çünkü işlevin tanımlandığı andaki blok numarası olurdu. Uzun süre çalışan bir aracıda bu bir sorun olurdu.

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 )

İnsanlar ve büyük dil modelleri (LLM'ler) için okunabilir bir biçime biçimlendirmek üzere datetime kütüphanesini (opens in a new tab) kullanın. Değeri iki ondalık basamağa yuvarlamak için Decimal.quantize (opens in a new tab) kullanın.

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

Python'da, list[<tür>] kullanarak yalnızca belirli bir türü içerebilen bir liste (opens in a new tab) tanımlarsınız.

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

Python'da bir for döngüsü (opens in a new tab) genellikle bir liste üzerinde yinelenir. Teklif bulunacak blok numaraları listesi range (opens in a new tab) fonksiyonundan gelir.

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

Her blok numarası için bir Quote nesnesi alın ve bunu quotes listesine ekleyin. Ardından bu listeyi döndürün.

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)
Tümünü göster

Bu, betiğin ana kodudur. Havuz bilgilerini okuyun, on iki teklif alın ve bunları pprint (opens in a new tab) ile yazdırın.

Bir istem oluşturma

Ardından, bu teklif listesini bir LLM için bir isteme dönüştürmemiz ve beklenen bir gelecek değeri elde etmemiz gerekiyor.

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

Çıktı şimdi bir LLM'ye bir istem olacak, şuna benzer:

1Bu teklifler verildiğinde:
2Varlık: WETH/USDC
3 2026-01-20T16:34 3016.21
4 .
5 .
6 .
7 2026-02-01T17:49 2299.10
8
9Varlık: WBTC/WETH
10 2026-01-20T16:34 29.84
11 .
12 .
13 .
14 2026-02-01T17:50 33.46
15
16
172026-02-02T17:56 zamanında WETH/USDC değerinin ne olmasını beklersiniz?
18
19Cevabınızı iki ondalık basamağa yuvarlanmış tek bir sayı olarak,
20başka metin olmadan verin.
Tümünü göster

Burada iki varlık için teklifler olduğuna dikkat edin, WETH/USDC ve WBTC/WETH. Başka bir varlıktan teklif eklemek tahmin doğruluğunu artırabilir.

Bir istemin nasıl göründüğü

Bu istem, LLM istemlerinde oldukça yaygın olan üç bölüm içerir.

  1. Bilgi. LLM'ler eğitimlerinden çok fazla bilgiye sahiptir, ancak genellikle en güncel bilgilere sahip değillerdir. En son teklifleri burada almamızın nedeni budur. Bir isteme bilgi eklemeye geri getirme artırılmış üretim (RAG) (opens in a new tab) denir.

  2. Asıl soru. Bilmek istediğimiz şey bu.

  3. Çıktı biçimlendirme talimatları. Normalde, bir LLM bize bu tahmine nasıl ulaştığının bir açıklamasıyla birlikte bir tahmin verir. Bu insanlar için daha iyidir, ancak bir bilgisayar programının yalnızca sonuca ihtiyacı vardır.

Kod açıklaması

İşte yeni kod.

1from datetime import datetime, timezone, timedelta

LLM'e bir tahmin istediğimiz zamanı sağlamamız gerekiyor. Gelecekte "n dakika/saat/gün" zamanını elde etmek için timedelta sınıfını (opens in a new tab) kullanırız.

1# Okuduğumuz havuzların adresleri
2WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
3WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD")

Okumamız gereken iki havuzumuz var.

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 gelecekte"
10 sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0])
11 raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (token0 başına token1)
12 if self.reverse:
13 return 1/(raw_price * self.decimal_factor)
14 else:
15 return raw_price * self.decimal_factor
Tümünü göster

WETH/USDC havuzunda, bir adet token1 (WETH) satın almak için kaç adet token0 (USDC) gerektiğini bilmek istiyoruz. WETH/WBTC havuzunda, bir adet token0 (WBTC, sarılmış Bitcoin'dir) satın almak için kaç adet token1 (WETH) gerektiğini bilmek istiyoruz. Havuzun oranının tersine çevrilmesi gerekip gerekmediğini izlememiz gerekiyor.

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 )
Tümünü göster

Bir havuzun tersine çevrilmesi gerekip gerekmediğini bilmek için bunu read_pool'a girdi olarak almamız gerekiyor. Ayrıca, varlık sembolünün doğru bir şekilde ayarlanması gerekir.

<a> if <b> else <c> söz dizimi, C türevi bir dilde <b> ? şeklinde olan üçlü koşullu operatörün (opens in a new tab) Python eşdeğeridir. <a> : <c>.

1def format_quotes(quotes: list[Quote]) -> str:
2 result = f"Varlık: {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

Bu işlev, hepsinin aynı varlığa uygulandığını varsayarak bir Quote nesne listesini biçimlendiren bir dize oluşturur.

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

Python'da çok satırlı dize sabitleri (opens in a new tab) """ .... şeklinde yazılır. """.

1Bu teklifler verildiğinde:
2{
3 functools.reduce(lambda acc, q: acc + '\n' + q,
4 map(lambda q: format_quotes(q), quotes))
5}

Burada, her teklif listesi için format_quotes ile bir dize oluşturmak için MapReduce (opens in a new tab) desenini kullanırız, ardından bunları istemde kullanılmak üzere tek bir dizede birleştiririz.

1{expected_time} zamanında {asset}in değerin ne olmasını beklersiniz?
2
3Cevabınızı iki ondalık basamağa yuvarlanmış tek bir sayı olarak,
4başka bir metin olmadan verin.
5 """

İstemin geri kalanı beklendiği gibidir.

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)
Tümünü göster

İki havuzu gözden geçirin ve her ikisinden de teklifler alın.

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))

Tahmin istediğimiz gelecekteki zaman noktasını belirleyin ve istemi oluşturun.

Bir LLM ile arayüz oluşturma

Ardından, gerçek bir LLM'yi sorgularız ve beklenen bir gelecek değeri alırız. Bu programı OpenAI kullanarak yazdım, bu yüzden farklı bir sağlayıcı kullanmak isterseniz, onu ayarlamanız gerekecektir.

  1. Bir OpenAI hesabı (opens in a new tab) edinin

  2. Hesaba para yatırın (opens in a new tab)—bu yazının yazıldığı sırada asgari tutar 5$'dır

  3. Bir API anahtarı oluşturun (opens in a new tab)

  4. Komut satırında, programınızın kullanabilmesi için API anahtarını dışa aktarın

    1export OPENAI_API_KEY=sk-<anahtarın geri kalanı buraya gelecek>
  5. Ajanı kullanıma alın ve çalıştırın

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

İşte yeni kod.

1from openai import OpenAI
2
3open_ai = OpenAI() # İstemci, OPENAI_API_KEY ortam değişkenini okur

OpenAI API'sini içe aktarın ve başlatın.

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)

Yanıtı oluşturmak için OpenAI API'sini (open_ai.chat.completions.create) çağırın.

1expected_price = Decimal(response.choices[0].message.content.strip())
2current_price = wethusdc_quotes[-1].price
3
4print ("Mevcut fiyat:", wethusdc_quotes[-1].price)
5print(f"{future_time} zamanında, beklenen fiyat: {expected_price} USD")
6
7if (expected_price > current_price):
8 print(f"Al, fiyatın {expected_price - current_price} USD artmasını bekliyorum")
9else:
10 print(f"Sat, fiyatın {current_price - expected_price} USD düşmesini bekliyorum")
Tümünü göster

Fiyatı çıktılayın ve bir al veya sat tavsiyesi verin.

Tahminleri test etme

Artık tahminler üretebildiğimize göre, yararlı tahminler üretip üretmediğimizi değerlendirmek için geçmiş verileri de kullanabiliriz.

1uv run test-predictor.py

Beklenen sonuç şuna benzer:

12026-01-05T19:50 için tahmin: tahmin edilen 3138.93 USD, gerçek 3218.92 USD, hata 79.99 USD
22026-01-06T19:56 için tahmin: tahmin edilen 3243.39 USD, gerçek 3221.08 USD, hata 22.31 USD
32026-01-07T20:02 için tahmin: tahmin edilen 3223.24 USD, gerçek 3146.89 USD, hata 76.35 USD
42026-01-08T20:11 için tahmin: tahmin edilen 3150.47 USD, gerçek 3092.04 USD, hata 58.43 USD
5.
6.
7.
82026-01-31T22:33 için tahmin: tahmin edilen 2637.73 USD, gerçek 2417.77 USD, hata 219.96 USD
92026-02-01T22:41 için tahmin: tahmin edilen 2381.70 USD, gerçek 2318.84 USD, hata 62.86 USD
102026-02-02T22:49 için tahmin: tahmin edilen 2234.91 USD, gerçek 2349.28 USD, hata 114.37 USD
1129 tahmin üzerinden ortalama tahmin hatası: 83.87103448275862068965517241 USD
12Tavsiye başına ortalama değişim: 4.787931034482758620689655172 USD
13Değişikliklerin standart sapması: 104.42 USD
14Kârlı günler: %51.72
15Zararlı günler: %48.28
Tümünü göster

Test edicinin çoğu aracı ile aynıdır, ancak burada yeni veya değiştirilmiş kısımlar bulunmaktadır.

1CYCLES_FOR_TEST = 40 # Geriye dönük test için kaç döngü test edeceğimiz
2
3# Çok sayıda teklif al
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)
Tümünü göster

CYCLES_FOR_TEST (burada 40 olarak belirtilmiştir) gün geriye bakıyoruz.

1# Tahminler oluşturun ve bunları gerçek geçmişe karşı kontrol edin
2
3total_error = Decimal(0)
4changes = []

İlgilendiğimiz iki tür hata var. İlki, total_error, sadece tahmin edicinin yaptığı hataların toplamıdır.

İkincisini, changes'i anlamak için aracının amacını hatırlamamız gerekir. WETH/USDC oranını (ETH fiyatı) tahmin etmek değil. Satış ve alım önerileri yayınlamak içindir. Fiyat şu anda 2000$ ise ve yarın için 2010$ tahmin ediyorsa, gerçek sonucun 2020$ olması ve fazladan para kazanmamız umrumuzda olmaz. Ancak 2010 dolar tahmin edip bu tavsiyeye göre ETH alırsak ve fiyat 1990 dolara düşerse bunu dikkate alırız.

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

Yalnızca tüm geçmişin (tahmin için kullanılan değerler ve karşılaştırılacak gerçek dünya değeri) mevcut olduğu durumlara bakabiliriz. Bu, en yeni durumun CYCLES_BACK önce başlayan durum olması gerektiği anlamına gelir.

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

Aracının kullandığı sayıyla aynı sayıda örnek almak için dilimleri (opens in a new tab) kullanın. Buradan bir sonraki segmente kadar olan kod, aracıda sahip olduğumuz tahmin alma koduyla aynıdır.

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

Tahmini fiyatı, gerçek fiyatı ve tahmin anındaki fiyatı alın. Tavsiyenin alım mı yoksa satım mı olduğunu belirlemek için tahmin anındaki fiyata ihtiyacımız var.

1 error = abs(predicted_price - real_price)
2 total_error += error
3 print (f"{prediction_time} için tahmin: tahmin edilen {predicted_price} USD, gerçek {real_price} USD, hata {error} USD")

Hatayı hesaplayın ve toplama ekleyin.

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)

changes için, bir ETH almanın veya satmanın parasal etkisini istiyoruz. Bu nedenle önce tavsiyeyi belirlememiz, ardından gerçek fiyatın nasıl değiştiğini ve tavsiyenin para kazandırıp kazandırmadığını (pozitif değişim) veya para kaybettirip kaybettirmediğini (negatif değişim) değerlendirmemiz gerekiyor.

1print (f"{len(wethusdc_quotes)-CYCLES_BACK} tahmin üzerinden ortalama tahmin hatası: {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"Tavsiye başına ortalama değişim: {mean_change} USD")
6var = sum((x - mean_change) ** 2 for x in changes) / length_changes
7print (f"Değişikliklerin standart sapması: {var.sqrt().quantize(Decimal("0.01"))} USD")

Sonuçları raporlayın.

1print (f"Kârlı günler: {len(list(filter(lambda x: x > 0, changes)))/length_changes:.2%}")
2print (f"Zararlı günler: {len(list(filter(lambda x: x < 0, changes)))/length_changes:.2%}")

Kârlı günlerin ve maliyetli günlerin sayısını saymak için filter (opens in a new tab) kullanın. Sonuç bir filtre nesnesidir, uzunluğunu almak için bunu bir listeye dönüştürmemiz gerekir.

İşlemleri gönderme

Şimdi gerçekten işlemleri göndermemiz gerekiyor. Ancak, sistem kanıtlanmadan önce bu noktada gerçek para harcamak istemiyorum. Bunun yerine, ana ağın yerel bir çatalını oluşturacağız ve o ağda "alım satım" yapacağız.

Yerel bir çatal oluşturma ve alım satımı etkinleştirme adımları şunlardır.

  1. Foundry (opens in a new tab) yükleyin

  2. anvil (opens in a new tab) başlatın

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

    anvil, Foundry için varsayılan URL olan http://localhost:8545 (opens in a new tab) adresinde dinliyor, bu nedenle blokzinciri değiştirmek için kullandığımız cast komutu (opens in a new tab) için URL belirtmemize gerek yok.

  3. anvil'de çalıştırıldığında, ETH'ye sahip on test hesabı vardır—ilk hesap için ortam değişkenlerini ayarlayın

    1PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
    2ADDRESS=`cast wallet address $PRIVATE_KEY`
  4. Bunlar kullanmamız gereken sözleşmeler. SwapRouter (opens in a new tab), aslında alım satım yapmak için kullandığımız Uniswap v3 sözleşmesidir. Doğrudan havuz aracılığıyla alım satım yapabilirdik, ancak bu çok daha kolay.

    Alttaki iki değişken, WETH ve USDC arasında takas yapmak için gereken Uniswap v3 yollarıdır.

    1WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
    2USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
    3POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640
    4SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564
    5WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
    6USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
  5. Test hesaplarının her birinde 10.000 ETH bulunur. Alım satım için 1000 WETH elde etmek üzere 1000 ETH sarmalamak için WETH sözleşmesini kullanın.

    1cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY
  6. 500 WETH'yi USDC ile takas etmek için SwapRouter'ı kullanın.

    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

    approve çağrısı, SwapRouter'ın jetonlarımızın bir kısmını harcamasına izin veren bir ödenek oluşturur. Sözleşmeler olayları izleyemez, bu nedenle jetonları doğrudan SwapRouter sözleşmesine aktarırsak, ödendiğini bilemez. Bunun yerine, SwapRouter sözleşmesinin belirli bir miktarı harcamasına izin veririz ve ardından SwapRouter bunu yapar. Bu, SwapRouter tarafından çağrılan bir işlev aracılığıyla yapılır, böylece başarılı olup olmadığını bilir.

  7. Her iki jetondan da yeterli miktarda olduğunu doğrulayın.

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

Artık WETH ve USDC'ye sahip olduğumuza göre, aracıyı gerçekten çalıştırabiliriz.

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

Çıktı şuna benzer olacaktır:

1(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py
2Mevcut fiyat: 1843.16
32026-02-06T23:07 zamanında beklenen fiyat: 1724.41 USD
4Alım satım öncesi hesap bakiyeleri:
5USDC Bakiyesi: 927301.578272
6WETH Bakiyesi: 500
7Sat, fiyatın 118.75 USD düşmesini bekliyorum
8Onay işlemi gönderildi: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f
9Onay işlemi çıkarıldı.
10Satış işlemi gönderildi: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf
11Satış işlemi çıkarıldı.
12Alım satım sonrası hesap bakiyeleri:
13USDC Bakiyesi: 929143.797116
14WETH Bakiyesi: 499
Tümünü göster

Gerçekten kullanmak için birkaç küçük değişiklik yapmanız gerekir.

    1. satırda, MAINNET_URL'yi https://eth.drpc.org gibi gerçek bir erişim noktasına değiştirin
    1. satırda, PRIVATE_KEY'i kendi özel anahtarınızla değiştirin
  • Çok zengin değilseniz ve kanıtlanmamış bir aracı için her gün 1 ETH alıp satamıyorsanız, WETH_TRADE_AMOUNT'ı azaltmak için 29. satırı değiştirmek isteyebilirsiniz

Kod açıklaması

İşte yeni kod.

1SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564")
2WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
3USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
4PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
  1. adımda kullandığımız aynı değişkenler.
1WETH_TRADE_AMOUNT=1

Alım satım yapılacak miktar.

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

Gerçekten alım satım yapmak için approve fonksiyonuna ihtiyacımız var. Ayrıca öncesi ve sonrası bakiyeleri göstermek istiyoruz, bu yüzden balanceOf'a da ihtiyacımız var.

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

SwapRouter ABI'sinde sadece exactInput'a ihtiyacımız var. İlgili bir fonksiyon olan exactOutput'u tam olarak bir WETH satın almak için kullanabilirdik, ancak basitlik açısından her iki durumda da sadece exactInput kullanıyoruz.

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

account (opens in a new tab) ve SwapRouter sözleşmesi için Web3 tanımları.

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 }

İşlem parametreleri. Burada bir fonksiyona ihtiyacımız var çünkü nonce (opens in a new tab) her seferinde değişmelidir.

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

SwapRouter için bir jeton ödeneğini onaylayın.

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)

Web3'te bu şekilde bir işlem gönderiyoruz. İlk olarak, işlemi oluşturmak için Sözleşme nesnesini (opens in a new tab) kullanırız. Ardından, işlemi PRIVATE_KEY kullanarak imzalamak için web3.eth.account.sign_transaction (opens in a new tab) kullanırız. Son olarak, işlemi göndermek için w3.eth.send_raw_transaction (opens in a new tab) kullanırız.

1 print(f"Onay işlemi gönderildi: {tx_hash.hex()}")
2 w3.eth.wait_for_transaction_receipt(tx_hash)
3 print("Onay işlemi çıkarıldı.")

w3.eth.wait_for_transaction_receipt (opens in a new tab), işlem çıkarılana kadar bekler. Gerekirse makbuzu döndürür.

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}

Bunlar WETH satarken kullanılan parametrelerdir.

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 }

SELL_PARAMS'ın aksine, satın alma parametreleri değişebilir. Giriş tutarı, quote'da mevcut olan 1 WETH'nin maliyetidir.

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"Alım işlemi gönderildi: {tx_hash.hex()}")
8 w3.eth.wait_for_transaction_receipt(tx_hash)
9 print("Alım işlemi çıkarıldı.")
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"Satış işlemi gönderildi: {tx_hash.hex()}")
19 w3.eth.wait_for_transaction_receipt(tx_hash)
20 print("Satış işlemi çıkarıldı.")
Tümünü göster

buy() ve sell() fonksiyonları neredeyse aynıdır. Önce SwapRouter için yeterli bir ödenek onaylarız ve sonra onu doğru yol ve miktar ile çağırırız.

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} Bakiye: {Decimal(token0_balance) / Decimal(10 ** wethusdc_pool.token0.decimals)}")
6 print(f"{wethusdc_pool.token1.symbol} Bakiye: {Decimal(token1_balance) / Decimal(10 ** wethusdc_pool.token1.decimals)}")

Her iki para birimindeki kullanıcı bakiyelerini rapor edin.

1print("Alım satım öncesi hesap bakiyeleri:")
2balances()
3
4if (expected_price > current_price):
5 print(f"Al, fiyatın {expected_price - current_price} USD artmasını bekliyorum")
6 buy(wethusdc_quotes[-1])
7else:
8 print(f"Sat, fiyatın {current_price - expected_price} USD düşmesini bekliyorum")
9 sell()
10
11print("Alım satım sonrası hesap bakiyeleri:")
12balances()
Tümünü göster

Bu aracı şu anda yalnızca bir kez çalışır. Ancak, crontab (opens in a new tab) üzerinden çalıştırarak veya 368-400 satırlarını bir döngüye alıp time.sleep (opens in a new tab) kullanarak bir sonraki döngü zamanı gelene kadar bekleyerek sürekli çalışmasını sağlayabilirsiniz.

Olası iyileştirmeler

Bu tam bir üretim sürümü değildir; sadece temelleri öğretmek için bir örnektir. İşte iyileştirme için bazı fikirler.

Daha akıllı ticaret

Aracının ne yapacağına karar verirken göz ardı ettiği iki önemli gerçek var.

  • Beklenen değişimin büyüklüğü. Aracı, düşüşün büyüklüğüne bakılmaksızın, fiyatın düşmesi bekleniyorsa sabit bir miktarda WETH satar. Tartışmalı olarak, küçük değişiklikleri görmezden gelmek ve fiyatın ne kadar düşmesini beklediğimize göre satış yapmak daha iyi olurdu.
  • Mevcut portföy. Portföyünüzün %10'u WETH'de ise ve fiyatın artacağını düşünüyorsanız, daha fazla satın almak muhtemelen mantıklıdır. Ancak portföyünüzün %90'ı WETH'de ise, yeterince riske maruz kalmış olabilirsiniz ve daha fazla satın almanıza gerek yoktur. Fiyatın düşmesini bekliyorsanız, tam tersi geçerlidir.

Ticaret stratejinizi gizli tutmak isterseniz ne olur?

Yapay zeka satıcıları, LLM'lerine gönderdiğiniz sorguları görebilir, bu da aracınızla geliştirdiğiniz dahi ticaret sistemini açığa çıkarabilir. Çok fazla insanın kullandığı bir alım satım sistemi değersizdir çünkü siz satın almak istediğinizde çok fazla insan satın almaya çalışır (ve fiyat yükselir) ve siz satmak istediğinizde çok fazla insan satmaya çalışır (ve fiyat düşer).

Bu sorunu önlemek için, örneğin LM-Studio (opens in a new tab) kullanarak yerel olarak bir LLM çalıştırabilirsiniz.

Yapay zeka botundan yapay zeka aracısına

Bunun bir yapay zeka botu değil, bir yapay zeka aracısı olduğuna dair iyi bir argüman sunabilirsiniz. Önceden tanımlanmış bilgilere dayanan nispeten basit bir strateji uygular. Örneğin, Uniswap v3 havuzlarının bir listesini ve en son değerlerini sağlayarak ve hangi kombinasyonun en iyi tahminsel değere sahip olduğunu sorarak kendi kendine geliştirmeyi etkinleştirebiliriz.

Kayma koruması

Şu anda kayma koruması (opens in a new tab) yoktur. Mevcut teklif 2000$ ise ve beklenen fiyat 2100$ ise, aracı satın alacaktır. Ancak, aracı satın almadan önce maliyet 2200$'a yükselirse, artık satın almanın bir anlamı kalmaz.

Kayma korumasını uygulamak için, agent.py (opens in a new tab) dosyasının 325 ve 334. satırlarında bir amountOutMinimum değeri belirtin.

Sonuç

Umarım, artık yapay zeka aracılarıyla başlamak için yeterince bilgiye sahipsinizdir. Bu, konunun kapsamlı bir incelemesi değildir; buna adanmış bütün kitaplar var, ancak bu başlamanız için yeterli. İyi şanslar!

Çalışmalarımdan daha fazlası için buraya bakın (opens in a new tab).

Sayfanın son güncellenmesi: 10 Şubat 2026

Bu rehber yararlı oldu mu?