Ana içeriğe atla

Ethereum üzerinde kendi yapay zeka alım satım ajanınızı yapın

yapay zeka
alım satım
ajan
Python
Orta
Ori Pomerantz
13 Şubat 2026
21 dakikalık okuma

Bu eğitimde basit bir yapay zeka alım satım ajanı oluşturmayı öğreneceksiniz. Bu ajan şu adımları kullanarak çalışır:

  1. Bir Token'ın mevcut ve geçmiş fiyatlarının yanı sıra potansiyel olarak ilgili diğer bilgileri okumak
  2. Bu bilgilerle ve bunların nasıl ilgili olabileceğini açıklayan arka plan bilgileriyle bir sorgu oluşturmak
  3. Sorguyu göndermek ve öngörülen bir fiyatı geri almak
  4. Tavsiyeye dayalı olarak alım satım yapmak
  5. Beklemek ve tekrarlamak

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

Neden bunu yapmalısınız?

Otomatik alım satım ajanları, geliştiricilerin bir alım satım stratejisi seçmesine ve yürütmesine olanak tanır. Yapay zeka ajanları, geliştiricinin kullanmayı düşünmediği bilgi ve algoritmaları potansiyel olarak kullanarak daha karmaşık ve dinamik alım satım stratejilerine olanak tanır.

Araçlar

Bu eğitim, fiyat teklifleri 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)'dır, bu yüzden 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.

Blokzincir üzerinde alım satım

Ethereum üzerinde Token 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 fiyat teklifleri (Token'ların göreceli değerlerini görmek için) hem de alım satımlar için kullanabileceğimiz yaygın olarak kullanılan bir DEX'tir.

OpenAI

Büyük bir dil modeli (LLM) için OpenAI (opens in a new tab) ile başlamayı seçtim. Bu eğitimdeki uygulamayı çalıştırmak için API erişimi için ödeme yapmanız gerekecektir. Minimum 5 dolarlık ö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 şunlardır:

  1. Henüz sahip değilseniz, Python (opens in a new tab)'ı indirin ve kurun.

  2. GitHub deposunu klonlayın.

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

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

    uv sync
    
  5. Sanal ortamı etkinleştirin.

    source .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 >>> isteminde girebilirsiniz; bir dosya oluşturmanıza gerek yoktur.

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

Blokzincirden okuma

Bir sonraki adım Blokzincir'den okumaktı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.

git checkout 02-read-quote
uv 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.

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

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

Python'un print işlevini, çı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.

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

Ana Ağ'a ulaşmak için bir URL. Hizmet olarak Düğüm sağlayıcılarından bir tane alabilir veya Chainlist (opens in a new tab)'te tanıtılanlardan birini kullanabilirsiniz.

BLOCK_TIME_SECONDS = 12
MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS)
HOUR_BLOCKS = MINUTE_BLOCKS * 60
DAY_BLOCKS = HOUR_BLOCKS * 24

Bir Ethereum Ana Ağı bloğu tipik olarak her 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çisi çöktüğünde, o Blok atlanır ve bir sonraki Blok için süre 24 saniyedir. Bir zaman damgası için tam bloğu almak isteseydik, ikili arama (binary search) (opens in a new tab) kullanırdık. Ancak, bu bizim amaçlarımız için yeterince yakındır. Geleceği tahmin etmek kesin bir bilim değildir.

CYCLE_BLOCKS = DAY_BLOCKS

Döngünün boyutu. Fiyat tekliflerini her döngüde bir kez inceliyoruz ve bir sonraki döngünün sonundaki değeri tahmin etmeye çalışıyoruz.

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

Fiyat teklifi değerleri, 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 (opens in a new tab) Adresindeki Uniswap 3 USDC/WETH havuzundan alınır. Bu Adres zaten sağlama toplamı (checksum) formundadır, ancak kodu yeniden kullanılabilir hale getirmek için Web3.to_checksum_address (opens in a new tab) kullanmak daha iyidir.

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.

