Ethereum üzerinde kendi yapay zeka alım satım aracınızı oluşturun
Bu öğreticide, basit bir yapay zeka alım satım aracısı oluşturmayı öğreneceksiniz. Bu aracı şu adımları kullanarak çalışır:
- Bir jetonun mevcut ve geçmiş fiyatlarının yanı sıra diğer potansiyel olarak ilgili bilgileri okuyun
- Bu bilgilerle birlikte, nasıl ilgili olabileceğini açıklamak için arka plan bilgilerini de içeren bir sorgu oluşturun
- Sorguyu gönderin ve tahmini bir fiyat alın
- Tavsiyeye göre alım satım yapın
- 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
-
Henüz sahip değilseniz, Python (opens in a new tab) indirip kurun.
-
GitHub deposunu klonlayın.
1git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started2cd 260215-ai-agent -
uv(opens in a new tab) yükleyin. Sisteminizdeki komut farklı olabilir.1pipx install uv -
Kütüphaneleri indirin.
1uv sync -
Sanal ortamı etkinleştirin.
1source .venv/bin/activate -
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 Web32MAINNET_URL = "https://eth.drpc.org"3w3 = Web3(Web3.HTTPProvider(MAINNET_URL))4w3.eth.block_number5quit()
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-quote2uv run agent.pyHer 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 Web32from web3.contract import Contract3from decimal import Decimal, ROUND_HALF_UP4from dataclasses import dataclass5from datetime import datetime, timezone6from pprint import pprint7import time8import functools9import sysTü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 = 122MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS)3HOUR_BLOCKS = MINUTE_BLOCKS * 604DAY_BLOCKS = HOUR_BLOCKS * 24Bir 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_BLOCKSDö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 adresi2WETHUSDC_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]67ERC20_ABI = [8 { "name": "symbol", ... },9 { "name": "decimals", ... }10]Tümünü gösterBunlar, 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: str4 symbol: str5 decimals: int6 contract: ContractBu, 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: str4 token0: ERC20Token5 token1: ERC20Token6 contract: Contract7 asset: str8 decimal_factor: Decimal = 1Decimal (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)) ** 2Zincir ü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 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: str4 price: Decimal5 asset: strBu 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()56 return ERC20Token(7 address=address,8 symbol=symbol,9 decimals=decimals,10 contract=token11 )Tümünü gösterBu 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)78 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österBu 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_numberBir 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.asset7 )İ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 quotesHer 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_BLOCKS7)89pprint(quotes)Tümünü gösterBu, 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-prompt2uv run agent.pyÇıktı şimdi bir LLM'ye bir istem olacak, şuna benzer:
1Bu teklifler verildiğinde:2Varlık: WETH/USDC3 2026-01-20T16:34 3016.214 .5 .6 .7 2026-02-01T17:49 2299.1089Varlık: WBTC/WETH10 2026-01-20T16:34 29.8411 .12 .13 .14 2026-02-01T17:50 33.461516172026-02-02T17:56 zamanında WETH/USDC değerinin ne olmasını beklersiniz?1819Cevabınızı iki ondalık basamağa yuvarlanmış tek bir sayı olarak,20başka metin olmadan verin.Tümünü gösterBurada 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.
-
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.
-
Asıl soru. Bilmek istediğimiz şey bu.
-
Çı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, timedeltaLLM'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 adresleri2WETHUSDC_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 = False78 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_factorTümünü gösterWETH/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 .56 return PoolInfo(7 .8 .9 .1011 asset= f"{token1.symbol}/{token0.symbol}" if reverse else f"{token0.symbol}/{token1.symbol}",12 reverse=reverse13 )Tümünü gösterBir 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 resultBu 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} için değerin ne olmasını beklersiniz?23Cevabı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)89wethwbtc_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_BLOCKS15)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]23print(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.
-
Bir OpenAI hesabı (opens in a new tab) edinin
-
Hesaba para yatırın (opens in a new tab)—bu yazının yazıldığı sırada asgari tutar 5$'dır
-
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> -
Ajanı kullanıma alın ve çalıştırın
1git checkout 04-interface-llm2uv run agent.py
İşte yeni kod.
1from openai import OpenAI23open_ai = OpenAI() # İstemci, OPENAI_API_KEY ortam değişkenini okurOpenAI 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].price34print ("Mevcut fiyat:", wethusdc_quotes[-1].price)5print(f"{future_time} zamanında, beklenen fiyat: {expected_price} USD")67if (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österFiyatı çı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.pyBeklenen sonuç şuna benzer:
12026-01-05T19:50 için tahmin: tahmin edilen 3138.93 USD, gerçek 3218.92 USD, hata 79.99 USD22026-01-06T19:56 için tahmin: tahmin edilen 3243.39 USD, gerçek 3221.08 USD, hata 22.31 USD32026-01-07T20:02 için tahmin: tahmin edilen 3223.24 USD, gerçek 3146.89 USD, hata 76.35 USD42026-01-08T20:11 için tahmin: tahmin edilen 3150.47 USD, gerçek 3092.04 USD, hata 58.43 USD5.6.7.82026-01-31T22:33 için tahmin: tahmin edilen 2637.73 USD, gerçek 2417.77 USD, hata 219.96 USD92026-02-01T22:41 için tahmin: tahmin edilen 2381.70 USD, gerçek 2318.84 USD, hata 62.86 USD102026-02-02T22:49 için tahmin: tahmin edilen 2234.91 USD, gerçek 2349.28 USD, hata 114.37 USD1129 tahmin üzerinden ortalama tahmin hatası: 83.87103448275862068965517241 USD12Tavsiye başına ortalama değişim: 4.787931034482758620689655172 USD13Değişikliklerin standart sapması: 104.42 USD14Kârlı günler: %51.7215Zararlı günler: %48.28Tümünü gösterTest 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ğimiz23# Çok sayıda teklif al4wethusdc_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)1112wethwbtc_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_BLOCKS18)Tümünü gösterCYCLES_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 edin23total_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].price3 prediction_time_price = wethusdc_quotes[index+CYCLES_BACK-1].priceTahmini 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 += error3 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_price3 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")23length_changes = Decimal(len(changes))4mean_change = sum(changes, Decimal(0)) / length_changes5print (f"Tavsiye başına ortalama değişim: {mean_change} USD")6var = sum((x - mean_change) ** 2 for x in changes) / length_changes7print (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.
-
Foundry (opens in a new tab) yükleyin
-
anvil(opens in a new tab) başlatın1anvil --fork-url https://eth.drpc.org --block-time 12anvil, 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ızcastkomutu (opens in a new tab) için URL belirtmemize gerek yok. -
anvil'de çalıştırıldığında, ETH'ye sahip on test hesabı vardır—ilk hesap için ortam değişkenlerini ayarlayın1PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff802ADDRESS=`cast wallet address $PRIVATE_KEY` -
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=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc22USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB483POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f56404SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C058615645WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB486USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 -
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 -
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_KEY2MAXINT=`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_KEYapproveç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ğrudanSwapRoutersözleşmesine aktarırsak, ödendiğini bilemez. Bunun yerine,SwapRoutersözleşmesinin belirli bir miktarı harcamasına izin veririz ve ardındanSwapRouterbunu yapar. Bu,SwapRoutertarafından çağrılan bir işlev aracılığıyla yapılır, böylece başarılı olup olmadığını bilir. -
Her iki jetondan da yeterli miktarda olduğunu doğrulayın.
1cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei2echo `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-trade2uv run agent.pyÇıktı şuna benzer olacaktır:
1(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py2Mevcut fiyat: 1843.1632026-02-06T23:07 zamanında beklenen fiyat: 1724.41 USD4Alım satım öncesi hesap bakiyeleri:5USDC Bakiyesi: 927301.5782726WETH Bakiyesi: 5007Sat, fiyatın 118.75 USD düşmesini bekliyorum8Onay işlemi gönderildi: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f9Onay işlemi çıkarıldı.10Satış işlemi gönderildi: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf11Satış işlemi çıkarıldı.12Alım satım sonrası hesap bakiyeleri:13USDC Bakiyesi: 929143.79711614WETH Bakiyesi: 499Tümünü gösterGerçekten kullanmak için birkaç küçük değişiklik yapmanız gerekir.
-
- satırda,
MAINNET_URL'yihttps://eth.drpc.orggibi gerçek bir erişim noktasına değiştirin
- satırda,
-
- satırda,
PRIVATE_KEY'i kendi özel anahtarınızla değiştirin
- satırda,
- Ç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"- adımda kullandığımız aynı değişkenler.
1WETH_TRADE_AMOUNT=1Alı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_ABI5)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ı.")101112def 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österbuy() 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()45 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()34if (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()1011print("Alım satım sonrası hesap bakiyeleri:")12balances()Tümünü gösterBu 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
WETHsatar. 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