Tengeneza ajenti wako wa akili bandia wa kufanya biashara kwenye Ethereum
Katika mafunzo haya utajifunza jinsi ya kujenga ajenti rahisi wa akili bandia wa kufanya biashara. Ajenti huyu anafanya kazi kwa kutumia hatua hizi:
- Kusoma bei za sasa na za zamani za tokeni, pamoja na taarifa nyingine zinazoweza kuwa muhimu
- Kujenga swali kwa kutumia taarifa hizi, pamoja na taarifa za msingi kuelezea jinsi zinavyoweza kuwa muhimu
- Kuwasilisha swali na kupokea makadirio ya bei
- Kufanya biashara kulingana na pendekezo
- Kusubiri na kurudia
Ajenti huyu anaonyesha jinsi ya kusoma taarifa, kuzitafsiri kuwa swali linalotoa jibu linaloweza kutumika, na kutumia jibu hilo. Hizi zote ni hatua zinazohitajika kwa ajenti wa akili bandia. Ajenti huyu ametekelezwa kwa Python kwa sababu ndiyo lugha inayotumiwa sana katika AI.
Kwa nini ufanye hivi?
Majenti wa biashara wa kujiendesha wanaruhusu wasanidi kuchagua na kutekeleza mkakati wa biashara. Majenti wa akili bandia wanaruhusu mikakati ya biashara iliyo ngumu zaidi na inayobadilika, ikiwezekana kutumia taarifa na aligoriti ambazo msanidi hata hajafikiria kuzitumia.
Zana
Mafunzo haya yanatumia Python (opens in a new tab), maktaba ya Web3 (opens in a new tab), na Uniswap v3 (opens in a new tab) kwa nukuu na biashara.
Kwa nini Python?
Lugha inayotumiwa sana kwa AI ni Python (opens in a new tab), kwa hivyo tunaitumia hapa. Usijali ikiwa hujui Python. Lugha hii iko wazi sana, na nitaelezea kwa usahihi kile inachofanya.
Maktaba ya Web3 (opens in a new tab) ndiyo API ya Ethereum ya Python inayojulikana zaidi. Ni rahisi sana kutumia.
Kufanya biashara kwenye mnyororo wa vitalu
Kuna mabadilishano mengi yaliyosambazwa (DEX) yanayokuruhusu kufanya biashara ya tokeni kwenye Ethereum. Hata hivyo, huwa na viwango vya ubadilishaji vinavyofanana kutokana na arbitrage.
Uniswap (opens in a new tab) ni DEX inayotumiwa sana ambayo tunaweza kuitumia kwa nukuu (kuona thamani za tokeni zinazolingana) na biashara.
OpenAI
Kwa mfumo mkubwa wa lugha, nilichagua kuanza na OpenAI (opens in a new tab). Ili kuendesha programu katika mafunzo haya utahitaji kulipia ufikiaji wa API. Malipo ya chini ya $5 yanatosha kabisa.
Uendelezaji, hatua kwa hatua
Ili kurahisisha uendelezaji, tunaendelea kwa hatua. Kila hatua ni tawi katika GitHub.
Kuanza
Kuna hatua za kuanza chini ya UNIX au Linux (ikiwa ni pamoja na WSL (opens in a new tab))
-
Ikiwa bado huna, pakua na usakinishe Python (opens in a new tab).
-
Nakili hazina ya GitHub.
git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started cd 260215-ai-agent -
Sakinisha
uv(opens in a new tab). Amri kwenye mfumo wako inaweza kuwa tofauti.pipx install uv -
Pakua maktaba.
uv sync -
Washa mazingira pepe.
source .venv/bin/activate -
Ili kuthibitisha kuwa Python na Web3 zinafanya kazi kwa usahihi, endesha
python3na uipe programu hii. Unaweza kuiingiza kwenye kidokezo cha>>>; hakuna haja ya kuunda faili.from web3 import Web3 MAINNET_URL = "https://eth.drpc.org" w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) w3.eth.block_number quit()
Kusoma kutoka kwenye mnyororo wa vitalu
Hatua inayofuata ni kusoma kutoka kwenye mnyororo wa vitalu. Ili kufanya hivyo, unahitaji kubadilisha kwenda kwenye tawi la 02-read-quote na kisha utumie uv kuendesha programu.
git checkout 02-read-quote
uv run agent.py
Unapaswa kupokea orodha ya vitu vya Quote, kila kimoja kikiwa na muhuri wa muda, bei, na rasilimali (kwa sasa kila wakati ni WETH/USDC).
Hapa kuna maelezo ya mstari kwa mstari.
from web3 import Web3
from web3.contract import Contract
from decimal import Decimal, ROUND_HALF_UP
from dataclasses import dataclass
from datetime import datetime, timezone
from pprint import pprint
import time
import functools
import sys
Ingiza maktaba tunazohitaji. Zimeelezwa hapa chini zinapotumika.
print = functools.partial(print, flush=True)
Inabadilisha print ya Python na toleo ambalo kila wakati hutoa matokeo mara moja. Hii ni muhimu katika hati inayoendeshwa kwa muda mrefu kwa sababu hatutaki kusubiri sasisho za hali au matokeo ya utatuzi.
MAINNET_URL = "https://eth.drpc.org"
URL ya kufika kwenye Mtandao Mkuu. Unaweza kupata moja kutoka kwa Nodi kama huduma au kutumia mojawapo ya zile zilizotangazwa katika Chainlist (opens in a new tab).
BLOCK_TIME_SECONDS = 12
MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS)
HOUR_BLOCKS = MINUTE_BLOCKS * 60
DAY_BLOCKS = HOUR_BLOCKS * 24
Kitalu cha Mtandao Mkuu wa Ethereum kwa kawaida hutokea kila sekunde kumi na mbili, kwa hivyo hizi ni idadi ya vitalu tunavyotarajia kutokea katika kipindi cha muda. Kumbuka kuwa hii si takwimu kamili. Wakati mpendekezaji wa bloku yuko chini, kitalu hicho hurukwa, na muda wa kitalu kinachofuata ni sekunde 24. Ikiwa tungetaka kupata kitalu kamili kwa muhuri wa muda, tungetumia utafutaji wa binary (opens in a new tab). Hata hivyo, hii inakaribia kutosha kwa madhumuni yetu. Kutabiri siku zijazo si sayansi kamili.
CYCLE_BLOCKS = DAY_BLOCKS
Ukubwa wa mzunguko. Tunakagua nukuu mara moja kwa kila mzunguko na kujaribu kukadiria thamani mwishoni mwa mzunguko unaofuata.
# Anwani ya bwawa tunalosoma
WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
Thamani za nukuu zinachukuliwa kutoka kwenye bwawa la Uniswap 3 USDC/WETH kwenye anwani 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 (opens in a new tab). Anwani hii tayari iko katika muundo wa checksum, lakini ni bora kutumia Web3.to_checksum_address (opens in a new tab) ili kufanya msimbo uweze kutumika tena.
POOL_ABI = [
{ "name": "slot0", ... },
{ "name": "token0", ... },
{ "name": "token1", ... },
]
ERC20_ABI = [
{ "name": "symbol", ... },
{ "name": "decimals", ... }
]
Hizi ni ABI (opens in a new tab) za mikataba miwili tunayohitaji kuwasiliana nayo. Ili kuweka msimbo kwa ufupi, tunajumuisha tu kazi tunazohitaji kuita.
w3 = Web3(Web3.HTTPProvider(MAINNET_URL))
Anzisha maktaba ya Web3 (opens in a new tab) na uunganishe kwenye nodi ya Ethereum.
@dataclass(frozen=True)
class ERC20Token:
address: str
symbol: str
decimals: int
contract: Contract
Hii ni njia moja ya kuunda darasa la data katika Python. Aina ya data ya Contract (opens in a new tab) inatumika kuunganisha kwenye mkataba. Kumbuka (frozen=True). Katika Python booleans (opens in a new tab) zinafafanuliwa kama True au False, kwa herufi kubwa. Darasa hili la data ni frozen, ikimaanisha sehemu haziwezi kurekebishwa.
Kumbuka uingizaji (indentation). Tofauti na lugha zinazotokana na C (opens in a new tab), Python inatumia uingizaji kuashiria vitalu. Mkalimani wa Python anajua kuwa ufafanuzi ufuatao si sehemu ya darasa hili la data kwa sababu hauanzi kwenye uingizaji sawa na sehemu za darasa la data.
@dataclass(frozen=True)
class PoolInfo:
address: str
token0: ERC20Token
token1: ERC20Token
contract: Contract
asset: str
decimal_factor: Decimal = 1
Aina ya Decimal (opens in a new tab) inatumika kwa kushughulikia kwa usahihi sehemu za desimali.
def get_price(self, block: int) -> Decimal:
Hii ndiyo njia ya kufafanua kazi katika Python. Ufafanuzi umeingizwa ili kuonyesha bado ni sehemu ya PoolInfo.
Katika kazi ambayo ni sehemu ya darasa la data kigezo cha kwanza kila wakati ni self, mfano wa darasa la data ulioita hapa. Hapa kuna kigezo kingine, nambari ya kitalu.
assert block <= w3.eth.block_number, "Block is in the future"
Kama tungeweza kusoma siku zijazo, tusingehitaji AI kwa biashara.
sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0])
Sintaksia ya kuita kazi kwenye EVM kutoka Web3 ni hii: <contract object>.functions.<function name>().call(<parameters>). Vigezo vinaweza kuwa vigezo vya kazi ya EVM (kama vipo; hapa hakuna) au vigezo vilivyotajwa (opens in a new tab) kwa kurekebisha tabia ya mnyororo wa vitalu. Hapa tunatumia kimoja, block_identifier, kubainisha nambari ya kitalu tunayotaka kuendesha.
Matokeo ni muundo huu, katika umbo la safu (opens in a new tab). Thamani ya kwanza ni kazi ya kiwango cha ubadilishaji kati ya tokeni mbili.
raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2
Ili kupunguza hesabu mnyororoni, Uniswap v3 haihifadhi kipengele halisi cha ubadilishaji bali kipeo chake cha pili. Kwa sababu EVM haiauni hesabu za nambari zinazoelea au sehemu, badala ya thamani halisi, jibu ni
# (tokeni1 kwa kila tokeni0)
return 1/(raw_price * self.decimal_factor)
Bei ghafi tunayopata ni idadi ya token0 tunayopata kwa kila token1. Katika bwawa letu token0 ni USDC (sarafu thabiti yenye thamani sawa na dola ya Marekani) na token1 ni WETH (opens in a new tab). Thamani tunayotaka hasa ni idadi ya dola kwa kila WETH, si kinyume chake.
Kipengele cha desimali ni uwiano kati ya vipengele vya desimali (opens in a new tab) kwa tokeni mbili.
@dataclass(frozen=True)
class Quote:
timestamp: str
price: Decimal
asset: str
Darasa hili la data linawakilisha nukuu: bei ya rasilimali maalum kwa wakati fulani. Katika hatua hii, sehemu ya asset haina umuhimu kwa sababu tunatumia bwawa moja na kwa hivyo tuna rasilimali moja. Hata hivyo, tutaongeza rasilimali zaidi baadaye.
def read_token(address: str) -> ERC20Token:
token = w3.eth.contract(address=address, abi=ERC20_ABI)
symbol = token.functions.symbol().call()
decimals = token.functions.decimals().call()
return ERC20Token(
address=address,
symbol=symbol,
decimals=decimals,
contract=token
)
Kazi hii inachukua anwani na kurudisha taarifa kuhusu mkataba wa tokeni kwenye anwani hiyo. Ili kuunda Web3 Contract (opens in a new tab) mpya, tunatoa anwani na ABI kwa w3.eth.contract.
def read_pool(address: str) -> PoolInfo:
pool_contract = w3.eth.contract(address=address, abi=POOL_ABI)
token0Address = pool_contract.functions.token0().call()
token1Address = pool_contract.functions.token1().call()
token0 = read_token(token0Address)
token1 = read_token(token1Address)
return PoolInfo(
address=address,
asset=f"{token1.symbol}/{token0.symbol}",
token0=token0,
token1=token1,
contract=pool_contract,
decimal_factor=Decimal(10) ** Decimal(token0.decimals - token1.decimals)
)
Kazi hii inarudisha kila kitu tunachohitaji kuhusu bwawa maalum (opens in a new tab). Sintaksia f"<string>" ni kamba iliyoumbizwa (opens in a new tab).
def get_quote(pool: PoolInfo, block_number: int = None) -> Quote:
Pata kitu cha Quote. Thamani chaguo-msingi ya block_number ni None (hakuna thamani).
if block_number is None:
block_number = w3.eth.block_number
Ikiwa nambari ya kitalu haikubainishwa, tumia w3.eth.block_number, ambayo ni nambari ya kitalu cha hivi punde. Hii ni sintaksia ya taarifa ya if (opens in a new tab).
Inaweza kuonekana kana kwamba ingekuwa bora kuweka tu chaguo-msingi kuwa w3.eth.block_number, lakini hiyo haifanyi kazi vizuri kwa sababu itakuwa nambari ya kitalu wakati kazi inafafanuliwa. Katika ajenti anayeendeshwa kwa muda mrefu, hili lingekuwa tatizo.
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
)
Tumia maktaba ya datetime (opens in a new tab) kuiumbiza kwa muundo unaosomeka kwa binadamu na mifumo mikubwa ya lugha (LLMs). Tumia Decimal.quantize (opens in a new tab) kuzungusha thamani kwa nafasi mbili za desimali.
def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]:
Katika Python unafafanua orodha (opens in a new tab) inayoweza tu kuwa na aina maalum kwa kutumia list[<type>].
quotes = []
for block in range(start_block, end_block + 1, step):
Katika Python kitanzi cha for (opens in a new tab) kwa kawaida hupitia orodha. Orodha ya nambari za vitalu za kupata nukuu inatoka kwa range (opens in a new tab).
quote = get_quote(pool, block)
quotes.append(quote)
return quotes
Kwa kila nambari ya kitalu, pata kitu cha Quote na ukiambatanishe kwenye orodha ya quotes. Kisha rudisha orodha hiyo.
pool = read_pool(WETHUSDC_ADDRESS)
quotes = get_quotes(
pool,
w3.eth.block_number - 12*CYCLE_BLOCKS,
w3.eth.block_number,
CYCLE_BLOCKS
)
pprint(quotes)
Huu ndio msimbo mkuu wa hati. Soma taarifa za bwawa, pata nukuu kumi na mbili, na pprint (opens in a new tab) hizo.
Kuunda kidokezo
Kisha, tunahitaji kubadilisha orodha hii ya nukuu kuwa kidokezo kwa LLM na kupata thamani inayotarajiwa ya baadaye.
git checkout 03-create-prompt
uv run agent.py
Matokeo sasa yatakuwa kidokezo kwa LLM, sawa na:
Given these quotes:
Asset: WETH/USDC
2026-01-20T16:34 3016.21
.
.
.
2026-02-01T17:49 2299.10
Asset: WBTC/WETH
2026-01-20T16:34 29.84
.
.
.
2026-02-01T17:50 33.46
What would you expect the value for WETH/USDC to be at time 2026-02-02T17:56?
Provide your answer as a single number rounded to two decimal places,
without any other text.
Kumbuka kuwa kuna nukuu za rasilimali mbili hapa, WETH/USDC na WBTC/WETH. Kuongeza nukuu kutoka kwa rasilimali nyingine kunaweza kuboresha usahihi wa utabiri.
Jinsi kidokezo kinavyoonekana
Kidokezo hiki kina sehemu tatu, ambazo ni za kawaida sana katika vidokezo vya LLM.
-
Taarifa. LLM zina taarifa nyingi kutoka kwenye mafunzo yao, lakini kwa kawaida hazina za hivi punde. Hii ndiyo sababu tunahitaji kupata nukuu za hivi punde hapa. Kuongeza taarifa kwenye kidokezo kunaitwa retrieval augmented generation (RAG) (opens in a new tab).
-
Swali halisi. Hiki ndicho tunachotaka kujua.
-
Maagizo ya uumbizaji wa matokeo. Kwa kawaida, LLM itatupa makadirio na maelezo ya jinsi ilivyofikia. Hii ni bora kwa binadamu, lakini programu ya kompyuta inahitaji tu jibu la mwisho.
Maelezo ya msimbo
Hapa kuna msimbo mpya.
from datetime import datetime, timezone, timedelta
Tunahitaji kuipa LLM muda ambao tunataka makadirio. Ili kupata muda "dakika/saa/siku n" katika siku zijazo, tunatumia darasa la timedelta (opens in a new tab).
# Anwani za mabwawa tunayosoma
WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD")
Tuna mabwawa mawili tunayohitaji kusoma.
@dataclass(frozen=True)
class PoolInfo:
.
.
.
reverse: bool = False
def get_price(self, block: int) -> Decimal:
assert block <= w3.eth.block_number, "Block is in the future"
sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0])
raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (tokeni1 kwa kila tokeni0)
if self.reverse:
return 1/(raw_price * self.decimal_factor)
else:
return raw_price * self.decimal_factor
Katika bwawa la WETH/USDC, tunataka kujua ni token0 (USDC) ngapi tunahitaji kununua moja ya token1 (WETH). Katika bwawa la WETH/WBTC, tunataka kujua ni token1 (WETH) ngapi tunahitaji kununua moja ya token0 (WBTC, ambayo ni Bitcoin iliyofungwa). Tunahitaji kufuatilia ikiwa uwiano wa bwawa unahitaji kugeuzwa.
def read_pool(address: str, reverse: bool = False) -> PoolInfo:
.
.
.
return PoolInfo(
.
.
.
asset= f"{token1.symbol}/{token0.symbol}" if reverse else f"{token0.symbol}/{token1.symbol}",
reverse=reverse
)
Ili kujua ikiwa bwawa linahitaji kugeuzwa, tunapata hiyo kama ingizo kwa read_pool. Pia, alama ya rasilimali inahitaji kuwekwa kwa usahihi.
Sintaksia <a> if <b> else <c> ni sawa na Python ya kigezo cha masharti cha ternary (opens in a new tab), ambayo katika lugha inayotokana na C ingekuwa <b> ? <a> : <c>.
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
Kazi hii inajenga kamba inayoumbiza orodha ya vitu vya Quote, ikichukulia vyote vinatumika kwa rasilimali sawa.
def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str:
return f"""
Katika Python kamba halisi za mistari mingi (opens in a new tab) huandikwa kama """ .... """.
Given these quotes:
{
functools.reduce(lambda acc, q: acc + '\n' + q,
map(lambda q: format_quotes(q), quotes))
}
Hapa, tunatumia muundo wa MapReduce (opens in a new tab) kuzalisha kamba kwa kila orodha ya nukuu na format_quotes, kisha kuzipunguza kuwa kamba moja kwa matumizi katika kidokezo.
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.
"""
Sehemu iliyobaki ya kidokezo ni kama inavyotarajiwa.
wethusdc_pool = read_pool(WETHUSDC_ADDRESS, True)
wethusdc_quotes = get_quotes(
wethusdc_pool,
w3.eth.block_number - 12*CYCLE_BLOCKS,
w3.eth.block_number,
CYCLE_BLOCKS,
)
wethwbtc_pool = read_pool(WETHWBTC_ADDRESS)
wethwbtc_quotes = get_quotes(
wethwbtc_pool,
w3.eth.block_number - 12*CYCLE_BLOCKS,
w3.eth.block_number,
CYCLE_BLOCKS
)
Kagua mabwawa mawili na upate nukuu kutoka kwa yote mawili.
future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16]
print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset))
Amua hatua ya muda ujao ambayo tunataka makadirio, na uunde kidokezo.
Kuunganisha na LLM
Kisha, tunadokeza LLM halisi na kupokea thamani inayotarajiwa ya baadaye. Niliandika programu hii kwa kutumia OpenAI, kwa hivyo ikiwa unataka kutumia mtoa huduma tofauti, utahitaji kuirekebisha.
-
Weka pesa kwenye akaunti (opens in a new tab)—kiasi cha chini wakati wa kuandika ni $5
-
Katika mstari wa amri, hamisha ufunguo wa API ili programu yako iweze kuutumia
export OPENAI_API_KEY=sk-<the rest of the key goes here> -
Angalia na uendeshe ajenti
git checkout 04-interface-llm uv run agent.py
Hapa kuna msimbo mpya.
from openai import OpenAI
open_ai = OpenAI() # Mteja anasoma kibadilika cha mazingira cha OPENAI_API_KEY
Ingiza na uanzishe API ya OpenAI.
response = open_ai.chat.completions.create(
model="gpt-4-turbo",
messages=[
{"role": "user", "content": prompt}
],
temperature=0.0,
max_tokens=16,
)
Ita API ya OpenAI (open_ai.chat.completions.create) ili kuunda jibu.
expected_price = Decimal(response.choices[0].message.content.strip())
current_price = wethusdc_quotes[-1].price
print ("Current price:", wethusdc_quotes[-1].price)
print(f"In {future_time}, expected price: {expected_price} USD")
if (expected_price > current_price):
print(f"Buy, I expect the price to go up by {expected_price - current_price} USD")
else:
print(f"Sell, I expect the price to go down by {current_price - expected_price} USD")
Toa bei na utoe pendekezo la kununua au kuuza.
Kujaribu utabiri
Sasa kwa kuwa tunaweza kuzalisha utabiri, tunaweza pia kutumia data ya kihistoria kutathmini ikiwa tunazalisha utabiri muhimu.
uv run test-predictor.py
Matokeo yanayotarajiwa ni sawa na:
Prediction for 2026-01-05T19:50: predicted 3138.93 USD, real 3218.92 USD, error 79.99 USD
Prediction for 2026-01-06T19:56: predicted 3243.39 USD, real 3221.08 USD, error 22.31 USD
Prediction for 2026-01-07T20:02: predicted 3223.24 USD, real 3146.89 USD, error 76.35 USD
Prediction for 2026-01-08T20:11: predicted 3150.47 USD, real 3092.04 USD, error 58.43 USD
.
.
.
Prediction for 2026-01-31T22:33: predicted 2637.73 USD, real 2417.77 USD, error 219.96 USD
Prediction for 2026-02-01T22:41: predicted 2381.70 USD, real 2318.84 USD, error 62.86 USD
Prediction for 2026-02-02T22:49: predicted 2234.91 USD, real 2349.28 USD, error 114.37 USD
Mean prediction error over 29 predictions: 83.87103448275862068965517241 USD
Mean change per recommendation: 4.787931034482758620689655172 USD
Standard variance of changes: 104.42 USD
Profitable days: 51.72%
Losing days: 48.28%
Sehemu kubwa ya kijaribu inafanana na ajenti, lakini hapa kuna sehemu ambazo ni mpya au zilizorekebishwa.
CYCLES_FOR_TEST = 40 # Kwa upimaji wa nyuma, ni mizunguko mingapi tunayoipima
# Pata nukuu nyingi
wethusdc_pool = read_pool(WETHUSDC_ADDRESS, True)
wethusdc_quotes = get_quotes(
wethusdc_pool,
w3.eth.block_number - CYCLE_BLOCKS*CYCLES_FOR_TEST,
w3.eth.block_number,
CYCLE_BLOCKS,
)
wethwbtc_pool = read_pool(WETHWBTC_ADDRESS)
wethwbtc_quotes = get_quotes(
wethwbtc_pool,
w3.eth.block_number - CYCLE_BLOCKS*CYCLES_FOR_TEST,
w3.eth.block_number,
CYCLE_BLOCKS
)
Tunaangalia siku CYCLES_FOR_TEST (zilizobainishwa kama 40 hapa) nyuma.
# Tengeneza utabiri na uukague dhidi ya historia halisi
total_error = Decimal(0)
changes = []
Kuna aina mbili za makosa tunayovutiwa nayo. Ya kwanza, total_error, ni jumla ya makosa yaliyofanywa na mtabiri.
Ili kuelewa ya pili, changes, tunahitaji kukumbuka madhumuni ya ajenti. Sio kutabiri uwiano wa WETH/USDC (bei ya ETH). Ni kutoa mapendekezo ya kuuza na kununua. Ikiwa bei kwa sasa ni $2000 na inatabiri $2010 kesho, hatujali ikiwa matokeo halisi ni $2020 na tunapata pesa za ziada. Lakini tunajali ikiwa ilitabiri $2010, na kununua ETH kulingana na pendekezo hilo, na bei inashuka hadi $1990.
for index in range(0,len(wethusdc_quotes)-CYCLES_BACK):
Tunaweza tu kuangalia kesi ambapo historia kamili (thamani zilizotumika kwa utabiri na thamani ya ulimwengu halisi ya kulinganisha nayo) inapatikana. Hii inamaanisha kesi mpya zaidi lazima iwe ile iliyoanza CYCLES_BACK iliyopita.
wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK]
wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK]
Tumia vipande (opens in a new tab) kupata idadi sawa ya sampuli kama nambari anayotumia ajenti. Msimbo kati ya hapa na sehemu inayofuata ni msimbo ule ule wa kupata-utabiri tulio nao katika ajenti.
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
Pata bei iliyotabiriwa, bei halisi, na bei wakati wa utabiri. Tunahitaji bei wakati wa utabiri ili kuamua ikiwa pendekezo lilikuwa la kununua au kuuza.
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")
Tafuta kosa, na uongeze kwenye jumla.
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)
Kwa changes, tunataka athari ya kifedha ya kununua au kuuza ETH moja. Kwa hivyo kwanza, tunahitaji kuamua pendekezo, kisha kutathmini jinsi bei halisi ilivyobadilika, na ikiwa pendekezo lilitengeneza pesa (mabadiliko chanya) au kugharimu pesa (mabadiliko hasi).
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")
Ripoti matokeo.
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%}")
Tumia filter (opens in a new tab) kuhesabu idadi ya siku zenye faida na idadi ya siku za gharama. Matokeo ni kitu cha kichujio, ambacho tunahitaji kubadilisha kuwa orodha ili kupata urefu.
Kuwasilisha miamala
Sasa tunahitaji kuwasilisha miamala. Hata hivyo, sitaki kutumia pesa halisi katika hatua hii, kabla ya mfumo kuthibitishwa. Badala yake, tutaunda mchepuo wa ndani wa Mtandao Mkuu, na "kufanya biashara" kwenye mtandao huo.
Hapa kuna hatua za kuunda mchepuo wa ndani na kuwezesha biashara.
-
Sakinisha Foundry (opens in a new tab)
-
Anza
anvil(opens in a new tab)anvil --fork-url https://eth.drpc.org --block-time 12anvilinasikiliza kwenye URL chaguo-msingi ya Foundry, http://localhost:8545 (opens in a new tab), kwa hivyo hatuhitaji kubainisha URL kwa amri yacast(opens in a new tab) tunayotumia kudhibiti mnyororo wa vitalu. -
Wakati wa kuendesha katika
anvil, kuna akaunti kumi za majaribio ambazo zina ETH—weka vigezo vya mazingira kwa ya kwanzaPRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 ADDRESS=`cast wallet address $PRIVATE_KEY` -
Hii ndiyo mikataba tunayohitaji kutumia.
SwapRouter(opens in a new tab) ni mkataba wa Uniswap v3 tunaotumia kufanya biashara hasa. Tungeweza kufanya biashara moja kwa moja kupitia bwawa, lakini hii ni rahisi zaidi.Vigezo viwili vya chini ni njia za Uniswap v3 zinazohitajika kwa badilishano kati ya WETH na USDC.
WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 -
Kila moja ya akaunti za majaribio ina 10,000 ETH. Tumia mkataba wa WETH kufunga 1000 ETH ili kupata 1000 WETH kwa biashara.
cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY -
Tumia
SwapRouterkufanya biashara ya 500 WETH kwa USDC.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_KEYWito wa
approveunaunda kibali kinachoruhusuSwapRouterkutumia baadhi ya tokeni zetu. Mikataba haiwezi kufuatilia matukio, kwa hivyo ikiwa tutafanya hamisho la tokeni moja kwa moja kwenye mkataba waSwapRouter, isingejua ililipwa. Badala yake, tunaruhusu mkataba waSwapRouterkutumia kiasi fulani, na kishaSwapRouterinafanya hivyo. Hii inafanywa kupitia kazi inayoitwa naSwapRouter, kwa hivyo inajua ikiwa ilifanikiwa. -
Thibitisha una kiasi cha kutosha cha tokeni zote mbili.
cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc
Sasa kwa kuwa tuna WETH na USDC, tunaweza kuendesha ajenti.
git checkout 05-trade
uv run agent.py
Matokeo yataonekana sawa na:
(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py
Current price: 1843.16
In 2026-02-06T23:07, expected price: 1724.41 USD
Account balances before trade:
USDC Balance: 927301.578272
WETH Balance: 500
Sell, I expect the price to go down by 118.75 USD
Approve transaction sent: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f
Approve transaction mined.
Sell transaction sent: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf
Sell transaction mined.
Account balances after trade:
USDC Balance: 929143.797116
WETH Balance: 499
Ili kuitumia hasa, unahitaji mabadiliko machache madogo.
- Katika mstari wa 14, badilisha
MAINNET_URLkuwa kituo halisi cha ufikiaji, kama vilehttps://eth.drpc.org - Katika mstari wa 28, badilisha
PRIVATE_KEYkuwa ufunguo wa siri wako mwenyewe - Isipokuwa wewe ni tajiri sana na unaweza kununua au kuuza 1 ETH kila siku kwa ajenti ambaye hajathibitishwa, unaweza kutaka kubadilisha 29 ili kupunguza
WETH_TRADE_AMOUNT
Maelezo ya msimbo
Hapa kuna msimbo mpya.
SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564")
WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
Vigezo vile vile tulivyotumia katika hatua ya 4.
WETH_TRADE_AMOUNT=1
Kiasi cha kufanyia biashara.
ERC20_ABI = [
{ "name": "symbol", ... },
{ "name": "decimals", ... },
{ "name": "balanceOf", ...},
{ "name": "approve", ...}
]
Ili kufanya biashara hasa, tunahitaji kazi ya approve. Pia tunataka kuonyesha salio kabla na baada, kwa hivyo tunahitaji pia balanceOf.
SWAP_ROUTER_ABI = [
{ "name": "exactInput", ...},
]
Katika ABI ya SwapRouter tunahitaji tu exactInput. Kuna kazi inayohusiana, exactOutput, ambayo tungeweza kutumia kununua WETH moja haswa, lakini kwa urahisi tunatumia tu exactInput katika visa vyote viwili.
account = w3.eth.account.from_key(PRIVATE_KEY)
swap_router = w3.eth.contract(
address=SWAP_ROUTER_ADDRESS,
abi=SWAP_ROUTER_ABI
)
Ufafanuzi wa Web3 kwa account (opens in a new tab) na mkataba wa SwapRouter.
def txn_params() -> dict:
return {
"from": account.address,
"value": 0,
"gas": 300000,
"nonce": w3.eth.get_transaction_count(account.address),
}
Vigezo vya muamala. Tunahitaji kazi hapa kwa sababu nonsi (opens in a new tab) lazima ibadilike kila wakati.
def approve_token(contract: Contract, amount: int):
Idhinisha kibali cha tokeni kwa SwapRouter.
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)
Hivi ndivyo tunavyotuma muamala katika Web3. Kwanza tunatumia kitu cha Contract (opens in a new tab) kujenga muamala. Kisha tunatumia web3.eth.account.sign_transaction (opens in a new tab) kutia saini muamala, kwa kutumia PRIVATE_KEY. Hatimaye, tunatumia w3.eth.send_raw_transaction (opens in a new tab) kutuma muamala.
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) inasubiri hadi muamala uchimbwe. Inarudisha stakabadhi ikihitajika.
SELL_PARAMS = {
"path": WETH_TO_USDC,
"recipient": account.address,
"deadline": 2**256 - 1,
"amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals,
"amountOutMinimum": 0,
}
Hivi ni vigezo wakati wa kuuza WETH.
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,
}
Tofauti na SELL_PARAMS, vigezo vya kununua vinaweza kubadilika. Kiasi cha kuingiza ni gharama ya 1 WETH, kama inavyopatikana katika quote.
def buy(quote: Quote):
buy_params = make_buy_params(quote)
approve_token(wethusdc_pool.token0.contract, buy_params["amountIn"])
txn = swap_router.functions.exactInput(buy_params).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)
print(f"Buy transaction sent: {tx_hash.hex()}")
w3.eth.wait_for_transaction_receipt(tx_hash)
print("Buy transaction mined.")
def sell():
approve_token(wethusdc_pool.token1.contract,
WETH_TRADE_AMOUNT * 10**wethusdc_pool.token1.decimals)
txn = swap_router.functions.exactInput(SELL_PARAMS).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)
print(f"Sell transaction sent: {tx_hash.hex()}")
w3.eth.wait_for_transaction_receipt(tx_hash)
print("Sell transaction mined.")
Kazi za buy() na sell() zinakaribia kufanana. Kwanza tunaidhinisha kibali cha kutosha kwa SwapRouter, na kisha tunaiita kwa njia na kiasi sahihi.
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)}")
Ripoti salio la mtumiaji katika sarafu zote mbili.
print("Account balances before trade:")
balances()
if (expected_price > current_price):
print(f"Buy, I expect the price to go up by {expected_price - current_price} USD")
buy(wethusdc_quotes[-1])
else:
print(f"Sell, I expect the price to go down by {current_price - expected_price} USD")
sell()
print("Account balances after trade:")
balances()
Ajenti huyu kwa sasa anafanya kazi mara moja tu. Hata hivyo, unaweza kumbadilisha ili afanye kazi mfululizo ama kwa kumuendesha kutoka crontab (opens in a new tab) au kwa kufunga mistari 368-400 katika kitanzi na kutumia time.sleep (opens in a new tab) kusubiri hadi wakati wa mzunguko unaofuata.
Maboresho yanayowezekana
Hili si toleo kamili la uzalishaji; ni mfano tu wa kufundisha mambo ya msingi. Hapa kuna baadhi ya mawazo ya maboresho.
Biashara nadhifu zaidi
Kuna ukweli mbili muhimu ambazo ajenti anapuuza anapoamua nini cha kufanya.
- Ukubwa wa mabadiliko yanayotarajiwa. Ajenti anauza kiasi maalum cha
WETHikiwa bei inatarajiwa kushuka, bila kujali ukubwa wa kushuka huko. Inawezekana, ingekuwa bora kupuuza mabadiliko madogo na kuuza kulingana na kiasi gani tunatarajia bei kushuka. - Kwingineko ya sasa. Ikiwa 10% ya kwingineko yako iko katika WETH na unafikiri bei itapanda, labda ina maana kununua zaidi. Lakini ikiwa 90% ya kwingineko yako iko katika WETH, unaweza kuwa umejiweka wazi vya kutosha, na hakuna haja ya kununua zaidi. Kinyume chake ni kweli ikiwa unatarajia bei kushuka.
Vipi ikiwa unataka kuweka mkakati wako wa biashara siri?
Wachuuzi wa AI wanaweza kuona maswali unayotuma kwa LLM zao, ambayo yanaweza kufichua mfumo wa biashara wa kipekee uliouendeleza na ajenti wako. Mfumo wa biashara ambao watu wengi sana wanautumia hauna thamani kwa sababu watu wengi sana wanajaribu kununua wakati unataka kununua (na bei inapanda) na kujaribu kuuza wakati unataka kuuza (na bei inashuka).
Unaweza kuendesha LLM ndani ya nchi, kwa mfano, kwa kutumia LM-Studio (opens in a new tab), ili kuepuka tatizo hili.
Kutoka kwa boti ya AI hadi ajenti wa akili bandia
Unaweza kutoa hoja nzuri kwamba hii ni boti ya AI, sio ajenti wa akili bandia. Inatekeleza mkakati rahisi kiasi ambao unategemea taarifa zilizofafanuliwa awali. Tunaweza kuwezesha kujiboresha, kwa mfano, kwa kutoa orodha ya mabwawa ya Uniswap v3 na thamani zao za hivi punde na kuuliza ni mchanganyiko upi una thamani bora ya utabiri.
Ulinzi wa tofauti ya utekelezaji
Kwa sasa hakuna ulinzi wa tofauti ya utekelezaji (opens in a new tab). Ikiwa nukuu ya sasa ni $2000, na bei inayotarajiwa ni $2100, ajenti atanunua. Hata hivyo, ikiwa kabla ya ajenti kununua gharama inapanda hadi $2200, haina maana kununua tena.
Ili kutekeleza ulinzi wa tofauti ya utekelezaji, bainisha thamani ya amountOutMinimum katika mistari ya 325 na 334 ya agent.py (opens in a new tab).
Hitimisho
Tunatumai, sasa unajua vya kutosha kuanza na majenti wa akili bandia. Huu sio muhtasari wa kina wa somo; kuna vitabu vizima vilivyotengwa kwa hilo, lakini hii inatosha kukuanzisha. Kila la heri!