w3 = 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.

@dataclass(frozen=True)
class ERC20Token:
    address: str
    symbol: str
    decimals: int
    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) kısmına dikkat edin. Python'da boole değerleri (opens in a new tab) büyük harfle True veya False olarak tanımlanır. Bu veri sınıfı frozen olarak ayarlanmıştır, yani alanlar değiştirilemez.

Girintiye dikkat edin. C türevi dillerin (opens in a new tab) aksine, Python blokları belirtmek için girinti 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.

@dataclass(frozen=True)
class PoolInfo:
    address: str
    token0: ERC20Token
    token1: ERC20Token
    contract: Contract
    asset: str
    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.

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

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

Bir veri sınıfının parçası olan bir işlevde ilk parametre her zaman self'dir, yani burayı çağıran veri sınıfı örneğidir. Burada başka bir parametre daha var, Blok numarası.

        assert block <= w3.eth.block_number, "Block is in the future"

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

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

Web3'ten EVM üzerinde bir işlev çağırmanın sözdizimi şudur: <contract object>.functions.<function name>().call(<parameters>). Parametreler, EVM işlevinin parametreleri (varsa; burada yok) veya Blokzincir davranışını değiştirmek için adlandırılmış parametreler (opens in a new tab) olabilir. Burada, çalışmak istediğimiz Blok numarasını belirtmek için block_identifier kullanıyoruz.

Sonuç, dizi formundaki bu yapıdır (struct) (opens in a new tab). İlk değer, iki Token arasındaki döviz kurunun bir işlevidir.

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

Zincir içi hesaplamaları azaltmak için Uniswap v3, gerçek değişim faktörünü değil, onun karekökünü depolar. EVM kayan nokta matematiğini veya kesirleri desteklemediğinden, gerçek değer yerine yanıt şudur: price296

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

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

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

@dataclass(frozen=True)
class Quote:
    timestamp: str
    price: Decimal
    asset: str

Bu veri sınıfı bir fiyat teklifini temsil eder: belirli bir varlığın belirli bir zamandaki fiyatı. Bu noktada, asset alanı alakasızdır çünkü tek bir havuz kullanıyoruz ve bu nedenle tek bir varlığımız var. Ancak, daha sonra daha fazla varlık ekleyeceğiz.

Bu işlev bir Adres alır ve o Adresteki Token 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.

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

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

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

    if block_number is None:
        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 ifadesi (opens in a new tab) için sözdizimidir.

Varsayılanı sadece 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 ajanda bu bir sorun olurdu.

    block = w3.eth.get_block(block_number)
    price = pool.get_price(block_number)
    return Quote(
        timestamp=datetime.fromtimestamp(block.timestamp, timezone.utc).isoformat(),
        price=price.quantize(Decimal("0.01")),
        asset=pool.asset
    )

