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 (inafunguka katika kichupo kipya), maktaba ya Web3 (inafunguka katika kichupo kipya), na Uniswap v3 (inafunguka katika kichupo kipya) kwa nukuu na biashara.
Kwa nini Python?
Lugha inayotumiwa sana kwa AI ni Python (inafunguka katika kichupo kipya), kwa hivyo tunaitumia hapa. Usijali ikiwa hujui Python. Lugha hii iko wazi sana, na nitaelezea kwa usahihi kile inachofanya.
Maktaba ya Web3 (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya). 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 (inafunguka katika kichupo kipya))
-
Ikiwa bado huna, pakua na usakinishe Python (inafunguka katika kichupo kipya).
-
Nakili hazina ya GitHub.
git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started cd 260215-ai-agent -
Sakinisha
uv(inafunguka katika kichupo kipya). 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 (inafunguka katika kichupo kipya).
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 (inafunguka katika kichupo kipya). 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 (inafunguka katika kichupo kipya). Anwani hii tayari iko katika muundo wa checksum, lakini ni bora kutumia Web3.to_checksum_address (inafunguka katika kichupo kipya) ili kufanya msimbo uweze kutumika tena.
POOL_ABI = [
{ "name": "slot0", ... },
{ "name": "token0", ... },
{ "name": "token1", ... },
]
ERC20_ABI = [
{ "name": "symbol", ... },
{ "name": "decimals", ... }
]
Hizi ni ABI (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya) inatumika kuunganisha kwenye mkataba. Kumbuka (frozen=True). Katika Python booleans (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya), 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 (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya). 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 (inafunguka katika kichupo kipya). Thamani tunayotaka hasa ni idadi ya dola kwa kila WETH, si kinyume chake.
Kipengele cha desimali ni uwiano kati ya vipengele vya desimali (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya). Sintaksia f"<string>" ni kamba iliyoumbizwa (inafunguka katika kichupo kipya).
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 (inafunguka katika kichupo kipya).
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 (inafunguka katika kichupo kipya) kuiumbiza kwa muundo unaosomeka kwa binadamu na mifumo mikubwa ya lugha (LLMs). Tumia Decimal.quantize (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya) kwa kawaida hupitia orodha. Orodha ya nambari za vitalu za kupata nukuu inatoka kwa range (inafunguka katika kichupo kipya).
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 (inafunguka katika kichupo kipya) 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) (inafunguka katika kichupo kipya).
-
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 (inafunguka katika kichupo kipya).
# 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 (inafunguka katika kichupo kipya), 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 (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya)—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 (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya) 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.
-
Anza
anvil(inafunguka katika kichupo kipya)anvil --fork-url https://eth.drpc.org --block-time 12anvilinasikiliza kwenye URL chaguo-msingi ya Foundry, http://localhost:8545 (inafunguka katika kichupo kipya), kwa hivyo hatuhitaji kubainisha URL kwa amri yacast(inafunguka katika kichupo kipya) 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(inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya) kujenga muamala. Kisha tunatumia web3.eth.account.sign_transaction (inafunguka katika kichupo kipya) kutia saini muamala, kwa kutumia PRIVATE_KEY. Hatimaye, tunatumia w3.eth.send_raw_transaction (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya) au kwa kufunga mistari 368-400 katika kitanzi na kutumia time.sleep (inafunguka katika kichupo kipya) 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 (inafunguka katika kichupo kipya), 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 (inafunguka katika kichupo kipya). 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 (inafunguka katika kichupo kipya).
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!
Tazama hapa kwa kazi zangu zaidi (inafunguka katika kichupo kipya).