İnsanlar ve büyük dil modelleri (YDM'ler) için okunabilir bir biçime dönüştürmek ü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.

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

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

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

Python'da bir for döngüsü (opens in a new tab) tipik olarak bir liste üzerinde yinelenir. Fiyat tekliflerini bulmak için Blok numaralarının listesi range (opens in a new tab)'dan gelir.

        quote = get_quote(pool, block)
        quotes.append(quote)
    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.

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

Bir istem oluşturma

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

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

Çıktı artık bir YDM'ye yönelik, şuna benzer bir istem olacaktır:

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

Bir istem neye benzer

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

  1. Bilgi. YDM'ler eğitimlerinden elde ettikleri pek çok bilgiye sahiptir, ancak genellikle en güncel bilgilere sahip değillerdir. Burada en son fiyat tekliflerini almamızın nedeni budur. Bir isteme bilgi eklemeye geri getirmeyle artırılmış üretim (RAG) (opens in a new tab) denir.

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

  3. Çıktı biçimlendirme talimatları. Normalde, bir YDM bize bu tahmine nasıl ulaştığına dair bir açıklama ile birlikte bir tahmin verecektir. Bu insanlar için daha iyidir, ancak bir bilgisayar programının sadece sonuca ihtiyacı vardır.

Kod açıklaması

İşte yeni kod.

from datetime import datetime, timezone, timedelta

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

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

Okumamız gereken iki havuzumuz var.

WETH/USDC havuzunda, bir token1 (WETH) satın almak için kaç tane token0 (USDC) gerektiğini bilmek istiyoruz. WETH/WBTC havuzunda, bir token0 (WBTC, yani sarılmış Bitcoin) satın almak için kaç tane token1 (WETH) gerektiğini bilmek istiyoruz. Havuzun oranının tersine çevrilmesi gerekip gerekmediğini takip etmeliyiz.

Bir havuzun tersine çevrilmesi gerekip gerekmediğini bilmek için, bunu read_pool'ye girdi olarak almalıyız. Ayrıca, varlık sembolünün doğru ayarlanması gerekir.

<a> if <b> else <c> sözdizimi, C türevi bir dilde <b> ? <a> : <c> olacak olan üçlü koşullu operatörün (ternary conditional operator) (opens in a new tab) Python'daki karşılığıdır.

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

Bu işlev, hepsinin aynı varlık için geçerli olduğunu varsayarak bir Quote nesneleri listesini biçimlendiren bir dize oluşturur.

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

Python'da çok satırlı dize değişmezleri (opens in a new tab) """ .... """ olarak yazılır.

Given these quotes:
{
    functools.reduce(lambda acc, q: acc + '\n' + q,
        map(lambda q: format_quotes(q), quotes))
}

Burada, format_quotes ile her fiyat teklifi listesi için bir dize oluşturmak üzere MapReduce (opens in a new tab) modelini kullanıyoruz, ardından bunları istemde kullanmak üzere tek bir dizeye indirgiyoruz.

What would you expect the value for {asset} to be at time {expected_time}?

Provide your answer as a single number rounded to two decimal places,
without any other text.
    """

İstemin geri kalanı beklendiği gibidir.

İki havuzu inceleyin ve her ikisinden de fiyat teklifleri alın.

future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16]

print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset))

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

Bir YDM ile arayüz oluşturma

Ardından, gerçek bir YDM'ye istem göndeririz ve beklenen bir gelecek değeri alırız. Bu programı OpenAI kullanarak yazdım, bu nedenle 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)—yazının yazıldığı sıradaki minimum tutar 5 dolardı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

    export OPENAI_API_KEY=sk-<the rest of the key goes here>
    
  5. Ajanı kontrol edin ve çalıştırın

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

İşte yeni kod.

from openai import OpenAI

open_ai = OpenAI()  # İstemci, OPENAI_API_KEY ortam değişkenini okur

OpenAI API'sini içe aktarın ve örneğini oluşturun.

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

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

Fiyatı çıktı olarak verin ve bir alım veya satım tavsiyesi sağlayın.

Tahminleri test etme

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

uv run test-predictor.py

Beklenen sonuç şuna benzer:

Test edicinin çoğu ajanla aynıdır, ancak işte yeni veya değiştirilmiş olan kısımlar.

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

# Tahminler oluştur ve bunları gerçek geçmişle karşılaştır

total_error = Decimal(0)
changes = []

İlgilendiğimiz iki tür hata vardır. İlki, total_error, basitçe tahmin edicinin yaptığı hataların toplamıdır.

İkincisini, changes'yi anlamak için ajanın amacını hatırlamamız gerekir. Amacı WETH/USDC oranını (ETH fiyatı) tahmin etmek değildir. Alım ve satım tavsiyeleri vermektir. Fiyat şu anda 2000$ ise ve yarın 2010$ olacağını tahmin ediyorsa, gerçek sonuç 2020$ olursa ve ekstra para kazanırsak bunu umursamayız. Ancak 2010$ tahmin edip bu tavsiyeye dayanarak ETH satın aldıysa ve fiyat 1990$'a düşerse bunu umursarız.

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

Yalnızca tam 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.

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

Ajanın kullandığı sayıyla aynı sayıda örnek elde etmek için dilimleri (slices) (opens in a new tab) kullanın. Burası ile bir sonraki bölüm arasındaki kod, ajanda sahip olduğumuz tahmin alma kodunun aynısıdır.

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

Tahmin edilen 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.

    error = abs(predicted_price - real_price)
    total_error += error
    print (f"Prediction for {prediction_time}: predicted {predicted_price} USD, real {real_price} USD, error {error} USD")

Hatayı hesaplayın ve toplama ekleyin.

    recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell'
    price_increase = real_price - prediction_time_price
    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 yüzden ö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 gerekir.

print (f"Mean prediction error over {len(wethusdc_quotes)-CYCLES_BACK} predictions: {total_error / Decimal(len(wethusdc_quotes)-CYCLES_BACK)} USD")

length_changes = Decimal(len(changes))
mean_change = sum(changes, Decimal(0)) / length_changes
print (f"Mean change per recommendation: {mean_change} USD")
var = sum((x - mean_change) ** 2 for x in changes) / length_changes
print (f"Standard variance of changes: {var.sqrt().quantize(Decimal("0.01"))} USD")

Sonuçları raporlayın.

print (f"Profitable days: {len(list(filter(lambda x: x > 0, changes)))/length_changes:.2%}")
print (f"Losing days: {len(list(filter(lambda x: x < 0, changes)))/length_changes:.2%}")

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

İş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 çatallanmasını oluşturacağız ve o ağ üzerinde "alım satım" yapacağız.

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

  1. Foundry (opens in a new tab) kurun

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

    anvil --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) üzerinde dinliyor, bu nedenle Blokzincir'i manipüle etmek için kullandığımız cast komutu (opens in a new tab) için URL'yi belirtmemize gerek yoktur.

  3. anvil içinde çalışırken, ETH'ye sahip on test Hesabı vardır—ilki için ortam değişkenlerini ayarlayın

    PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
    ADDRESS=`cast wallet address $PRIVATE_KEY`
    
  4. Bunlar kullanmamız gereken Sözleşmelerdir. SwapRouter (opens in a new tab), gerçekten alım satım yapmak için kullandığımız Uniswap v3 Sözleşmesidir. Doğrudan havuz üzerinden alım satım yapabilirdik, ancak bu çok daha kolaydır.

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

    WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
    USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
    POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640
    SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564
    WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
    USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
    
  5. Test Hesaplarının her birinde 10.000 ETH vardır. Alım satım için 1000 WETH elde etmek üzere 1000 ETH'yi sarmak için WETH Sözleşmesini kullanın.

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

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

    approve çağrısı, SwapRouter'ın Token'larımızın bir kısmını harcamasına izin veren bir harcama izni oluşturur. Sözleşmeler olayları izleyemez, bu nedenle Token'ları doğrudan SwapRouter Sözleşmesine transfer edersek, ö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 Token'dan da yeterince sahip olduğunuzu doğrulayın.

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

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

git checkout 05-trade
uv run agent.py

Çıktı şuna benzer olacaktır:

Gerçekten kullanmak için birkaç küçük değişikliğe ihtiyacınız var.

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

Kod açıklaması

İşte yeni kod.

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

Alım satım yapılacak miktar.

ERC20_ABI = [
    { "name": "symbol", ... },
    { "name": "decimals", ... },
    { "name": "balanceOf", ...},
    { "name": "approve", ...}
]

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

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

SwapRouter ABI'sinde sadece exactInput'a ihtiyacımız var. Tam olarak bir WETH satın almak için kullanabileceğimiz ilgili bir işlev olan exactOutput vardır, ancak basitlik için her iki durumda da sadece exactInput kullanıyoruz.

account = w3.eth.account.from_key(PRIVATE_KEY)
swap_router = w3.eth.contract(
    address=SWAP_ROUTER_ADDRESS,
    abi=SWAP_ROUTER_ABI
)

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

def txn_params() -> dict:
    return {
        "from": account.address,
        "value": 0,
        "gas": 300000,
        "nonce": w3.eth.get_transaction_count(account.address),
    }

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

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

SwapRouter için bir Token harcama iznini onaylayın.

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

Web3'te bir işlemi bu şekilde göndeririz. Önce işlemi oluşturmak için Contract nesnesini (opens in a new tab) kullanırız. Ardından, PRIVATE_KEY kullanarak işlemi 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.

    print(f"Approve transaction sent: {tx_hash.hex()}")
    w3.eth.wait_for_transaction_receipt(tx_hash)
    print("Approve transaction mined.")

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

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

Bunlar WETH satarken kullanılan parametrelerdir.

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

SELL_PARAMS'nın aksine, alım parametreleri değişebilir. Girdi miktarı, quote içinde mevcut olduğu gibi 1 WETH'nin maliyetidir.

buy() ve sell() işlevleri neredeyse aynıdır. Önce SwapRouter için yeterli bir harcama iznini onaylarız ve ardından onu doğru yol ve miktarla çağırırız.

def balances():
    token0_balance = wethusdc_pool.token0.contract.functions.balanceOf(account.address).call()
    token1_balance = wethusdc_pool.token1.contract.functions.balanceOf(account.address).call()

    print(f"{wethusdc_pool.token0.symbol} Balance: {Decimal(token0_balance) / Decimal(10 ** wethusdc_pool.token0.decimals)}")
    print(f"{wethusdc_pool.token1.symbol} Balance: {Decimal(token1_balance) / Decimal(10 ** wethusdc_pool.token1.decimals)}")

Her iki para biriminde de kullanıcı bakiyelerini raporlayın.

Bu ajan şu anda yalnızca bir kez çalışır. Ancak, onu crontab (opens in a new tab) üzerinden çalıştırarak veya 368-400 satırlarını bir döngüye sarıp bir sonraki döngü zamanı gelene kadar beklemek için time.sleep (opens in a new tab) kullanarak sürekli çalışacak şekilde değiştirebilirsiniz.

Olası iyileştirmeler

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

Daha akıllı alım satım

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

  • Beklenen değişimin büyüklüğü. Ajan, düşüşün büyüklüğünden bağımsız olarak, fiyatın düşmesi bekleniyorsa sabit miktarda WETH satar. Muhtemelen, küçük değişiklikleri göz ardı etmek ve fiyatın ne kadar düşmesini beklediğimize bağlı olarak satmak daha iyi olacaktır.
  • Mevcut portföy. Portföyünüzün %10'u WETH'de ise ve fiyatın artacağını düşünüyorsanız, muhtemelen daha fazla satın almak mantıklıdır. Ancak portföyünüzün %90'ı WETH'de ise, yeterince maruz kalmış olabilirsiniz ve daha fazla satın almanıza gerek yoktur. Fiyatın düşmesini bekliyorsanız bunun tersi geçerlidir.

Ya alım satım stratejinizi gizli tutmak isterseniz?

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

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

Yapay zeka botundan yapay zeka ajanına

Bunun bir yapay zeka ajanı değil, bir yapay zeka botu 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 tahmin değerine sahip olduğunu sorarak kendi kendini geliştirmeyi etkinleştirebiliriz.

Fiyat kayması koruması

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

Fiyat kayması 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 ajanlarına başlamak için yeterince şey biliyorsunuzdur. Bu, konunun kapsamlı bir özeti değildir; buna adanmış koca kitaplar vardır, ancak bu başlamanız için yeterlidir. İyi şanslar!

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