diff --git a/docs/solutions/build-errors/mdx-compilation-crowdin-backtick-split.md b/docs/solutions/build-errors/mdx-compilation-crowdin-backtick-split.md new file mode 100644 index 00000000000..7c54bad2d42 --- /dev/null +++ b/docs/solutions/build-errors/mdx-compilation-crowdin-backtick-split.md @@ -0,0 +1,139 @@ +--- +title: MDX Compilation Error from Split Backticks in Translated Angle Bracket Expressions +date: 2026-02-19 +category: build-errors +tags: + - MDX + - translations + - Crowdin + - angle-brackets + - backticks + - i18n + - next-mdx-remote +severity: high +component: next-mdx-remote / Crowdin translations +symptoms: + - "Netlify build fails with: Expected a closing tag for `` before the end of `paragraph`" + - Build failure on translated pages only (not English source) + - Error points to lines where angle brackets appear outside backtick code spans +root_cause: Crowdin translation splits backtick wrapping around code expressions containing angle brackets, leaving bare , , etc. that MDX interprets as unclosed JSX tags +resolution_time: quick +--- + +# MDX Compilation Error from Split Backticks in Translated Angle Bracket Expressions + +## Problem Symptom + +Netlify build fails during static page generation with repeated errors like: + +``` +[next-mdx-remote] error compiling MDX: +Expected a closing tag for `` (436:204-436:207) before the end of `paragraph` +``` + +Affected pages (this instance): +- `/cs/developers/tutorials/ai-trading-agent` +- `/ja/developers/tutorials/ai-trading-agent` +- `/pt-br/developers/tutorials/ai-trading-agent` +- `/ur/developers/tutorials/ai-trading-agent` + +## Root Cause Analysis + +The English source uses backtick-wrapped code expressions containing angle brackets: + +```markdown +which in a C-derived language would be ` ? : `. +``` + +During Crowdin translation, translators inadvertently split the backtick pair. The backtick closes after `?`, leaving `` and `` as bare text: + +```markdown +# Broken (backtick closes too early): +který by v jazyce odvozeném od C byl ` ?` : `. + +# Fixed (single backtick pair wraps all angle brackets): +který by v jazyce odvozeném od C byl ` ? : `. +``` + +MDX (via `next-mdx-remote`) interprets bare `` and `` as opening JSX/HTML tags without corresponding closing tags, causing compilation failure. + +## Solution + +Re-wrap the angle-bracketed expressions inside a single backtick pair in each affected translation file. + +### Files Modified + +| Language | File | Line | +|----------|------|------| +| Czech | `public/content/translations/cs/developers/tutorials/ai-trading-agent/index.md` | 446 | +| Japanese | `public/content/translations/ja/developers/tutorials/ai-trading-agent/index.md` | 445 | +| Portuguese-BR | `public/content/translations/pt-br/developers/tutorials/ai-trading-agent/index.md` | 447 | +| Urdu | `public/content/translations/ur/developers/tutorials/ai-trading-agent/index.md` | 446 | + +### Before/After + +**Czech:** +```diff +- ` ?` : ` ++ ` ? : ` +``` + +**Japanese:** +```diff +- ` ?` : ` ++ ` ? : ` +``` + +**Portuguese-BR:** +```diff +- ` ?` : `.` ++ ` ? : `. +``` +(Also removed stray extra backtick at end) + +**Urdu:** +```diff +- ` ?` ہوگا : ` ++ ` ? : ` ہوگا +``` +(Reordered so angle brackets stay inside backticks) + +## Related Context + +### Prior Occurrences + +- **Commit `76675a5717`** ("fix: escape angle brackets in translated MDX files") — Fixed the same class of issue across 18 languages in an earlier import. This confirms it is a recurring pattern. +- **Commit `3ef2bbb91e`** ("i18n: post-import sanitization") — Ran the existing sanitizer on the same tutorial content. +- **Commit `01fd093439`** ("fix(i18n): post_import_sanitize on ai-trading-agents") — Latest sanitizer run, which did not catch this particular pattern. + +### Existing Sanitizer + +The project has `src/scripts/i18n/post_import_sanitize.ts` with functions like `fixAsciiGuillemets()` that handle `<<`/`>>` conversions. However, it does **not** currently detect split backtick wrapping around single angle bracket expressions like ``, ``, ``. + +## Prevention Strategies + +### 1. Add Sanitizer Rule (Recommended) + +Add a `fixBareAngleBrackets()` function to `post_import_sanitize.ts` that: +- Scans each line for bare `` patterns outside of backtick spans and code fences +- Compares with the English source to find the intended backtick-wrapped version +- Auto-fixes when the match is unambiguous; flags for manual review otherwise + +### 2. Pre-Build Validation + +Add a validation step that checks all translated MDX files for bare angle brackets outside code contexts before the build runs. This would catch issues before they reach Netlify. + +### 3. Crowdin Configuration + +- Lock inline code patterns containing angle brackets as "Do not translate" segments +- Add context notes warning translators about preserving backtick pairs around code expressions + +## Verification + +After applying fixes, confirm with a full build: + +```bash +pnpm build +``` + +Check that the 4 previously-failing pages no longer produce MDX compilation errors. diff --git a/public/content/translations/ar/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/ar/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..5755f1464fc --- /dev/null +++ b/public/content/translations/ar/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "اصنع وكيل تداول الذكاء الاصطناعي الخاص بك على الإيثريوم" +description: "في هذا البرنامج التعليمي، ستتعلم كيفية إنشاء وكيل تداول بسيط للذكاء الاصطناعي. يقرأ هذا الوكيل المعلومات من سلسلة الكتل (blockchain)، ويطلب من LLM توصية بناءً على تلك المعلومات، وينفذ الصفقة التي يوصي بها LLM، ثم ينتظر ويكرر." +author: Ori Pomerantz +tags: [ "الذكاء الاصطناعي", "تداول", "وكيل", "python" ] +skill: intermediate +published: 2026-02-13 +lang: ar +sidebarDepth: 3 +--- + +في هذا البرنامج التعليمي، ستتعلم كيفية إنشاء وكيل تداول بسيط للذكاء الاصطناعي. يعمل هذا الوكيل باستخدام هذه الخطوات: + +1. قراءة الأسعار الحالية والسابقة لرمز مميز، بالإضافة إلى معلومات أخرى قد تكون ذات صلة +2. إنشاء استعلام بهذه المعلومات، بالإضافة إلى معلومات أساسية لشرح مدى صلتها بالموضوع +3. إرسال الاستعلام واستلام سعر متوقع +4. التداول بناءً على التوصية +5. انتظر وكرر + +يوضح هذا الوكيل كيفية قراءة المعلومات وترجمتها إلى استعلام ينتج عنه إجابة قابلة للاستخدام واستخدام تلك الإجابة. كل هذه خطوات مطلوبة لوكيل الذكاء الاصطناعي. يتم تنفيذ هذا الوكيل بلغة بايثون Python لأنها اللغة الأكثر شيوعًا المستخدمة في الذكاء الاصطناعي. + +## لماذا نفعل هذا؟ {#why-do-this} + +تسمح وكلاء التداول الآلي للمطورين باختيار وتنفيذ استراتيجية تداول. تسمح [وكلاء الذكاء الاصطناعي](/ai-agents) باستراتيجيات تداول أكثر تعقيدًا وديناميكية، ومن المحتمل استخدام معلومات وخوارزميات لم يفكر المطور في استخدامها. + +## الأدوات {#tools} + +يستخدم هذا البرنامج التعليمي [Python](https://www.python.org/) و[مكتبة Web3](https://web3py.readthedocs.io/en/stable/) و[Uniswap v3](https://github.com/Uniswap/v3-periphery) للحصول على عروض الأسعار والتداول. + +### لماذا لغة Python؟ {#python} + +اللغة الأكثر استخدامًا في الذكاء الاصطناعي هي [Python](https://www.python.org/)، لذلك نستخدمها هنا. لا تقلق إذا كنت لا تعرف لغة بايثون Python. اللغة واضحة جدًا، وسأشرح بالضبط ما تفعله. + +تُعد [مكتبة Web3](https://web3py.readthedocs.io/en/stable/) هي واجهة برمجة تطبيقات Ethereum API الأكثر شيوعًا في لغة Python. إنه سهل الاستخدام إلى حد ما. + +### التداول على سلسلة الكتل (blockchain) {#trading-on-blockchain} + +هناك [العديد من منصات التداول الموزعة (DEX)](/apps/categories/defi/) التي تتيح لك تداول الرموز المميزة على Ethereum. ومع ذلك، فإنها تميل إلى أن تكون لها أسعار صرف متشابهة بسبب [المراجحة](/developers/docs/smart-contracts/composability/#better-user-experience). + +تعتبر [Uniswap](https://app.uniswap.org/) منصة تداول لامركزية مستخدمة على نطاق واسع ويمكننا استخدامها لكل من عروض الأسعار (لرؤية القيم النسبية للرموز المميزة) والصفقات. + +### OpenAI {#openai} + +بالنسبة لنموذج لغوي كبير، اخترت أن أبدأ مع [OpenAI](https://openai.com/). لتشغيل التطبيق في هذا البرنامج التعليمي، ستحتاج إلى الدفع للوصول إلى واجهة برمجة التطبيقات API. الحد الأدنى للدفع وهو 5 دولارات هو أكثر من كافٍ. + +## التطوير، خطوة بخطوة {#step-by-step} + +لتبسيط التطوير، ننتقل على مراحل. كل خطوة هي فرع في GitHub. + +### البدء {#getting-started} + +هناك خطوات للبدء في استخدام UNIX أو Linux (بما في ذلك [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) + +1. إذا لم يكن لديك بالفعل، فقم بتنزيل وتثبيت [Python](https://www.python.org/downloads/). + +2. استنسخ مستودع GitHub. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. قم بتثبيت [`uv`](https://docs.astral.sh/uv/getting-started/installation/). قد يكون الأمر على نظامك مختلفًا. + + ```sh + pipx install uv + ``` + +4. قم بتنزيل المكتبات. + + ```sh + uv sync + ``` + +5. قم بتنشيط البيئة الافتراضية. + + ```sh + source .venv/bin/activate + ``` + +6. للتحقق من أن Python وWeb3 يعملان بشكل صحيح، قم بتشغيل `python3` وزوده بهذا البرنامج. يمكنك إدخاله في الموجه `>>>`؛ ليست هناك حاجة لإنشاء ملف. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### القراءة من سلسلة الكتل (blockchain) {#read-blockchain} + +الخطوة التالية هي القراءة من سلسلة الكتل (blockchain). للقيام بذلك، تحتاج إلى التغيير إلى الفرع `02-read-quote` ثم استخدام `uv` لتشغيل البرنامج. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +يجب أن تتلقى قائمة بكائنات `Quote`، لكل منها طابع زمني وسعر وأصل (حاليًا دائمًا `WETH/USDC`). + +إليك شرح سطر بسطر. + +```python +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 +``` + +استورد المكتبات التي نحتاجها. يتم شرحها أدناه عند استخدامها. + +```python +print = functools.partial(print, flush=True) +``` + +يستبدل `print` في لغة Python بإصدار يقوم دائمًا بتفريغ الإخراج على الفور. هذا مفيد في نص برمجي طويل الأمد لأننا لا نريد انتظار تحديثات الحالة أو إخراج تصحيح الأخطاء. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +عنوان URL للوصول إلى الشبكة الرئيسية (mainnet). يمكنك الحصول على واحدة من [العقدة كخدمة](/developers/docs/nodes-and-clients/nodes-as-a-service/) أو استخدام إحدى تلك المعلن عنها في [Chainlist](https://chainlist.org/chain/1). + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +تحدث كتلة الشبكة الرئيسية لـ Ethereum عادةً كل اثنتي عشرة ثانية، لذا فهذه هي عدد الكتل التي نتوقع حدوثها في فترة زمنية. لاحظ أن هذا ليس رقمًا دقيقًا. عندما يكون [مقدم الكتلة](/developers/docs/consensus-mechanisms/pos/block-proposal/) معطلاً، يتم تخطي تلك الكتلة، ويكون وقت الكتلة التالية 24 ثانية. إذا أردنا الحصول على الكتلة الدقيقة للطابع الزمني، فسنستخدم [البحث الثنائي](https://en.wikipedia.org/wiki/Binary_search). ومع ذلك، هذا قريب بما فيه الكفاية لأغراضنا. التنبؤ بالمستقبل ليس علمًا دقيقًا. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +حجم الدورة. نراجع عروض الأسعار مرة واحدة في كل دورة ونحاول تقدير القيمة في نهاية الدورة التالية. + +```python +# عنوان المجمع الذي نقرأ منه +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +تؤخذ قيم عروض الأسعار من مجمع Uniswap 3 USDC/WETH على العنوان [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract). هذا العنوان موجود بالفعل في شكل المجموع الاختباري، ولكن من الأفضل استخدام [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) لجعل الكود قابلاً لإعادة الاستخدام. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +هذه هي [واجهات التطبيق الثنائية ABIs](https://docs.soliditylang.org/en/latest/abi-spec.html) للعقدين اللذين نحتاج إلى الاتصال بهما. للحفاظ على الكود موجزًا، نقوم بتضمين الوظائف التي نحتاج إلى استدعائها فقط. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +قم ببدء مكتبة [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) واتصل بعقدة Ethereum. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +هذه إحدى طرق إنشاء فئة بيانات في لغة Python. يُستخدم نوع البيانات [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) للاتصال بالعقد. لاحظ `(frozen=True)`. في لغة بايثون Python، تُعرَّف [القيم المنطقية booleans](https://en.wikipedia.org/wiki/Boolean_data_type) على أنها `True` أو `False`، بأحرف كبيرة. فئة البيانات هذه `frozen`، مما يعني أنه لا يمكن تعديل الحقول. + +لاحظ المسافة البادئة. على عكس [اللغات المشتقة من C](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages)، تستخدم لغة Python المسافة البادئة للإشارة إلى الكتل. يعرف مفسر Python أن التعريف التالي ليس جزءًا من فئة البيانات هذه لأنه لا يبدأ بنفس المسافة البادئة لحقول فئة البيانات. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +يُستخدم النوع [`Decimal`](https://docs.python.org/3/library/decimal.html) للتعامل مع الكسور العشرية بدقة. + +```python + def get_price(self, block: int) -> Decimal: +``` + +هذه هي طريقة تعريف دالة في Python. التعريف بمسافة بادئة لإظهار أنه لا يزال جزءًا من `PoolInfo`. + +في دالة تعد جزءًا من فئة بيانات، يكون المعامل الأول دائمًا هو `self`، وهو مثيل فئة البيانات الذي تم استدعاؤه هنا. هنا يوجد معلمة أخرى، رقم الكتلة. + +```python + assert block <= w3.eth.block_number, "Block is in the future" +``` + +إذا استطعنا قراءة المستقبل، فلن نحتاج إلى الذكاء الاصطناعي للتداول. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +بناء الجملة لاستدعاء دالة على آلة الإيثريوم الافتراضية EVM من Web3 هو كالتالي: `.functions.().call()`. يمكن أن تكون المعلمات هي معلمات دالة آلة الإيثريوم الافتراضية EVM (إن وجدت؛ هنا لا توجد) أو [معلمات مسماة](https://en.wikipedia.org/wiki/Named_parameter) لتعديل سلوك سلسلة الكتل (blockchain). هنا نستخدم واحدًا، `block_identifier`، لتحديد [رقم الكتلة](/developers/docs/apis/json-rpc/#default-block) الذي نرغب في التشغيل فيه. + +النتيجة هي [هذه البنية، في شكل مصفوفة](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). القيمة الأولى هي دالة لسعر الصرف بين الرمزين. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +لتقليل الحسابات على السلسلة، لا يقوم Uniswap v3 بتخزين عامل الصرف الفعلي بل جذره التربيعي. نظرًا لأن آلة إيثريوم الافتراضية (EVM) لا تدعم حسابات النقطة العائمة أو الكسور، فبدلاً من القيمة الفعلية، تكون الاستجابة price296 + +```python + # (token1 لكل token0) + return 1/(raw_price * self.decimal_factor) +``` + +السعر الخام الذي نحصل عليه هو عدد `token0` الذي نحصل عليه مقابل كل `token1`. في مجمعنا، `token0` هو USDC (عملة مستقرة بنفس قيمة الدولار الأمريكي) و`token1` هو [WETH](https://opensea.io/learn/blockchain/what-is-weth). القيمة التي نريدها حقًا هي عدد الدولارات لكل WETH، وليس العكس. + +العامل العشري هو النسبة بين [العوامل العشرية](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) للرمزين. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +تمثل فئة البيانات هذه عرض سعر: سعر أصل معين في نقطة زمنية معينة. في هذه المرحلة، حقل `asset` غير ذي صلة لأننا نستخدم مجمعًا واحدًا وبالتالي لدينا أصل واحد. ومع ذلك، سنضيف المزيد من الأصول لاحقًا. + +```python +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 + ) +``` + +تأخذ هذه الدالة عنوانًا وتعيد معلومات حول عقد الرمز المميز في ذلك العنوان. لإنشاء [عقد Web3 `Contract` جديد](https://web3py.readthedocs.io/en/stable/web3.contract.html)، نوفر العنوان وواجهة التطبيق الثنائية ABI لـ `w3.eth.contract`. + +```python +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) + ) +``` + +تعيد هذه الدالة كل ما نحتاجه حول [مجمع معين](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol). البناء `f""` هو [سلسلة نصية منسقة](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +الحصول على كائن `Quote`. القيمة الافتراضية لـ `block_number` هي `None` (بدون قيمة). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +إذا لم يتم تحديد رقم كتلة، فاستخدم `w3.eth.block_number`، وهو أحدث رقم كتلة. هذا هو بناء الجملة لـ[عبارة `if`](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement). + +قد يبدو أنه كان من الأفضل تعيين القيمة الافتراضية على `w3.eth.block_number`، ولكن هذا لا يعمل جيدًا لأنه سيكون رقم الكتلة في وقت تعريف الدالة. في وكيل يعمل لفترة طويلة، ستكون هذه مشكلة. + +```python + 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 + ) +``` + +استخدم [مكتبة `datetime`](https://docs.python.org/3/library/datetime.html) لتنسيقها إلى تنسيق قابل للقراءة من قبل البشر ونماذج اللغة الكبيرة (LLMs). استخدم [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) لتقريب القيمة إلى منزلتين عشريتين. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +في لغة Python، يمكنك تعريف [قائمة](https://docs.python.org/3/library/stdtypes.html#typesseq-list) لا يمكن أن تحتوي إلا على نوع معين باستخدام `list[]`. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +في Python، تتكرر [حلقة `for`](https://docs.python.org/3/tutorial/controlflow.html#for-statements) عادةً على قائمة. تأتي قائمة أرقام الكتل التي يتم البحث عن عروض الأسعار فيها من [`range`](https://docs.python.org/3/library/stdtypes.html#range). + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +لكل رقم كتلة، احصل على كائن `Quote` وأضفه إلى قائمة `quotes`. ثم قم بإرجاع تلك القائمة. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +هذا هو الكود الرئيسي للبرنامج النصي. اقرأ معلومات المجمع، واحصل على اثني عشر عرض سعر، وقم بطباعتها بشكل جميل [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint). + +### إنشاء موجه {#prompt} + +بعد ذلك، نحتاج إلى تحويل هذه القائمة من عروض الأسعار إلى موجه لـ LLM والحصول على قيمة مستقبلية متوقعة. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +سيكون الإخراج الآن موجهًا إلى LLM، على غرار: + +``` +بالنظر إلى عروض الأسعار هذه: +الأصل: WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +الأصل: WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +ماذا تتوقع أن تكون قيمة WETH/USDC في الوقت 2026-02-02T17:56؟ + +قدم إجابتك كرقم واحد مقرب إلى منزلتين عشريتين، +بدون أي نص آخر. +``` + +لاحظ أن هناك عروض أسعار لأصلين هنا، `WETH/USDC` و`WBTC/WETH`. قد تؤدي إضافة عروض أسعار من أصل آخر إلى تحسين دقة التنبؤ. + +#### كيف يبدو الموجه {#prompt-explanation} + +يحتوي هذا الموجه على ثلاثة أقسام، وهي شائعة جدًا في موجهات LLM. + +1. المعلومات. تحتوي نماذج اللغة الكبيرة على الكثير من المعلومات من تدريبها، لكنها عادة لا تملك الأحدث. هذا هو سبب حاجتنا إلى استرداد أحدث عروض الأسعار هنا. تسمى إضافة المعلومات إلى موجه [التوليد المعزز بالاسترداد (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +2. السؤال الفعلي. هذا ما نريد أن نعرفه. + +3. تعليمات تنسيق الإخراج. عادة، سيعطينا نموذج اللغة الكبير تقديرًا مع شرح لكيفية وصوله إليه. هذا أفضل للبشر، لكن برنامج الكمبيوتر يحتاج فقط إلى الخلاصة. + +#### شرح الكود {#prompt-code} + +ها هو الكود الجديد. + +```python +from datetime import datetime, timezone, timedelta +``` + +نحتاج إلى تزويد LLM بالوقت الذي نريد تقديرًا له. للحصول على وقت "n دقيقة/ساعة/يوم" في المستقبل، نستخدم [فئة `timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta). + +```python +# عناوين المجمعات التي نقرأها +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +لدينا مجمعان نحتاج إلى قراءتهما. + +```python +@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 # (token1 per token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +في مجمع WETH/USDC، نريد أن نعرف كم عدد `token0` (USDC) الذي نحتاجه لشراء واحد من `token1` (WETH). في مجمع WETH/WBTC، نريد أن نعرف كم عدد `token1` (WETH) الذي نحتاجه لشراء واحد `token0` (WBTC، وهو بيتكوين مغلف). نحتاج إلى تتبع ما إذا كانت نسبة المجمع بحاجة إلى عكسها. + +```python +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 + ) +``` + +لمعرفة ما إذا كان المجمع بحاجة إلى عكسه، نحصل على ذلك كمدخل إلى `read_pool`. أيضًا، يجب إعداد رمز الأصل بشكل صحيح. + +إن البناء ` if else ` هو المكافئ في لغة Python لـ[المشغل الشرطي الثلاثي](https://en.wikipedia.org/wiki/Ternary_conditional_operator)، والذي سيكون في لغة مشتقة من C ` ? : `. + +```python +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 +``` + +تبني هذه الدالة سلسلة نصية تنسق قائمة بكائنات `Quote`، بافتراض أنها تنطبق جميعها على نفس الأصل. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +في Python، تُكتب [السلاسل النصية الحرفية متعددة الأسطر](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) على النحو التالي: `"""` .... `"""`. + +```python +Given these quotes: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +هنا، نستخدم نمط [MapReduce](https://en.wikipedia.org/wiki/MapReduce) لإنشاء سلسلة نصية لكل قائمة عرض أسعار باستخدام `format_quotes`، ثم نختصرها إلى سلسلة نصية واحدة لاستخدامها في الموجه. + +```python +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. + """ +``` + +بقية الموجه كما هو متوقع. + +```python +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 +) +``` + +راجع المجمعين واحصل على عروض أسعار من كليهما. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +حدد النقطة الزمنية المستقبلية التي نريد التقدير لها، وقم بإنشاء الموجه. + +### التفاعل مع LLM {#interface-llm} + +بعد ذلك، نقوم بتوجيه LLM فعلي ونحصل على قيمة مستقبلية متوقعة. لقد كتبت هذا البرنامج باستخدام OpenAI، لذا إذا كنت تريد استخدام مزود مختلف، فستحتاج إلى تعديله. + +1. احصل على [حساب OpenAI](https://auth.openai.com/create-account) + +2. [قم بتمويل الحساب](https://platform.openai.com/settings/organization/billing/overview)—الحد الأدنى للمبلغ في وقت كتابة هذا التقرير هو 5 دولارات + +3. [أنشئ مفتاح واجهة برمجة تطبيقات](https://platform.openai.com/settings/organization/api-keys) + +4. في سطر الأوامر، قم بتصدير مفتاح واجهة برمجة التطبيقات حتى يتمكن برنامجك من استخدامه + + ```sh + export OPENAI_API_KEY=sk-<بقية المفتاح هنا> + ``` + +5. سحب الوكيل وتشغيله + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +ها هو الكود الجديد. + +```python +from openai import OpenAI + +open_ai = OpenAI() # يقرأ العميل متغير البيئة OPENAI_API_KEY +``` + +قم باستيراد واجهة برمجة تطبيقات OpenAI وإنشاء مثيل لها. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +استدعاء واجهة برمجة تطبيقات OpenAI (`open_ai.chat.completions.create`) لإنشاء الاستجابة. + +```python +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") +``` + +إخراج السعر وتقديم توصية بالشراء أو البيع. + +#### اختبار التنبؤات {#testing-the-predictions} + +الآن بعد أن أصبح بإمكاننا إنشاء تنبؤات، يمكننا أيضًا استخدام البيانات التاريخية لتقييم ما إذا كنا ننتج تنبؤات مفيدة. + +```sh +uv run test-predictor.py +``` + +النتيجة المتوقعة مشابهة لـ: + +``` +التنبؤ لـ 2026-01-05T19:50: التنبؤ 3138.93 دولارًا أمريكيًا، الحقيقي 3218.92 دولارًا أمريكيًا، الخطأ 79.99 دولارًا أمريكيًا +التنبؤ لـ 2026-01-06T19:56: التنبؤ 3243.39 دولارًا أمريكيًا، الحقيقي 3221.08 دولارًا أمريكيًا، الخطأ 22.31 دولارًا أمريكيًا +التنبؤ لـ 2026-01-07T20:02: التنبؤ 3223.24 دولارًا أمريكيًا، الحقيقي 3146.89 دولارًا أمريكيًا، الخطأ 76.35 دولارًا أمريكيًا +التنبؤ لـ 2026-01-08T20:11: التنبؤ 3150.47 دولارًا أمريكيًا، الحقيقي 3092.04 دولارًا أمريكيًا، الخطأ 58.43 دولارًا أمريكيًا +. +. +. +التنبؤ لـ 2026-01-31T22:33: التنبؤ 2637.73 دولارًا أمريكيًا، الحقيقي 2417.77 دولارًا أمريكيًا، الخطأ 219.96 دولارًا أمريكيًا +التنبؤ لـ 2026-02-01T22:41: التنبؤ 2381.70 دولارًا أمريكيًا، الحقيقي 2318.84 دولارًا أمريكيًا، الخطأ 62.86 دولارًا أمريكيًا +التنبؤ لـ 2026-02-02T22:49: التنبؤ 2234.91 دولارًا أمريكيًا، الحقيقي 2349.28 دولارًا أمريكيًا، الخطأ 114.37 دولارًا أمريكيًا +متوسط خطأ التنبؤ على مدار 29 تنبؤًا: 83.87103448275862068965517241 دولارًا أمريكيًا +متوسط التغيير لكل توصية: 4.787931034482758620689655172 دولار أمريكي +التباين المعياري للتغيرات: 104.42 دولار أمريكي +أيام مربحة: 51.72% +أيام خاسرة: 48.28% +``` + +معظم المختبر مطابق للوكيل، ولكن إليك الأجزاء الجديدة أو المعدلة. + +```python +CYCLES_FOR_TEST = 40 # للاختبار الخلفي، كم عدد الدورات التي نختبرها + +# الحصول على الكثير من عروض الأسعار +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 +) +``` + +نحن ننظر إلى `CYCLES_FOR_TEST` (المحدد هنا بـ 40) يومًا إلى الوراء. + +```python +# إنشاء تنبؤات والتحقق منها مقابل التاريخ الحقيقي + +total_error = Decimal(0) +changes = [] +``` + +هناك نوعان من الأخطاء التي نهتم بها. الأول، `total_error`، هو ببساطة مجموع الأخطاء التي ارتكبها المتنبئ. + +لفهم الثاني، `changes`، نحتاج إلى تذكر غرض الوكيل. ليس التنبؤ بنسبة WETH/USDC (سعر ETH). بل إصدار توصيات البيع والشراء. إذا كان السعر حاليًا 2000 دولار وتنبأ بـ 2010 دولارات غدًا، فإننا لا نمانع إذا كانت النتيجة الفعلية 2020 دولارًا وحققنا أموالًا إضافية. لكننا نمانع إذا تنبأ بـ 2010 دولارات، واشترى ETH بناءً على تلك التوصية، وانخفض السعر إلى 1990 دولارًا. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +لا يمكننا النظر إلا في الحالات التي يتوفر فيها السجل الكامل (القيم المستخدمة للتنبؤ والقيمة الحقيقية لمقارنتها بها). وهذا يعني أن أحدث حالة يجب أن تكون تلك التي بدأت قبل `CYCLES_BACK`. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +استخدم [شرائح](https://www.w3schools.com/python/ref_func_slice.asp) للحصول على نفس عدد العينات التي يستخدمها الوكيل. الكود بين هنا والجزء التالي هو نفس كود الحصول على التنبؤ الموجود لدينا في الوكيل. + +```python + 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 +``` + +احصل على السعر المتوقع والسعر الحقيقي والسعر وقت التنبؤ. نحتاج إلى السعر وقت التنبؤ لتحديد ما إذا كانت التوصية بالشراء أو البيع. + +```python + 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") +``` + +احسب الخطأ، وأضفه إلى الإجمالي. + +```python + recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell' + price_increase = real_price - prediction_time_price + changes.append(price_increase if recomended_action == 'buy' else -price_increase) +``` + +بالنسبة لـ `changes`، نريد التأثير النقدي لشراء أو بيع عملة ETH واحدة. لذا أولاً، نحتاج إلى تحديد التوصية، ثم تقييم كيفية تغير السعر الفعلي، وما إذا كانت التوصية قد حققت ربحًا (تغيير إيجابي) أو كلفت أموالًا (تغيير سلبي). + +```python +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") +``` + +تقرير النتائج. + +```python +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%}") +``` + +استخدم [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) لحساب عدد الأيام المربحة وعدد الأيام المكلفة. النتيجة هي كائن مرشح، والذي نحتاج إلى تحويله إلى قائمة للحصول على الطول. + +### إرسال المعاملات {#submit-txn} + +الآن نحن بحاجة إلى إرسال المعاملات بالفعل. ومع ذلك، لا أريد إنفاق أموال حقيقية في هذه المرحلة، قبل إثبات النظام. بدلاً من ذلك، سننشئ انقسام محلي للشبكة الرئيسية، و "نتداول" على تلك الشبكة. + +فيما يلي خطوات إنشاء انقسام محلي وتمكين التداول. + +1. قم بتثبيت [Foundry](https://getfoundry.sh/introduction/installation) + +2. ابدأ [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + يستمع `anvil` على عنوان URL الافتراضي لـ Foundry، http://localhost:8545، لذلك لا نحتاج إلى تحديد عنوان URL لأمر `cast` الذي نستخدمه لمعالجة سلسلة الكتل (blockchain). + +3. عند التشغيل في `anvil`، هناك عشرة حسابات اختبار تحتوي على ETH—قم بتعيين متغيرات البيئة للحساب الأول + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. هذه هي العقود التي نحتاج إلى استخدامها. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) هو عقد Uniswap v3 الذي نستخدمه للتداول الفعلي. يمكننا التداول مباشرة من خلال المجمع، ولكن هذا أسهل بكثير. + + المتغيران السفليان هما مسارات Uniswap v3 المطلوبة للتبديل بين WETH وUSDC. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. يحتوي كل حساب من حسابات الاختبار على 10000 ETH. استخدم عقد WETH لتغليف 1000 ETH للحصول على 1000 WETH للتداول. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. استخدم `SwapRouter` لتداول 500 WETH مقابل USDC. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + ينشئ استدعاء `approve` بدلًا يسمح لـ `SwapRouter` بإنفاق بعض رموزنا. لا يمكن للعقود مراقبة الأحداث، لذلك إذا قمنا بنقل الرموز المميزة مباشرة إلى عقد `SwapRouter`، فلن يعرف أنه تم الدفع له. بدلاً من ذلك، نسمح لعقد `SwapRouter` بإنفاق مبلغ معين، ثم يقوم `SwapRouter` بذلك. يتم ذلك من خلال دالة يستدعيها `SwapRouter`، لذلك يعرف ما إذا كانت ناجحة. + +7. تحقق من أن لديك ما يكفي من كلا الرمزين. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +الآن بعد أن أصبح لدينا WETH وUSDC، يمكننا تشغيل الوكيل بالفعل. + +```sh +git checkout 05-trade +uv run agent.py +``` + +سيبدو الإخراج مشابهًا لـ: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +السعر الحالي: 1843.16 +في 2026-02-06T23:07، السعر المتوقع: 1724.41 دولار أمريكي +أرصدة الحسابات قبل التداول: +رصيد USDC: 927301.578272 +رصيد WETH: 500 +بيع، أتوقع أن ينخفض السعر بمقدار 118.75 دولارًا أمريكيًا +تم إرسال معاملة الموافقة: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +تم تعدين معاملة الموافقة. +تم إرسال معاملة البيع: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +تم تعدين معاملة البيع. +أرصدة الحسابات بعد التداول: +رصيد USDC: 929143.797116 +رصيد WETH: 499 +``` + +لاستخدامه بالفعل، تحتاج إلى بعض التغييرات الطفيفة. + +- في السطر 14، قم بتغيير `MAINNET_URL` إلى نقطة وصول حقيقية، مثل `https://eth.drpc.org` +- في السطر 28، قم بتغيير `PRIVATE_KEY` إلى مفتاحك الخاص +- ما لم تكن ثريًا جدًا ويمكنك شراء أو بيع 1 ETH كل يوم لوكيل غير مثبت، فقد ترغب في تغيير 29 لتقليل `WETH_TRADE_AMOUNT` + +#### شرح الكود {#trading-code} + +ها هو الكود الجديد. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +نفس المتغيرات التي استخدمناها في الخطوة 4. + +```python +WETH_TRADE_AMOUNT=1 +``` + +المبلغ المراد تداوله. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +للتداول فعليًا، نحتاج إلى وظيفة `approve`. نريد أيضًا إظهار الأرصدة قبل وبعد، لذلك نحتاج أيضًا إلى `balanceOf`. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +في واجهة التطبيق الثنائية `SwapRouter`، نحتاج فقط إلى `exactInput`. هناك وظيفة ذات صلة، `exactOutput`، يمكننا استخدامها لشراء WETH واحد بالضبط، ولكن من أجل البساطة نستخدم `exactInput` فقط في كلتا الحالتين. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +تعريفات Web3 للحساب `account` ([https://web3py.readthedocs.io/en/stable/web3.eth.account.html](https://web3py.readthedocs.io/en/stable/web3.eth.account.html)) وعقد `SwapRouter`. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +معلمات المعاملة. نحتاج إلى وظيفة هنا لأن [النون](https://en.wikipedia.org/wiki/Cryptographic_nonce) يجب أن يتغير في كل مرة. + +```python +def approve_token(contract: Contract, amount: int): +``` + +الموافقة على بدل رمز مميز لـ `SwapRouter`. + +```python + txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params()) + signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY) + tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction) +``` + +هذه هي الطريقة التي نرسل بها معاملة في Web3. أولاً نستخدم كائن `Contract` ([https://web3py.readthedocs.io/en/stable/web3.contract.html](https://web3py.readthedocs.io/en/stable/web3.contract.html)) لبناء المعاملة. ثم نستخدم `web3.eth.account.sign_transaction` ([https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction)) لتوقيع المعاملة، باستخدام `PRIVATE_KEY`. أخيرًا، نستخدم `w3.eth.send_raw_transaction` ([https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction)) لإرسال المعاملة. + +```python + 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` ([https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt)) حتى يتم تعدين المعاملة. يعيد الإيصال إذا لزم الأمر. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +هذه هي المعلمات عند بيع WETH. + +```python +def make_buy_params(quote: Quote) -> dict: + return { + "path": USDC_TO_WETH, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals, + "amountOutMinimum": 0, + } +``` + +على عكس `SELL_PARAMS`، يمكن أن تتغير معلمات الشراء. مبلغ الإدخال هو تكلفة 1 WETH، كما هو متاح في `quote`. + +```python +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.") +``` + +وظائف `buy()` و `sell()` متطابقة تقريبًا. أولاً، نوافق على بدل كافٍ لـ `SwapRouter`، ثم نستدعيه بالمسار والمبلغ الصحيحين. + +```python +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)}") +``` + +تقرير أرصدة المستخدم بالعملتين. + +```python +print("أرصدة الحساب قبل التداول:") +balances() + +if (expected_price > current_price): + print(f"شراء، أتوقع أن يرتفع السعر بمقدار {expected_price - current_price} دولار أمريكي") + buy(wethusdc_quotes[-1]) +else: + print(f"بيع، أتوقع أن ينخفض السعر بمقدار {current_price - expected_price} دولار أمريكي") + sell() + +print("أرصدة الحساب بعد التداول:") +balances() +``` + +يعمل هذا الوكيل حاليًا مرة واحدة فقط. ومع ذلك، يمكنك تغييره للعمل بشكل مستمر إما عن طريق تشغيله من [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) أو عن طريق تغليف الأسطر 368-400 في حلقة واستخدام [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) للانتظار حتى يحين وقت الدورة التالية. + +## التحسينات الممكنة {#improvements} + +هذه ليست نسخة إنتاج كاملة؛ إنها مجرد مثال لتعليم الأساسيات. فيما يلي بعض الأفكار للتحسينات. + +### تداول أذكى {#smart-trading} + +هناك حقيقتان مهمتان يتجاهلهما الوكيل عند تحديد ما يجب القيام به. + +- _حجم التغيير المتوقع_. يبيع الوكيل كمية ثابتة من `WETH` إذا كان من المتوقع أن ينخفض السعر، بغض النظر عن حجم الانخفاض. + يمكن القول إنه من الأفضل تجاهل التغييرات الطفيفة والبيع بناءً على مدى توقعنا لانخفاض السعر. +- _المحفظة الحالية_. إذا كانت 10% من محفظتك في WETH وتعتقد أن السعر سيرتفع، فمن المحتمل أن يكون من المنطقي شراء المزيد. ولكن إذا كانت 90% من محفظتك في WETH، فقد تكون معرضًا بشكل كافٍ، وليست هناك حاجة لشراء المزيد. والعكس صحيح إذا كنت تتوقع أن ينخفض السعر. + +### ماذا لو كنت تريد أن تبقي استراتيجية التداول الخاصة بك سرية؟ {#secret} + +يمكن لبائعي الذكاء الاصطناعي رؤية الاستعلامات التي ترسلها إلى نماذج اللغة الكبيرة الخاصة بهم، مما قد يكشف عن نظام التداول العبقري الذي طورته مع وكيلك. نظام التداول الذي يستخدمه عدد كبير جدًا من الأشخاص لا قيمة له لأن عددًا كبيرًا جدًا من الأشخاص يحاولون الشراء عندما تريد الشراء (ويرتفع السعر) ويحاولون البيع عندما تريد البيع (وينخفض السعر). + +يمكنك تشغيل LLM محليًا، على سبيل المثال، باستخدام [LM-Studio](https://lmstudio.ai/)، لتجنب هذه المشكلة. + +### من بوت الذكاء الاصطناعي إلى وكيل الذكاء الاصطناعي {#bot-to-agent} + +يمكنك تقديم حجة جيدة بأن هذا [بوت ذكاء اصطناعي، وليس وكيل ذكاء اصطناعي](/ai-agents/#ai-agents-vs-ai-bots). إنه يطبق استراتيجية بسيطة نسبيًا تعتمد على معلومات محددة مسبقًا. يمكننا تمكين التحسين الذاتي، على سبيل المثال، من خلال توفير قائمة بمجمعات Uniswap v3 وأحدث قيمها والسؤال عن المجموعة التي لديها أفضل قيمة تنبؤية. + +### الحماية من الانزلاق {#slippage-protection} + +حاليًا لا توجد [حماية من الانزلاق](https://uniswapv3book.com/milestone_3/slippage-protection.html). إذا كان السعر الحالي 2000 دولار، والسعر المتوقع 2100 دولار، فسيقوم الوكيل بالشراء. ومع ذلك، إذا ارتفعت التكلفة إلى 2200 دولار قبل أن يشتري الوكيل، فلن يكون هناك معنى للشراء بعد الآن. + +لتنفيذ الحماية من الانزلاق، حدد قيمة `amountOutMinimum` في السطرين 325 و 334 من [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325). + +## الخلاصة {#conclusion} + +نأمل أن تعرف الآن ما يكفي للبدء في استخدام وكلاء الذكاء الاصطناعي. هذه ليست نظرة عامة شاملة على الموضوع؛ فهناك كتب كاملة مخصصة لذلك، لكن هذا يكفي لتبدأ. حظ سعيد! + +[انظر هنا لمزيد من أعمالي](https://cryptodocguy.pro/). diff --git a/public/content/translations/bn/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/bn/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..27730f4807f --- /dev/null +++ b/public/content/translations/bn/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "ইথেরিয়ামে আপনার নিজস্ব AI ট্রেডিং এজেন্ট তৈরি করুন" +description: "এই টিউটোরিয়ালে আপনি একটি সহজ AI ট্রেডিং এজেন্ট কীভাবে তৈরি করতে হয় তা শিখবেন। এই এজেন্টটি ব্লকচেইন থেকে তথ্য পড়ে, সেই তথ্যের উপর ভিত্তি করে একটি LLM-এর কাছে সুপারিশ চায়, LLM-এর সুপারিশকৃত ট্রেডটি সম্পাদন করে এবং তারপর অপেক্ষা করে ও পুনরাবৃত্তি করে।" +author: Ori Pomerantz +tags: [ "AI", "ট্রেডিং", "এজেন্ট", "python" ] +skill: intermediate +published: 2026-02-13 +lang: bn +sidebarDepth: 3 +--- + +এই টিউটোরিয়ালে আপনি একটি সহজ AI ট্রেডিং এজেন্ট কীভাবে তৈরি করতে হয় তা শিখবেন। এই এজেন্টটি এই ধাপগুলো ব্যবহার করে কাজ করে: + +1. একটি টোকেনের বর্তমান এবং অতীতের মূল্য, সেইসাথে অন্যান্য সম্ভাব্য প্রাসঙ্গিক তথ্য পড়ুন +2. এই তথ্যের সাথে একটি কোয়েরি তৈরি করুন, সাথে ব্যাকগ্রাউন্ড তথ্য যোগ করে ব্যাখ্যা করুন যে এটি কীভাবে প্রাসঙ্গিক হতে পারে +3. কোয়েরি জমা দিন এবং একটি অনুমানিত মূল্য ফেরত পান +4. সুপারিশের ভিত্তিতে ট্রেড করুন +5. অপেক্ষা করুন এবং পুনরাবৃত্তি করুন + +এই এজেন্টটি দেখায় কীভাবে তথ্য পড়তে হয়, এটিকে একটি কোয়েরিতে রূপান্তর করতে হয় যা একটি ব্যবহারযোগ্য উত্তর দেয় এবং সেই উত্তরটি ব্যবহার করতে হয়। এগুলো সবই একটি AI এজেন্টের জন্য প্রয়োজনীয় ধাপ। এই এজেন্টটি Python-এ প্রয়োগ করা হয়েছে কারণ এটি AI-তে ব্যবহৃত সবচেয়ে সাধারণ ভাষা। + +## এটা কেন করবেন? {#why-do-this} + +স্বয়ংক্রিয় ট্রেডিং এজেন্ট ডেভেলপারদের একটি ট্রেডিং কৌশল নির্বাচন এবং কার্যকর করার সুযোগ দেয়। [AI এজেন্ট](/ai-agents) আরও জটিল এবং গতিশীল ট্রেডিং কৌশলের সুযোগ দেয়, সম্ভাব্যভাবে এমন তথ্য এবং অ্যালগরিদম ব্যবহার করে যা ডেভেলপার ব্যবহার করার কথাও ভাবেনি। + +## টুলস {#tools} + +এই টিউটোরিয়ালটি কোট এবং ট্রেডিংয়ের জন্য [Python](https://www.python.org/), [Web3 লাইব্রেরি](https://web3py.readthedocs.io/en/stable/) এবং [Uniswap v3](https://github.com/Uniswap/v3-periphery) ব্যবহার করে। + +### কেন Python? {#python} + +AI-এর জন্য সবচেয়ে বেশি ব্যবহৃত ভাষা হল [Python](https://www.python.org/), তাই আমরা এখানে এটি ব্যবহার করেছি। আপনি Python না জানলেও চিন্তা করবেন না। ভাষাটি খুব স্পষ্ট, এবং আমি ব্যাখ্যা করব এটি ঠিক কী করে। + +[Web3 লাইব্রেরি](https://web3py.readthedocs.io/en/stable/) হল সবচেয়ে সাধারণ Python ইথেরিয়াম API। এটি ব্যবহার করা বেশ সহজ। + +### ব্লকচেইনে ট্রেডিং {#trading-on-blockchain} + +[অনেক ডিস্ট্রিবিউটেড এক্সচেঞ্জ (DEX)](/apps/categories/defi/) আছে যা আপনাকে ইথেরিয়ামে টোকেন ট্রেড করার সুযোগ দেয়। তবে, [আর্বিট্রেজ](/developers/docs/smart-contracts/composability/#better-user-experience)-এর কারণে তাদের এক্সচেঞ্জ রেট প্রায় একই রকম থাকে। + +[Uniswap](https://app.uniswap.org/) একটি বহুল ব্যবহৃত DEX যা আমরা কোট (টোকেনের আপেক্ষিক মান দেখতে) এবং ট্রেড উভয়ের জন্য ব্যবহার করতে পারি। + +### OpenAI {#openai} + +একটি বড় ল্যাঙ্গুয়েজ মডেলের জন্য, আমি [OpenAI](https://openai.com/) দিয়ে শুরু করতে চেয়েছি। এই টিউটোরিয়ালের অ্যাপ্লিকেশনটি চালানোর জন্য আপনাকে API অ্যাক্সেসের জন্য অর্থপ্রদান করতে হবে। $5-এর ন্যূনতম পেমেন্ট যথেষ্টর চেয়েও বেশি। + +## ডেভেলপমেন্ট, ধাপে ধাপে {#step-by-step} + +ডেভেলপমেন্টকে সহজ করার জন্য, আমরা ধাপে ধাপে এগোব। প্রতিটি ধাপ GitHub-এর একটি ব্রাঞ্চ। + +### শুরু করা যাক {#getting-started} + +UNIX বা Linux-এর অধীনে শুরু করার জন্য কিছু ধাপ রয়েছে (এর মধ্যে [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) অন্তর্ভুক্ত) + +1. আপনার কাছে যদি এটি আগে থেকে না থাকে, তাহলে [Python](https://www.python.org/downloads/) ডাউনলোড এবং ইনস্টল করুন। + +2. GitHub রিপোজিটরিটি ক্লোন করুন। + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. [`uv`](https://docs.astral.sh/uv/getting-started/installation/) ইনস্টল করুন। আপনার সিস্টেমে কমান্ডটি ভিন্ন হতে পারে। + + ```sh + pipx install uv + ``` + +4. লাইব্রেরিগুলো ডাউনলোড করুন। + + ```sh + uv sync + ``` + +5. ভার্চুয়াল এনভায়রনমেন্ট সক্রিয় করুন। + + ```sh + source .venv/bin/activate + ``` + +6. Python এবং Web3 সঠিকভাবে কাজ করছে কিনা তা যাচাই করতে, `python3` চালান এবং এটিকে এই প্রোগ্রামটি সরবরাহ করুন। আপনি এটি `>>>` প্রম্পটে প্রবেশ করতে পারেন; একটি ফাইল তৈরি করার প্রয়োজন নেই। + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### ব্লকচেইন থেকে পড়া {#read-blockchain} + +পরবর্তী ধাপ হল ব্লকচেইন থেকে পড়া। এটি করার জন্য, আপনাকে `02-read-quote` ব্রাঞ্চে পরিবর্তন করতে হবে এবং তারপর প্রোগ্রামটি চালানোর জন্য `uv` ব্যবহার করতে হবে। + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +আপনার একটি `Quote` অবজেক্টের তালিকা পাওয়া উচিত, প্রতিটিতে একটি টাইমস্ট্যাম্প, একটি মূল্য এবং অ্যাসেট (বর্তমানে সবসময় `WETH/USDC`) থাকবে। + +এখানে একটি লাইন-বাই-লাইন ব্যাখ্যা দেওয়া হলো। + +```python +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 +``` + +আমাদের প্রয়োজনীয় লাইব্রেরিগুলি আমদানি করুন। এগুলি ব্যবহার করার সময় নীচে ব্যাখ্যা করা হয়েছে। + +```python +print = functools.partial(print, flush=True) +``` + +Python-এর `print`-কে এমন একটি সংস্করণ দিয়ে প্রতিস্থাপন করে যা সবসময় অবিলম্বে আউটপুট ফ্লাশ করে। এটি একটি দীর্ঘ-চলমান স্ক্রিপ্টে উপযোগী কারণ আমরা স্ট্যাটাস আপডেট বা ডিবাগিং আউটপুটের জন্য অপেক্ষা করতে চাই না। + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +মেইননেটে যাওয়ার জন্য একটি URL। আপনি [নোড অ্যাজ এ সার্ভিস](/developers/docs/nodes-and-clients/nodes-as-a-service/) থেকে একটি পেতে পারেন বা [Chainlist](https://chainlist.org/chain/1)-এ বিজ্ঞাপিত একটি ব্যবহার করতে পারেন। + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +একটি ইথেরিয়াম মেইননেট ব্লক সাধারণত প্রতি বারো সেকেন্ডে ঘটে, তাই এগুলি হল ব্লকের সংখ্যা যা আমরা একটি সময়কালে ঘটার আশা করি। লক্ষ্য করুন যে এটি একটি সঠিক সংখ্যা নয়। যখন [ব্লক প্রস্তাবক](/developers/docs/consensus-mechanisms/pos/block-proposal/) ডাউন থাকে, তখন সেই ব্লকটি এড়িয়ে যাওয়া হয় এবং পরবর্তী ব্লকের জন্য সময় হল 24 সেকেন্ড। আমরা যদি একটি টাইমস্ট্যাম্পের জন্য সঠিক ব্লক পেতে চাই, তাহলে আমরা [বাইনারি সার্চ](https://en.wikipedia.org/wiki/Binary_search) ব্যবহার করব। তবে, এটি আমাদের উদ্দেশ্যের জন্য যথেষ্ট কাছাকাছি। ভবিষ্যদ্বাণী করা কোনো সঠিক বিজ্ঞান নয়। + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +চক্রের আকার। আমরা প্রতি চক্রে একবার কোট পর্যালোচনা করি এবং পরবর্তী চক্রের শেষে মান অনুমান করার চেষ্টা করি। + +```python +# আমরা যে পুলটি পড়ছি তার অ্যাড্রেস +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +কোট মানগুলি Uniswap 3 USDC/WETH পুল থেকে [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract) অ্যাড্রেসে নেওয়া হয়েছে। এই অ্যাড্রেসটি ইতিমধ্যেই চেকসাম ফর্মে আছে, কিন্তু কোডটিকে পুনঃব্যবহারযোগ্য করতে [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) ব্যবহার করা ভাল। + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +এগুলো হল দুটি কন্ট্র্যাক্টের [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html) যার সাথে আমাদের যোগাযোগ করতে হবে। কোডটি সংক্ষিপ্ত রাখতে, আমরা শুধুমাত্র সেই ফাংশনগুলো অন্তর্ভুক্ত করি যা আমাদের কল করতে হবে। + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +[`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) লাইব্রেরি শুরু করুন এবং একটি ইথেরিয়াম নোডের সাথে সংযোগ স্থাপন করুন। + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +এটি Python-এ একটি ডেটা ক্লাস তৈরি করার একটি উপায়। কন্ট্র্যাক্টের সাথে সংযোগ স্থাপন করতে [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) ডেটা টাইপ ব্যবহার করা হয়। ` (frozen=True)` লক্ষ্য করুন। Python-এ [বুলিয়ান](https://en.wikipedia.org/wiki/Boolean_data_type)-কে `True` বা `False` হিসেবে সংজ্ঞায়িত করা হয়, যা ক্যাপিটালাইজড। এই ডেটা ক্লাসটি `frozen`, যার মানে ফিল্ডগুলি পরিবর্তন করা যাবে না। + +ইনডেনটেশন লক্ষ্য করুন। [C-থেকে উদ্ভূত ভাষা](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages)-এর বিপরীতে, Python ব্লক বোঝাতে ইনডেনটেশন ব্যবহার করে। Python ইন্টারপ্রেটার জানে যে নিম্নলিখিত সংজ্ঞাটি এই ডেটা ক্লাসের অংশ নয় কারণ এটি ডেটা ক্লাসের ফিল্ডগুলির মতো একই ইনডেনটেশন থেকে শুরু হয় না। + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +দশমিক ভগ্নাংশ সঠিকভাবে পরিচালনা করার জন্য [`Decimal`](https://docs.python.org/3/library/decimal.html) টাইপ ব্যবহার করা হয়। + +```python + def get_price(self, block: int) -> Decimal: +``` + +এটি Python-এ একটি ফাংশন সংজ্ঞায়িত করার উপায়। সংজ্ঞাটি ইনডেন্ট করা হয়েছে এটি দেখানোর জন্য যে এটি এখনও `PoolInfo`-এর অংশ। + +একটি ডেটা ক্লাসের অংশ এমন একটি ফাংশনে প্রথম প্যারামিটারটি সর্বদা `self`, যা এখানে কল করা ডেটা ক্লাস ইনস্ট্যান্স। এখানে আরেকটি প্যারামিটার আছে, ব্লক নম্বর। + +```python + assert block <= w3.eth.block_number, "Block is in the future" +``` + +আমরা যদি ভবিষ্যৎ পড়তে পারতাম, তবে ট্রেডিংয়ের জন্য AI-এর প্রয়োজন হতো না। + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +Web3 থেকে EVM-এ একটি ফাংশন কল করার সিনট্যাক্স হল: `.functions."().call()`। প্যারামিটারগুলো EVM ফাংশনের প্যারামিটার হতে পারে (যদি থাকে; এখানে নেই) অথবা ব্লকচেইন আচরণ পরিবর্তন করার জন্য [নামযুক্ত প্যারামিটার](https://en.wikipedia.org/wiki/Named_parameter) হতে পারে। এখানে আমরা `block_identifier` ব্যবহার করি, যা [ব্লক নম্বর](/developers/docs/apis/json-rpc/#default-block) নির্দিষ্ট করে যেখানে আমরা চালাতে চাই। + +ফলাফল হল [এই struct, অ্যারে ফর্মে](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72)। প্রথম মানটি দুটি টোকেনের মধ্যে এক্সচেঞ্জ রেটের একটি ফাংশন। + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +অনচেইন গণনা কমাতে, Uniswap v3 আসল এক্সচেঞ্জ ফ্যাক্টর সংরক্ষণ করে না বরং এর বর্গমূল সংরক্ষণ করে। যেহেতু EVM ফ্লোটিং পয়েন্ট ম্যাথ বা ভগ্নাংশ সমর্থন করে না, তাই আসল মানের পরিবর্তে, প্রতিক্রিয়া হল price296 + +```python + # (টোকেন0 প্রতি টোকেন1) + return 1/(raw_price * self.decimal_factor) +``` + +আমরা যে কাঁচা মূল্য পাই তা হল `টোকেন1`-এর প্রতি আমরা কত `টোকেন0` পাব তার সংখ্যা। আমাদের পুলে `টোকেন0` হল USDC (মার্কিন ডলারের সমান মূল্যের স্টেবলকয়েন) এবং `টোকেন1` হল [WETH](https://opensea.io/learn/blockchain/what-is-weth)। আমরা আসলে যে মানটি চাই তা হল প্রতি WETH-এর জন্য ডলারের সংখ্যা, এর বিপরীত নয়। + +ডেসিমাল ফ্যাক্টর হলো দুটি টোকেনের [ডেসিমাল ফ্যাক্টর](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) এর মধ্যকার অনুপাত। + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +এই ডেটা ক্লাসটি একটি কোট উপস্থাপন করে: একটি নির্দিষ্ট সময়ে একটি নির্দিষ্ট অ্যাসেটের মূল্য। এই মুহূর্তে, `asset` ফিল্ডটি অপ্রাসঙ্গিক কারণ আমরা একটি একক পুল ব্যবহার করি এবং তাই একটি একক অ্যাসেট আছে। তবে, আমরা পরে আরও অ্যাসেট যোগ করব। + +```python +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 + ) +``` + +এই ফাংশনটি একটি অ্যাড্রেস নেয় এবং সেই অ্যাড্রেসে থাকা টোকেন কন্ট্র্যাক্ট সম্পর্কে তথ্য প্রদান করে। একটি নতুন [Web3 `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) তৈরি করতে, আমরা `w3.eth.contract`-কে অ্যাড্রেস এবং ABI প্রদান করি। + +```python +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) + ) +``` + +এই ফাংশনটি [একটি নির্দিষ্ট পুল](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol) সম্পর্কে আমাদের প্রয়োজনীয় সবকিছু প্রদান করে। সিনট্যাক্স `f""` একটি [ফরম্যাটেড স্ট্রিং](https://docs.python.org/3/reference/lexical_analysis.html#f-strings)। + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +একটি `Quote` অবজেক্ট পান। `block_number`-এর ডিফল্ট মান হল `None` (কোনো মান নেই)। + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +যদি কোনো ব্লক নম্বর নির্দিষ্ট করা না হয়, তাহলে `w3.eth.block_number` ব্যবহার করুন, যা সর্বশেষ ব্লক নম্বর। এটি [একটি `if` স্টেটমেন্টের](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement) সিনট্যাক্স। + +এটা দেখে মনে হতে পারে যে ডিফল্ট হিসেবে `w3.eth.block_number` সেট করাই ভালো ছিল, কিন্তু সেটা ঠিকভাবে কাজ করে না কারণ এটা ফাংশনটি সংজ্ঞায়িত করার সময়ের ব্লক নম্বর হতো। একটি দীর্ঘ-চলমান এজেন্টে, এটি একটি সমস্যা হবে। + +```python + 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 + ) +``` + +মানুষ এবং বড় ভাষার মডেলের (LLM) জন্য পঠনযোগ্য ফরম্যাটে ফরম্যাট করতে [`datetime` লাইব্রেরি](https://docs.python.org/3/library/datetime.html) ব্যবহার করুন। মানটিকে দুই দশমিক স্থানে রাউন্ড করতে [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) ব্যবহার করুন। + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +Python-এ আপনি `list[]` ব্যবহার করে একটি [তালিকা](https://docs.python.org/3/library/stdtypes.html#typesseq-list) সংজ্ঞায়িত করেন যা শুধুমাত্র একটি নির্দিষ্ট টাইপ ধারণ করতে পারে। + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +Python-এ একটি [`for` লুপ](https://docs.python.org/3/tutorial/controlflow.html#for-statements) সাধারণত একটি তালিকার উপর পুনরাবৃত্তি করে। কোট খুঁজে বের করার জন্য ব্লক নম্বরের তালিকাটি [`range`](https://docs.python.org/3/library/stdtypes.html#range) থেকে আসে। + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +প্রতিটি ব্লক নম্বরের জন্য, একটি `Quote` অবজেক্ট পান এবং এটিকে `quotes` তালিকায় যোগ করুন। তারপর সেই তালিকাটি ফেরত দিন। + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +এটি স্ক্রিপ্টের মূল কোড। পুল তথ্য পড়ুন, বারোটি কোট পান, এবং সেগুলোকে [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) করুন। + +### একটি প্রম্পট তৈরি করা {#prompt} + +এরপরে, আমাদের এই কোটগুলোর তালিকাকে একটি LLM-এর জন্য প্রম্পটে রূপান্তর করতে হবে এবং একটি প্রত্যাশিত ভবিষ্যৎ মান পেতে হবে। + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +আউটপুট এখন একটি LLM-এর জন্য একটি প্রম্পট হবে, যা দেখতে অনেকটা এইরকম: + +``` +এই কোটগুলো দেওয়া আছে: +অ্যাসেট: WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +অ্যাসেট: WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +2026-02-02T17:56 সময়ে WETH/USDC-এর মান কী হতে পারে বলে আপনি আশা করেন? + +আপনার উত্তরটি দুটি দশমিক স্থানে রাউন্ড করা একটি একক সংখ্যা হিসেবে দিন, +অন্য কোনো লেখা ছাড়া। +``` + +লক্ষ্য করুন যে এখানে দুটি অ্যাসেটের জন্য কোট রয়েছে, `WETH/USDC` এবং `WBTC/WETH`। অন্য একটি অ্যাসেট থেকে কোট যোগ করলে ভবিষ্যদ্বাণীর নির্ভুলতা উন্নত হতে পারে। + +#### একটি প্রম্পট কেমন দেখতে হয় {#prompt-explanation} + +এই প্রম্পটটিতে তিনটি বিভাগ রয়েছে, যা LLM প্রম্পটে বেশ সাধারণ। + +1. তথ্য। LLM-গুলির প্রশিক্ষণের অনেক তথ্য থাকে, কিন্তু সাধারণত তাদের কাছে সর্বশেষ তথ্য থাকে না। এই কারণে আমাদের এখানে সর্বশেষ কোটগুলি পুনরুদ্ধার করতে হবে। একটি প্রম্পটে তথ্য যোগ করাকে [রিট্রিভাল অগমেন্টেড জেনারেশন (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation) বলা হয়। + +2. আসল প্রশ্ন। এটাই আমরা জানতে চাই। + +3. আউটপুট ফরম্যাটিং নির্দেশাবলী। সাধারণত, একটি LLM আমাদের একটি অনুমান দেয় এবং এটি কীভাবে সেই অনুমানে পৌঁছেছে তার একটি ব্যাখ্যা দেয়। এটি মানুষের জন্য ভালো, কিন্তু একটি কম্পিউটার প্রোগ্রামের শুধু চূড়ান্ত ফলাফল প্রয়োজন। + +#### কোড ব্যাখ্যা {#prompt-code} + +এখানে নতুন কোডটি দেওয়া হলো। + +```python +from datetime import datetime, timezone, timedelta +``` + +আমাদের LLM-কে সেই সময়টি সরবরাহ করতে হবে যার জন্য আমরা একটি অনুমান চাই। ভবিষ্যতে "n মিনিট/ঘন্টা/দিন" সময় পেতে, আমরা [`timedelta` ক্লাস](https://docs.python.org/3/library/datetime.html#datetime.timedelta) ব্যবহার করি। + +```python +# আমরা যে পুলগুলো পড়ছি তার অ্যাড্রেস +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +আমাদের দুটি পুল পড়তে হবে। + +```python +@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 # (টোকেন0 প্রতি টোকেন1) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +WETH/USDC পুলে, আমরা জানতে চাই একটি `টোকেন1` (WETH) কিনতে কতগুলো `টোকেন0` (USDC) প্রয়োজন। WETH/WBTC পুলে, আমরা জানতে চাই একটি `টোকেন0` (WBTC, যা র‍্যাপড বিটকয়েন) কিনতে কতগুলো `টোকেন1` (WETH) প্রয়োজন। আমাদের পুলের অনুপাত বিপরীত করা প্রয়োজন কিনা তা ট্র্যাক করতে হবে। + +```python +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 + ) +``` + +একটি পুলকে বিপরীত করতে হবে কিনা তা জানতে, আমরা `read_pool`-এ ইনপুট হিসেবে তা পাই। এছাড়াও, অ্যাসেট প্রতীকটি সঠিকভাবে সেট করতে হবে। + +` if else ` সিনট্যাক্সটি পাইথনে [টারনারি কন্ডিশনাল অপারেটর](https://en.wikipedia.org/wiki/Ternary_conditional_operator) এর সমতুল্য, যা একটি C-ডিরাইভড ভাষায় ` ? : ` হবে। + +```python +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 +``` + +এই ফাংশনটি `Quote` অবজেক্টের একটি তালিকা ফরম্যাট করে একটি স্ট্রিং তৈরি করে, ধরে নেওয়া হয় যে সবগুলো একই অ্যাসেটের জন্য প্রযোজ্য। + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +Python-এ [মাল্টি-লাইন স্ট্রিং লিটারাল](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) লেখা হয় `"""` .... হিসেবে। `"""`। + +```python +এই কোটগুলো দেওয়া আছে: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +এখানে, আমরা প্রতিটি কোট তালিকার জন্য একটি স্ট্রিং তৈরি করতে [MapReduce](https://en.wikipedia.org/wiki/MapReduce) প্যাটার্ন ব্যবহার করি, `format_quotes` দিয়ে, তারপর প্রম্পটে ব্যবহারের জন্য সেগুলোকে একটি একক স্ট্রিং-এ পরিণত করি। + +```python +{expected_time} সময়ে {asset}-এর মান কী হবে বলে আপনি আশা করেন? + +আপনার উত্তরটি দুই দশমিক স্থান পর্যন্ত রাউন্ড করা একটি একক সংখ্যা হিসেবে দিন, +অন্য কোনো লেখা ছাড়া। + """ +``` + +প্রম্পটের বাকি অংশ প্রত্যাশিত। + +```python +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 +) +``` + +দুটি পুল পর্যালোচনা করুন এবং উভয় থেকে কোট সংগ্রহ করুন। + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +ভবিষ্যতের যে সময়বিন্দুর জন্য আমরা অনুমান চাই তা নির্ধারণ করুন এবং প্রম্পটটি তৈরি করুন। + +### একটি LLM-এর সাথে ইন্টারফেসিং {#interface-llm} + +এরপরে, আমরা একটি আসল LLM-কে প্রম্পট করি এবং একটি প্রত্যাশিত ভবিষ্যৎ মান পাই। আমি এই প্রোগ্রামটি OpenAI ব্যবহার করে লিখেছি, তাই আপনি যদি অন্য কোনো প্রদানকারী ব্যবহার করতে চান, তাহলে আপনাকে এটি অ্যাডজাস্ট করতে হবে। + +1. একটি [OpenAI অ্যাকাউন্ট](https://auth.openai.com/create-account) পান + +2. [অ্যাকাউন্টে ফান্ড যোগ করুন](https://platform.openai.com/settings/organization/billing/overview)—লেখার সময় ন্যূনতম পরিমাণ হল $5 + +3. [একটি API কী তৈরি করুন](https://platform.openai.com/settings/organization/api-keys) + +4. কমান্ড লাইনে, API কী এক্সপোর্ট করুন যাতে আপনার প্রোগ্রাম এটি ব্যবহার করতে পারে + + ```sh + export OPENAI_API_KEY=sk-<কী-এর বাকি অংশ এখানে দিন> + ``` + +5. এজেন্টটি চেকআউট করুন এবং চালান + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +এখানে নতুন কোডটি দেওয়া হলো। + +```python +from openai import OpenAI + +open_ai = OpenAI() # ক্লায়েন্ট OPENAI_API_KEY এনভায়রনমেন্ট ভেরিয়েবল পড়ে +``` + +OpenAI API ইম্পোর্ট এবং ইনস্ট্যানশিয়েট করুন। + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +প্রতিক্রিয়া তৈরি করতে OpenAI API (`open_ai.chat.completions.create`) কল করুন। + +```python +expected_price = Decimal(response.choices[0].message.content.strip()) +current_price = wethusdc_quotes[-1].price + +print ("বর্তমান মূল্য:", wethusdc_quotes[-1].price) +print(f"{future_time}-এ, প্রত্যাশিত মূল্য: {expected_price} USD") + +if (expected_price > current_price): + print(f"কিনুন, আমি আশা করি মূল্য {expected_price - current_price} USD বাড়বে") +else: + print(f"বিক্রি করুন, আমি আশা করি মূল্য {current_price - expected_price} USD কমবে") +``` + +মূল্যটি আউটপুট করুন এবং একটি কেনা বা বেচার সুপারিশ প্রদান করুন। + +#### ভবিষ্যদ্বাণী পরীক্ষা করা {#testing-the-predictions} + +এখন যেহেতু আমরা ভবিষ্যদ্বাণী তৈরি করতে পারি, আমরা ঐতিহাসিক ডেটা ব্যবহার করে মূল্যায়ন করতে পারি যে আমরা দরকারী ভবিষ্যদ্বাণী তৈরি করছি কিনা। + +```sh +uv run test-predictor.py +``` + +প্রত্যাশিত ফলাফলটি অনেকটা এইরকম: + +``` +2026-01-05T19:50-এর জন্য ভবিষ্যদ্বাণী: ভবিষ্যদ্বাণী 3138.93 USD, আসল 3218.92 USD, ত্রুটি 79.99 USD +2026-01-06T19:56-এর জন্য ভবিষ্যদ্বাণী: ভবিষ্যদ্বাণী 3243.39 USD, আসল 3221.08 USD, ত্রুটি 22.31 USD +2026-01-07T20:02-এর জন্য ভবিষ্যদ্বাণী: ভবিষ্যদ্বাণী 3223.24 USD, আসল 3146.89 USD, ত্রুটি 76.35 USD +2026-01-08T20:11-এর জন্য ভবিষ্যদ্বাণী: ভবিষ্যদ্বাণী 3150.47 USD, আসল 3092.04 USD, ত্রুটি 58.43 USD +. +. +. +2026-01-31T22:33-এর জন্য ভবিষ্যদ্বাণী: ভবিষ্যদ্বাণী 2637.73 USD, আসল 2417.77 USD, ত্রুটি 219.96 USD +2026-02-01T22:41-এর জন্য ভবিষ্যদ্বাণী: ভবিষ্যদ্বাণী 2381.70 USD, আসল 2318.84 USD, ত্রুটি 62.86 USD +2026-02-02T22:49-এর জন্য ভবিষ্যদ্বাণী: ভবিষ্যদ্বাণী 2234.91 USD, আসল 2349.28 USD, ত্রুটি 114.37 USD +29টি ভবিষ্যদ্বাণীর উপর গড় ভবিষ্যদ্বাণীর ত্রুটি: 83.87103448275862068965517241 USD +প্রতি সুপারিশে গড় পরিবর্তন: 4.787931034482758620689655172 USD +পরিবর্তনের স্ট্যান্ডার্ড ভ্যারিয়েন্স: 104.42 USD +লাভজনক দিন: 51.72% +লোকসানের দিন: 48.28% +``` + +পরীক্ষকের বেশিরভাগ অংশ এজেন্টের সাথে অভিন্ন, কিন্তু এখানে নতুন বা পরিবর্তিত অংশগুলি রয়েছে। + +```python +CYCLES_FOR_TEST = 40 # ব্যাকটেস্টের জন্য, আমরা কতগুলি চক্র পরীক্ষা করি + +# অনেক কোট পান +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 +) +``` + +আমরা `CYCLES_FOR_TEST` (এখানে 40 হিসাবে নির্দিষ্ট) দিন পিছনে দেখি। + +```python +# ভবিষ্যদ্বাণী তৈরি করুন এবং বাস্তব ইতিহাসের সাথে তাদের পরীক্ষা করুন + +total_error = Decimal(0) +changes = [] +``` + +দুই ধরনের ত্রুটি আছে যা নিয়ে আমরা আগ্রহী। প্রথমটি, `total_error`, হল ভবিষ্যদ্বাণীকারীর করা ত্রুটিগুলির সমষ্টি। + +দ্বিতীয়টি, `changes`, বোঝার জন্য, আমাদের এজেন্টের উদ্দেশ্য মনে রাখতে হবে। এটি WETH/USDC অনুপাত (ETH মূল্য) ভবিষ্যদ্বাণী করার জন্য নয়। এটি বিক্রয় এবং কেনার সুপারিশ জারি করার জন্য। যদি বর্তমানে মূল্য $2000 হয় এবং এটি আগামীকাল $2010 ভবিষ্যদ্বাণী করে, তাহলে আসল ফলাফল যদি $2020 হয় এবং আমরা অতিরিক্ত অর্থ উপার্জন করি তবে আমরা কিছু মনে করি না। কিন্তু আমরা _অবশ্যই_ কিছু মনে করি যদি এটি $2010 ভবিষ্যদ্বাণী করে, এবং সেই সুপারিশের ভিত্তিতে ETH কিনে, এবং দাম $1990-এ নেমে যায়। + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +আমরা কেবল সেইসব ক্ষেত্রে দেখতে পারি যেখানে সম্পূর্ণ ইতিহাস (ভবিষ্যদ্বাণীর জন্য ব্যবহৃত মান এবং এটির সাথে তুলনা করার জন্য বাস্তব-বিশ্বের মান) উপলব্ধ রয়েছে। এর মানে হল যে নতুনতম কেসটি অবশ্যই `CYCLES_BACK` আগে শুরু হয়েছে। + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +এজেন্ট যে সংখ্যক নমুনা ব্যবহার করে সেই একই সংখ্যক নমুনা পেতে [স্লাইস](https://www.w3schools.com/python/ref_func_slice.asp) ব্যবহার করুন। এখানে এবং পরবর্তী সেগমেন্টের মধ্যে কোডটি এজেন্টে আমাদের থাকা একই get-a-prediction কোড। + +```python + 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 +``` + +ভবিষ্যদ্বাণী করা মূল্য, আসল মূল্য এবং ভবিষ্যদ্বাণীর সময়ের মূল্য পান। সুপারিশটি কেনার নাকি বেচার ছিল তা নির্ধারণ করার জন্য আমাদের ভবিষ্যদ্বাণীর সময়ের মূল্য প্রয়োজন। + +```python + error = abs(predicted_price - real_price) + total_error += error + print (f"{prediction_time}-এর জন্য ভবিষ্যদ্বাণী: ভবিষ্যদ্বাণী {predicted_price} USD, আসল {real_price} USD, ত্রুটি {error} USD") +``` + +ত্রুটি নির্ণয় করুন, এবং এটিকে মোটের সাথে যোগ করুন। + +```python + recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell' + price_increase = real_price - prediction_time_price + changes.append(price_increase if recomended_action == 'buy' else -price_increase) +``` + +`changes`-এর জন্য, আমরা এক ETH কেনা বা বেচার আর্থিক প্রভাব চাই। তাই প্রথমে, আমাদের সুপারিশটি নির্ধারণ করতে হবে, তারপর আসল মূল্য কীভাবে পরিবর্তিত হয়েছে তা মূল্যায়ন করতে হবে এবং সুপারিশটি লাভজনক ছিল (ধনাত্মক পরিবর্তন) নাকি লোকসানের কারণ হয়েছিল (ঋণাত্মক পরিবর্তন)। + +```python +print (f"{len(wethusdc_quotes)-CYCLES_BACK}টি ভবিষ্যদ্বাণীর উপর গড় ভবিষ্যদ্বাণীর ত্রুটি: {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} USD") +var = sum((x - mean_change) ** 2 for x in changes) / length_changes +print (f"পরিবর্তনের স্ট্যান্ডার্ড ভ্যারিয়েন্স: {var.sqrt().quantize(Decimal("0.01"))} USD") +``` + +ফলাফলগুলি রিপোর্ট করুন। + +```python +print (f"লাভজনক দিন: {len(list(filter(lambda x: x > 0, changes)))/length_changes:.2%}") +print (f"লোকসানের দিন: {len(list(filter(lambda x: x < 0, changes)))/length_changes:.2%}") +``` + +লাভজনক দিন এবং লোকসানের দিনের সংখ্যা গণনা করতে [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) ব্যবহার করুন। ফলাফল একটি ফিল্টার অবজেক্ট, যা আমাদের দৈর্ঘ্য পেতে একটি তালিকায় রূপান্তর করতে হবে। + +### লেনদেন জমা দেওয়া {#submit-txn} + +এখন আমাদের আসলে লেনদেন জমা দিতে হবে। তবে, সিস্টেমটি প্রমাণিত হওয়ার আগে আমি এই মুহূর্তে আসল টাকা খরচ করতে চাই না। পরিবর্তে, আমরা মেইননেটের একটি স্থানীয় ফর্ক তৈরি করব এবং সেই নেটওয়ার্কে "ট্রেড" করব। + +এখানে একটি স্থানীয় ফর্ক তৈরি এবং ট্রেডিং সক্ষম করার ধাপগুলি দেওয়া হল। + +1. [Foundry](https://getfoundry.sh/introduction/installation) ইনস্টল করুন + +2. [`anvil`](https://getfoundry.sh/anvil/overview) শুরু করুন + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` Foundry-এর ডিফল্ট URL, http://localhost:8545-এ লিসেন করছে, তাই ব্লকচেইন ম্যানিপুলেট করার জন্য আমরা যে [`cast` কমান্ড](https://getfoundry.sh/cast/overview) ব্যবহার করি তার জন্য URL নির্দিষ্ট করার প্রয়োজন নেই। + +3. `anvil`-এ চালানোর সময়, দশটি টেস্ট অ্যাকাউন্ট থাকে যেগুলোতে ETH আছে—প্রথমটির জন্য এনভায়রনমেন্ট ভেরিয়েবল সেট করুন + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. এগুলো হল কন্ট্র্যাক্ট যা আমাদের ব্যবহার করতে হবে। [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) হল Uniswap v3 কন্ট্র্যাক্ট যা আমরা আসলে ট্রেড করতে ব্যবহার করি। আমরা সরাসরি পুলের মাধ্যমে ট্রেড করতে পারতাম, কিন্তু এটি অনেক সহজ। + + নীচের দুটি ভেরিয়েবল হল WETH এবং USDC-এর মধ্যে সোয়াপ করার জন্য প্রয়োজনীয় Uniswap v3 পাথ। + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. প্রতিটি টেস্ট অ্যাকাউন্টে 10,000 ETH আছে। ট্রেডিংয়ের জন্য 1000 WETH পেতে 1000 ETH র‍্যাপ করতে WETH কন্ট্র্যাক্ট ব্যবহার করুন। + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. `SwapRouter` ব্যবহার করে 500 WETH ট্রেড করে USDC নিন। + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + `approve` কল একটি অ্যালাওয়েন্স তৈরি করে যা `SwapRouter`-কে আমাদের কিছু টোকেন খরচ করার অনুমতি দেয়। কন্ট্র্যাক্টগুলি ইভেন্টগুলি নিরীক্ষণ করতে পারে না, তাই যদি আমরা সরাসরি `SwapRouter` কন্ট্র্যাক্টে টোকেন স্থানান্তর করি, তবে এটি জানতে পারবে না যে এটি পরিশোধ করা হয়েছে। পরিবর্তে, আমরা `SwapRouter` কন্ট্র্যাক্টকে একটি নির্দিষ্ট পরিমাণ খরচ করার অনুমতি দিই, এবং তারপর `SwapRouter` এটি করে। এটি `SwapRouter` দ্বারা কল করা একটি ফাংশনের মাধ্যমে করা হয়, তাই এটি জানে যে এটি সফল হয়েছে কিনা। + +7. আপনার কাছে উভয় টোকেন পর্যাপ্ত পরিমাণে আছে কিনা তা যাচাই করুন। + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +এখন যেহেতু আমাদের কাছে WETH এবং USDC আছে, আমরা আসলে এজেন্টটি চালাতে পারি। + +```sh +git checkout 05-trade +uv run agent.py +``` + +আউটপুটটি দেখতে অনেকটা এইরকম হবে: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +বর্তমান মূল্য: 1843.16 +2026-02-06T23:07-এ, প্রত্যাশিত মূল্য: 1724.41 USD +ট্রেডের আগে অ্যাকাউন্টের ব্যালেন্স: +USDC ব্যালেন্স: 927301.578272 +WETH ব্যালেন্স: 500 +বিক্রি করুন, আমি আশা করি মূল্য 118.75 USD কমবে +অনুমোদন লেনদেন পাঠানো হয়েছে: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +অনুমোদন লেনদেন মাইনিং করা হয়েছে। +বিক্রয় লেনদেন পাঠানো হয়েছে: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +বিক্রয় লেনদেন মাইনিং করা হয়েছে। +ট্রেডের পরে অ্যাকাউন্টের ব্যালেন্স: +USDC ব্যালেন্স: 929143.797116 +WETH ব্যালেন্স: 499 +``` + +এটি আসলে ব্যবহার করার জন্য, আপনাকে কয়েকটি ছোট পরিবর্তন করতে হবে। + +- লাইন 14-এ, `MAINNET_URL` পরিবর্তন করে একটি বাস্তব অ্যাক্সেস পয়েন্ট, যেমন `https://eth.drpc.org`-এ দিন +- লাইন 28-এ, `PRIVATE_KEY` পরিবর্তন করে আপনার নিজের প্রাইভেট কী দিন +- যদি না আপনি খুব ধনী হন এবং একটি অপ্রমাণিত এজেন্টের জন্য প্রতিদিন 1 ETH কিনতে বা বিক্রি করতে পারেন, তাহলে আপনি `WETH_TRADE_AMOUNT` কমাতে 29 পরিবর্তন করতে চাইতে পারেন + +#### কোড ব্যাখ্যা {#trading-code} + +এখানে নতুন কোডটি দেওয়া হলো। + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +ধাপ 4-এ আমরা যে একই ভেরিয়েবল ব্যবহার করেছি। + +```python +WETH_TRADE_AMOUNT=1 +``` + +ট্রেড করার পরিমাণ। + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +আসলে ট্রেড করার জন্য, আমাদের `approve` ফাংশন প্রয়োজন। আমরা আগে এবং পরে ব্যালেন্সও দেখাতে চাই, তাই আমাদের `balanceOf` ও প্রয়োজন। + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +`SwapRouter` ABI-তে আমাদের শুধু `exactInput` প্রয়োজন। একটি সম্পর্কিত ফাংশন আছে, `exactOutput`, যা আমরা ঠিক একটি WETH কেনার জন্য ব্যবহার করতে পারতাম, কিন্তু সরলতার জন্য আমরা উভয় ক্ষেত্রে `exactInput` ব্যবহার করি। + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +[`account`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) এবং `SwapRouter` কন্ট্র্যাক্টের জন্য Web3 সংজ্ঞা। + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +লেনদেনের প্যারামিটার। এখানে আমাদের একটি ফাংশন প্রয়োজন কারণ [নন্স](https://en.wikipedia.org/wiki/Cryptographic_nonce) প্রতিবার পরিবর্তন হতে হবে। + +```python +def approve_token(contract: Contract, amount: int): +``` + +`SwapRouter`-এর জন্য একটি টোকেন অ্যালাওয়েন্স অনুমোদন করুন। + +```python + txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params()) + signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY) + tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction) +``` + +এভাবে আমরা Web3-এ একটি লেনদেন পাঠাই। প্রথমে আমরা লেনদেন তৈরি করতে [`Contract` অবজেক্ট](https://web3py.readthedocs.io/en/stable/web3.contract.html) ব্যবহার করি। তারপর আমরা `PRIVATE_KEY` ব্যবহার করে লেনদেন সাইন করতে [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) ব্যবহার করি। অবশেষে, আমরা লেনদেন পাঠাতে [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) ব্যবহার করি। + +```python + print(f"অনুমোদন লেনদেন পাঠানো হয়েছে: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("অনুমোদন লেনদেন মাইনিং করা হয়েছে।") +``` + +[`w3.eth.wait_for_transaction_receipt`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) লেনদেন মাইনিং না হওয়া পর্যন্ত অপেক্ষা করে। প্রয়োজন হলে এটি রসিদ প্রদান করে। + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +WETH বিক্রি করার সময় এইগুলি হল প্যারামিটার। + +```python +def make_buy_params(quote: Quote) -> dict: + return { + "path": USDC_TO_WETH, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals, + "amountOutMinimum": 0, + } +``` + +`SELL_PARAMS`-এর বিপরীতে, কেনার প্যারামিটারগুলি পরিবর্তন হতে পারে। ইনপুট পরিমাণ হল 1 WETH-এর খরচ, যা `quote`-এ উপলব্ধ। + +```python +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"কেনার লেনদেন পাঠানো হয়েছে: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("কেনার লেনদেন মাইনিং করা হয়েছে।") + + +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"বিক্রয় লেনদেন পাঠানো হয়েছে: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("বিক্রয় লেনদেন মাইনিং করা হয়েছে।") +``` + +`buy()` এবং `sell()` ফাংশনগুলি প্রায় অভিন্ন। প্রথমে আমরা `SwapRouter`-এর জন্য একটি পর্যাপ্ত অ্যালাওয়েন্স অনুমোদন করি এবং তারপর আমরা সঠিক পাথ এবং পরিমাণ দিয়ে এটি কল করি। + +```python +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} ব্যালেন্স: {Decimal(token0_balance) / Decimal(10 ** wethusdc_pool.token0.decimals)}") + print(f"{wethusdc_pool.token1.symbol} ব্যালেন্স: {Decimal(token1_balance) / Decimal(10 ** wethusdc_pool.token1.decimals)}") +``` + +উভয় মুদ্রায় ব্যবহারকারীর ব্যালেন্স রিপোর্ট করুন। + +```python +print("ট্রেডের আগে অ্যাকাউন্টের ব্যালেন্স:") +balances() + +if (expected_price > current_price): + print(f"কিনুন, আমি আশা করি মূল্য {expected_price - current_price} USD বাড়বে") + buy(wethusdc_quotes[-1]) +else: + print(f"বিক্রি করুন, আমি আশা করি মূল্য {current_price - expected_price} USD কমবে") + sell() + +print("ট্রেডের পরে অ্যাকাউন্টের ব্যালেন্স:") +balances() +``` + +এই এজেন্ট বর্তমানে শুধুমাত্র একবার কাজ করে। তবে, আপনি এটিকে [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) থেকে চালিয়ে বা একটি লুপে 368-400 লাইনগুলি র‍্যাপ করে এবং পরবর্তী চক্রের জন্য অপেক্ষা করার জন্য [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) ব্যবহার করে ক্রমাগত কাজ করার জন্য পরিবর্তন করতে পারেন। + +## সম্ভাব্য উন্নতি {#improvements} + +এটি একটি সম্পূর্ণ প্রোডাকশন সংস্করণ নয়; এটি শুধুমাত্র মূল বিষয়গুলি শেখানোর জন্য একটি উদাহরণ। এখানে উন্নতির জন্য কিছু ধারণা দেওয়া হল। + +### স্মার্ট ট্রেডিং {#smart-trading} + +দুটি গুরুত্বপূর্ণ তথ্য রয়েছে যা এজেন্ট কী করতে হবে তা সিদ্ধান্ত নেওয়ার সময় উপেক্ষা করে। + +- _প্রত্যাশিত পরিবর্তনের মাত্রা_। মূল্য হ্রাসের মাত্রা নির্বিশেষে, যদি মূল্য হ্রাসের প্রত্যাশা করা হয় তবে এজেন্ট একটি নির্দিষ্ট পরিমাণ `WETH` বিক্রি করে। + যুক্তিযুক্তভাবে, ছোটখাটো পরিবর্তন উপেক্ষা করা এবং আমরা কতটা মূল্য হ্রাসের প্রত্যাশা করি তার উপর ভিত্তি করে বিক্রি করা ভাল হবে। +- _বর্তমান পোর্টফোলিও_। যদি আপনার পোর্টফোলিওর 10% WETH-এ থাকে এবং আপনি মনে করেন দাম বাড়বে, তাহলে সম্ভবত আরও কেনা যুক্তিযুক্ত। কিন্তু যদি আপনার পোর্টফোলিওর 90% WETH-এ থাকে, তাহলে আপনি যথেষ্ট এক্সপোজড হতে পারেন, এবং আরও কেনার প্রয়োজন নেই। আপনি যদি দাম কমার আশা করেন তবে এর বিপরীতটি সত্য। + +### আপনি যদি আপনার ট্রেডিং কৌশল গোপন রাখতে চান তাহলে কী হবে? {#secret} + +AI বিক্রেতারা তাদের LLM-গুলিতে আপনার পাঠানো কোয়েরিগুলি দেখতে পারে, যা আপনার এজেন্টের সাথে আপনি যে জিনিয়াস ট্রেডিং সিস্টেম তৈরি করেছেন তা প্রকাশ করতে পারে। একটি ট্রেডিং সিস্টেম যা অনেক লোক ব্যবহার করে তা মূল্যহীন কারণ যখন আপনি কিনতে চান তখন অনেক লোক কেনার চেষ্টা করে (এবং দাম বেড়ে যায়) এবং যখন আপনি বিক্রি করতে চান তখন বিক্রি করার চেষ্টা করে (এবং দাম কমে যায়)। + +এই সমস্যা এড়াতে আপনি স্থানীয়ভাবে একটি LLM চালাতে পারেন, উদাহরণস্বরূপ, [LM-Studio](https://lmstudio.ai/) ব্যবহার করে। + +### AI বট থেকে AI এজেন্ট {#bot-to-agent} + +আপনি একটি ভাল যুক্তি দিতে পারেন যে এটি [একটি AI বট, AI এজেন্ট নয়](/ai-agents/#ai-agents-vs-ai-bots)। এটি একটি তুলনামূলকভাবে সহজ কৌশল বাস্তবায়ন করে যা পূর্বনির্ধারিত তথ্যের উপর নির্ভর করে। আমরা স্ব-উন্নতি সক্ষম করতে পারি, উদাহরণস্বরূপ, Uniswap v3 পুল এবং তাদের সর্বশেষ মানগুলির একটি তালিকা প্রদান করে এবং জিজ্ঞাসা করে কোন সংমিশ্রণের সেরা ভবিষ্যদ্বাণীমূলক মান রয়েছে। + +### স্লিপেজ সুরক্ষা {#slippage-protection} + +বর্তমানে কোনো [স্লিপেজ সুরক্ষা](https://uniswapv3book.com/milestone_3/slippage-protection.html) নেই। যদি বর্তমান কোট $2000 হয় এবং প্রত্যাশিত মূল্য $2100 হয়, এজেন্ট কিনবে। তবে, এজেন্ট কেনার আগে যদি খরচ $2200-এ বেড়ে যায়, তবে আর কেনার কোনো মানে হয় না। + +স্লিপেজ সুরক্ষা বাস্তবায়ন করতে, [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325)-এর 325 এবং 334 লাইনে একটি `amountOutMinimum` মান নির্দিষ্ট করুন। + +## উপসংহার {#conclusion} + +আশা করি, এখন আপনি AI এজেন্টদের সাথে শুরু করার জন্য যথেষ্ট জানেন। এটি বিষয়টির একটি ব্যাপক ওভারভিউ নয়; এর জন্য পুরো বই উৎসর্গ করা হয়েছে, তবে এটি আপনাকে শুরু করার জন্য যথেষ্ট। শুভকামনা! + +[আমার আরও কাজের জন্য এখানে দেখুন](https://cryptodocguy.pro/)। diff --git a/public/content/translations/cs/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/cs/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..93cab455af5 --- /dev/null +++ b/public/content/translations/cs/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "Vytvořte si vlastního AI obchodního agenta na Ethereu" +description: "V tomto tutoriálu se naučíte, jak vytvořit jednoduchého AI obchodního agenta. Tento agent čte informace z blockchainu, požádá LLM o doporučení na základě těchto informací, provádí obchod, který LLM doporučí, a pak čeká a opakuje." +author: Ori Pomerantz +tags: [ "AI", "obchodování", "agent", "python" ] +skill: intermediate +published: 2026-02-13 +lang: cs +sidebarDepth: 3 +--- + +V tomto tutoriálu se naučíte, jak sestavit jednoduchého AI obchodního agenta. Tento agent funguje pomocí těchto kroků: + +1. Přečtěte si aktuální a minulé ceny tokenu, stejně jako další potenciálně relevantní informace +2. Sestavte dotaz s těmito informacemi spolu s doplňujícími informacemi k vysvětlení, jak by to mohlo být relevantní +3. Odešlete dotaz a obdržíte zpět předpokládanou cenu +4. Obchodujte na základě doporučení +5. Počkejte a opakujte + +Tento agent demonstruje, jak číst informace, přeložit je do dotazu, který poskytne použitelnou odpověď, a použít tuto odpověď. Všechny tyto kroky jsou nutné pro agenta AI. Tento agent je implementován v Pythonu, protože je to nejběžnější jazyk používaný v AI. + +## Proč to dělat? {#why-do-this} + +Automatizovaní obchodní agenti umožňují vývojářům vybrat a provést obchodní strategii. [Agenti AI](/ai-agents) umožňují složitější a dynamičtější obchodní strategie, potenciálně s využitím informací a algoritmů, o jejichž použití vývojář ani neuvažoval. + +## Nástroje {#tools} + +Tento tutoriál používá [Python](https://www.python.org/), knihovnu [Web3](https://web3py.readthedocs.io/en/stable/) a [Uniswap v3](https://github.com/Uniswap/v3-periphery) pro nabídky a obchodování. + +### Proč Python? {#python} + +Nejrozšířenějším jazykem pro AI je [Python](https://www.python.org/), takže ho použijeme i tady. Nebojte se, pokud neznáte Python. Tento jazyk je velmi srozumitelný a já vám přesně vysvětlím, co dělá. + +[Knihovna Web3](https://web3py.readthedocs.io/en/stable/) je nejběžnější Python API pro Ethereum. Její použití je celkem snadné. + +### Obchodování na blockchainu {#trading-on-blockchain} + +Existuje [mnoho decentralizovaných burz (DEX)](/apps/categories/defi/), které vám umožní obchodovat s tokeny na Ethereu. Nicméně mívají podobné směnné kurzy kvůli [arbitráži](/developers/docs/smart-contracts/composability/#better-user-experience). + +[Uniswap](https://app.uniswap.org/) je široce používaná DEX, kterou můžeme použít jak pro nabídky (pro zobrazení relativních hodnot tokenů), tak pro obchody. + +### OpenAI {#openai} + +Pro velký jazykový model jsem se rozhodl začít s [OpenAI](https://openai.com/). Abyste mohli spustit aplikaci v tomto tutoriálu, budete muset zaplatit za přístup k API. Minimální platba 5 $ je více než dostačující. + +## Vývoj, krok za krokem {#step-by-step} + +Pro zjednodušení vývoje postupujeme po etapách. Každý krok je větev v GitHubu. + +### Začínáme {#getting-started} + +Zde jsou kroky, jak začít v systémech UNIX nebo Linux (včetně [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) + +1. Pokud ho ještě nemáte, stáhněte a nainstalujte [Python](https://www.python.org/downloads/). + +2. Naklonujte repozitář na GitHubu. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. Nainstalujte si [`uv`](https://docs.astral.sh/uv/getting-started/installation/). Příkaz na vašem systému se může lišit. + + ```sh + pipx install uv + ``` + +4. Stáhněte si knihovny. + + ```sh + uv sync + ``` + +5. Aktivujte virtuální prostředí. + + ```sh + source .venv/bin/activate + ``` + +6. Chcete-li ověřit, že Python a Web3 fungují správně, spusťte `python3` a zadejte do něj tento program. Můžete jej zadat na příkazový řádek `>>>`, není třeba vytvářet soubor. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### Čtení z blockchainu {#read-blockchain} + +Dalším krokem je čtení z blockchainu. K tomu se musíte přepnout na větev `02-read-quote` a poté pomocí `uv` spustit program. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +Měli byste obdržet seznam objektů `Quote`, každý s časovým razítkem, cenou a aktivem (v současnosti vždy `WETH/USDC`). + +Zde je vysvětlení řádek po řádku. + +```python +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 +``` + +Importujte knihovny, které potřebujeme. Jsou vysvětleny níže, když jsou použity. + +```python +print = functools.partial(print, flush=True) +``` + +Nahrazuje pythonovský `print` verzí, která vždy okamžitě vyprázdní výstup. To je užitečné v dlouho běžícím skriptu, protože nechceme čekat na aktualizace stavu nebo na výstup pro ladění. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +URL pro přístup na hlavní síť. Můžete si ji pořídit z [uzlu jako služby](/developers/docs/nodes-and-clients/nodes-as-a-service/) nebo použít jednu z těch, které jsou inzerovány na [Chainlistu](https://chainlist.org/chain/1). + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +Blok na hlavní síti Etherea se obvykle objeví každých dvanáct sekund, takže toto jsou počty bloků, které bychom očekávali, že se objeví v daném časovém období. Upozorňujeme, že se nejedná o přesný údaj. Když je [navrhovatel bloku](/developers/docs/consensus-mechanisms/pos/block-proposal/) mimo provoz, tento blok je přeskočen a čas do dalšího bloku je 24 sekund. Kdybychom chtěli získat přesný blok pro časové razítko, použili bychom [binární vyhledávání](https://en.wikipedia.org/wiki/Binary_search). Pro naše účely je to však dostatečně blízko. Předpovídání budoucnosti není exaktní věda. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +Velikost cyklu. Jednou za cyklus přezkoumáme nabídky a pokusíme se odhadnout hodnotu na konci dalšího cyklu. + +```python +# Adresa fondu, který čteme +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +Hodnoty nabídky jsou převzaty z fondu Uniswap 3 USDC/WETH na adrese [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract). Tato adresa je již ve formě kontrolního součtu, ale je lepší použít [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address), aby byl kód znovu použitelný. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +Toto jsou [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html) pro dvě smlouvy, které potřebujeme kontaktovat. Aby byl kód stručný, zahrnuli jsme pouze funkce, které potřebujeme volat. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +Inicializujte knihovnu [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) a připojte se k uzlu Ethereum. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +Toto je jeden ze způsobů, jak v Pythonu vytvořit datovou třídu. Datový typ [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) se používá pro připojení ke smlouvě. Všimněte si `(frozen=True)`. V Pythonu jsou [booleany](https://en.wikipedia.org/wiki/Boolean_data_type) definovány jako `True` nebo `False` s velkým písmenem. Tato datová třída je `frozen` (zmrazená), což znamená, že pole nelze upravovat. + +Všimněte si odsazení. Na rozdíl od [jazyků odvozených od C](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages), Python používá k označení bloků odsazení. Interpret Pythonu ví, že následující definice není součástí této datové třídy, protože nezačíná na stejném odsazení jako pole datové třídy. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +Typ [`Decimal`](https://docs.python.org/3/library/decimal.html) se používá pro přesnou práci s desetinnými zlomky. + +```python + def get_price(self, block: int) -> Decimal: +``` + +Takto se definuje funkce v Pythonu. Definice je odsazena, aby bylo vidět, že je stále součástí `PoolInfo`. + +Ve funkci, která je součástí datové třídy, je prvním parametrem vždy `self`, instance datové třídy, která zde volala. Zde je další parametr, číslo bloku. + +```python + assert block <= w3.eth.block_number, "Blok je v budoucnosti" +``` + +Kdybychom uměli číst budoucnost, nepotřebovali bychom AI pro obchodování. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +Syntaxe pro volání funkce na EVM z Web3 je tato: `.functions.().call()`. Parametry mohou být parametry funkce EVM (pokud nějaké jsou; zde nejsou) nebo [pojmenované parametry](https://en.wikipedia.org/wiki/Named_parameter) pro úpravu chování blockchainu. Zde používáme jeden, `block_identifier`, pro určení [čísla bloku](/developers/docs/apis/json-rpc/#default-block), ve kterém chceme pracovat. + +Výsledkem je [tato struktura ve formě pole](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). První hodnota je funkcí směnného kurzu mezi dvěma tokeny. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +Pro snížení výpočtů na blockchainu Uniswap v3 neukládá skutečný směnný kurz, ale spíše jeho druhou odmocninu. Protože EVM nepodporuje matematiku s plovoucí desetinnou čárkou ani zlomky, místo skutečné hodnoty je odpověď price296 + +```python + # (token1 za token0) + return 1/(raw_price * self.decimal_factor) +``` + +Hrubá cena, kterou dostaneme, je počet `token0`, které získáme za každý `token1`. V našem fondu je `token0` USDC (stabilní kryptoměna se stejnou hodnotou jako americký dolar) a `token1` je [WETH](https://opensea.io/learn/blockchain/what-is-weth). Hodnota, kterou skutečně chceme, je počet dolarů za WETH, ne inverzní. + +Desetinný faktor je poměr mezi [desetinnými faktory](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) pro oba tokeny. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +Tato datová třída představuje nabídku: cenu konkrétního aktiva v daném časovém okamžiku. V tomto okamžiku je pole `asset` irelevantní, protože používáme jeden fond, a proto máme jedno aktivum. Později však přidáme další aktiva. + +```python +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 + ) +``` + +Tato funkce přebírá adresu a vrací informace o tokenové smlouvě na této adrese. Pro vytvoření nové smlouvy [Web3 `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) poskytneme adresu a ABI do `w3.eth.contract`. + +```python +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) + ) +``` + +Tato funkce vrací vše, co potřebujeme o [konkrétním fondu](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol). Syntaxe `f""` je [formátovaný řetězec](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +Získání objektu `Quote`. Výchozí hodnota pro `block_number` je `None` (žádná hodnota). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +Pokud nebylo zadáno číslo bloku, použije se `w3.eth.block_number`, což je poslední číslo bloku. Toto je syntaxe pro [příkaz `if`](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement). + +Mohlo by se zdát, že by bylo lepší nastavit výchozí hodnotu na `w3.eth.block_number`, ale to nefunguje dobře, protože by to bylo číslo bloku v době definování funkce. V dlouhodobě běžícím agentovi by to byl problém. + +```python + 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 + ) +``` + +Použijte [knihovnu `datetime`](https://docs.python.org/3/library/datetime.html) k formátování do formátu čitelného pro lidi a velké jazykové modely (LLM). Použijte [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) k zaokrouhlení hodnoty na dvě desetinná místa. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +V Pythonu se definuje [seznam](https://docs.python.org/3/library/stdtypes.html#typesseq-list), který může obsahovat pouze určitý typ, pomocí `list[]`. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +V Pythonu [`for` cyklus](https://docs.python.org/3/tutorial/controlflow.html#for-statements) obvykle prochází seznam. Seznam čísel bloků, ve kterých se mají hledat nabídky, pochází z [`range`](https://docs.python.org/3/library/stdtypes.html#range). + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +Pro každé číslo bloku získá objekt `Quote` a přidá jej do seznamu `quotes`. Poté tento seznam vrátí. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +Toto je hlavní kód skriptu. Přečte informace o fondu, získá dvanáct nabídek a [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) je vytiskne. + +### Vytvoření výzvy {#prompt} + +Dále musíme tento seznam nabídek převést na výzvu pro LLM a získat očekávanou budoucí hodnotu. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +Výstupem nyní bude výzva pro LLM, podobná této: + +``` +Vzhledem k těmto nabídkám: +Aktivum: WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +Aktivum: WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +Jakou hodnotu byste očekávali pro WETH/USDC v čase 2026-02-02T17:56? + +Poskytněte odpověď jako jediné číslo zaokrouhlené na dvě desetinná místa, +bez jakéhokoli dalšího textu. +``` + +Všimněte si, že zde jsou nabídky pro dvě aktiva, `WETH/USDC` a `WBTC/WETH`. Přidání nabídek z jiného aktiva může zlepšit přesnost předpovědi. + +#### Jak vypadá výzva {#prompt-explanation} + +Tato výzva obsahuje tři sekce, které jsou v LLM výzvách poměrně běžné. + +1. Informace. LLM mají spoustu informací ze svého trénování, ale obvykle nemají nejnovější. To je důvod, proč zde musíme získat nejnovější nabídky. Přidávání informací do výzvy se nazývá [retrieval augmented generation (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +2. Skutečná otázka. To je to, co chceme vědět. + +3. Pokyny pro formátování výstupu. Normálně nám LLM dá odhad s vysvětlením, jak k němu dospěl. To je lepší pro lidi, ale počítačový program potřebuje pouze výsledek. + +#### Vysvětlení kódu {#prompt-code} + +Zde je nový kód. + +```python +from datetime import datetime, timezone, timedelta +``` + +Musíme poskytnout LLM čas, pro který chceme odhad. Pro získání času „n minut/hodin/dní“ v budoucnu používáme [třídu `timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta). + +```python +# Adresy fondů, které čteme +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +Máme dva fondy, které musíme přečíst. + +```python +@dataclass(frozen=True) +class PoolInfo: + . + . + . + reverse: bool = False + + def get_price(self, block: int) -> Decimal: + assert block <= w3.eth.block_number, "Blok je v budoucnosti" + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (token1 za token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +Ve fondu WETH/USDC chceme vědět, kolik `token0` (USDC) potřebujeme k nákupu jednoho `token1` (WETH). Ve fondu WETH/WBTC chceme vědět, kolik `token1` (WETH) potřebujeme k nákupu jednoho `token0` (WBTC, což je wrapped Bitcoin). Musíme sledovat, zda je třeba poměr fondu obrátit. + +```python +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 + ) +``` + +Abychom věděli, zda je třeba fond obrátit, musíme to získat jako vstup do `read_pool`. Také je třeba správně nastavit symbol aktiva. + +Syntaxe ` if else ` je pythonovský ekvivalent [ternárního podmíněného operátoru](https://en.wikipedia.org/wiki/Ternary_conditional_operator), který by v jazyce odvozeném od C byl ` ? : `. + +```python +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 +``` + +Tato funkce sestaví řetězec, který formátuje seznam objektů `Quote`, za předpokladu, že se všechny vztahují ke stejnému aktivu. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +V Pythonu se [víceřádkové řetězcové literály](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) zapisují jako `"""` .... `"""`. + +```python +Vzhledem k těmto nabídkám: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +Zde používáme vzor [MapReduce](https://en.wikipedia.org/wiki/MapReduce) k vygenerování řetězce pro každý seznam nabídek pomocí `format_quotes`, a pak je zredukujeme do jediného řetězce pro použití ve výzvě. + +```python +Jakou hodnotu byste očekávali pro {asset} v čase {expected_time}? + +Poskytněte odpověď jako jediné číslo zaokrouhlené na dvě desetinná místa, +bez jakéhokoli dalšího textu. + """ +``` + +Zbytek výzvy je podle očekávání. + +```python +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 +) +``` + +Přezkoumejte oba fondy a získejte nabídky z obou. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +Určete budoucí časový bod, pro který chcete odhad, a vytvořte výzvu. + +### Propojení s LLM {#interface-llm} + +Dále vyzveme skutečný LLM a získáme očekávanou budoucí hodnotu. Tento program jsem napsal pomocí OpenAI, takže pokud chcete použít jiného poskytovatele, budete ho muset upravit. + +1. Získejte [účet OpenAI](https://auth.openai.com/create-account) + +2. [Vložte peníze na účet](https://platform.openai.com/settings/organization/billing/overview) — minimální částka v době psaní je 5 $ + +3. [Vytvořte API klíč](https://platform.openai.com/settings/organization/api-keys) + +4. V příkazovém řádku exportujte API klíč, aby ho váš program mohl použít + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. Checkout a spuštění agenta + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +Zde je nový kód. + +```python +from openai import OpenAI + +open_ai = OpenAI() # Klient čte proměnnou prostředí OPENAI_API_KEY +``` + +Import a instancování API OpenAI. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +Zavolejte API OpenAI (`open_ai.chat.completions.create`) pro vytvoření odpovědi. + +```python +expected_price = Decimal(response.choices[0].message.content.strip()) +current_price = wethusdc_quotes[-1].price + +print ("Aktuální cena:", wethusdc_quotes[-1].price) +print(f"V {future_time} je očekávaná cena: {expected_price} USD") + +if (expected_price > current_price): + print(f"Nákup, očekávám, že cena vzroste o {expected_price - current_price} USD") +else: + print(f"Prodej, očekávám, že cena klesne o {current_price - expected_price} USD") +``` + +Vypište cenu a poskytněte doporučení na nákup nebo prodej. + +#### Testování předpovědí {#testing-the-predictions} + +Nyní, když můžeme generovat předpovědi, můžeme také použít historická data k posouzení, zda produkujeme užitečné předpovědi. + +```sh +uv run test-predictor.py +``` + +Očekávaný výsledek je podobný: + +``` +Předpověď pro 2026-01-05T19:50: předpovězeno 3138,93 USD, reálná 3218,92 USD, chyba 79,99 USD +Předpověď pro 2026-01-06T19:56: předpovězeno 3243,39 USD, reálná 3221,08 USD, chyba 22,31 USD +Předpověď pro 2026-01-07T20:02: předpovězeno 3223,24 USD, reálná 3146,89 USD, chyba 76,35 USD +Předpověď pro 2026-01-08T20:11: předpovězeno 3150,47 USD, reálná 3092,04 USD, chyba 58,43 USD +. +. +. +Předpověď pro 2026-01-31T22:33: předpovězeno 2637,73 USD, reálná 2417,77 USD, chyba 219,96 USD +Předpověď pro 2026-02-01T22:41: předpovězeno 2381,70 USD, reálná 2318,84 USD, chyba 62,86 USD +Předpověď pro 2026-02-02T22:49: předpovězeno 2234,91 USD, reálná 2349,28 USD, chyba 114,37 USD +Průměrná chyba předpovědi u 29 předpovědí: 83,87103448275862068965517241 USD +Průměrná změna na doporučení: 4,787931034482758620689655172 USD +Standardní odchylka změn: 104,42 USD +Ziskové dny: 51,72% +Ztrátové dny: 48,28% +``` + +Většina testeru je identická s agentem, ale zde jsou části, které jsou nové nebo upravené. + +```python +CYCLES_FOR_TEST = 40 # Pro zpětné testování, kolik cyklů testujeme + +# Získání velkého množství nabídek +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 +) +``` + +Díváme se na `CYCLES_FOR_TEST` (zde uvedeno jako 40) dní zpět. + +```python +# Vytvoření předpovědí a jejich kontrola vůči skutečné historii + +total_error = Decimal(0) +changes = [] +``` + +Zajímají nás dva typy chyb. První, `total_error`, je jednoduše součet chyb, které prediktor udělal. + +Pro pochopení druhé, `changes`, si musíme připomenout účel agenta. Není to předpovídání poměru WETH/USDC (cena ETH). Je to vydávání doporučení na prodej a nákup. Pokud je cena aktuálně 2000 $ a předpovídá 2010 $ zítra, nevadí nám, pokud bude skutečný výsledek 2020 $ a vyděláme více peněz. Ale _vadí_ nám, když předpověděl 2010 $, na základě tohoto doporučení koupil ETH a cena klesla na 1990 $. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +Můžeme se podívat pouze na případy, kdy je k dispozici kompletní historie (hodnoty použité pro predikci a reálná hodnota pro srovnání). To znamená, že nejnovější případ musí být ten, který začal před `CYCLES_BACK`. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +Použijte [řezy](https://www.w3schools.com/python/ref_func_slice.asp) k získání stejného počtu vzorků, jaké používá agent. Kód mezi tímto a dalším segmentem je stejný kód pro získání predikce, který máme v agentu. + +```python + 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 +``` + +Získejte předpokládanou cenu, skutečnou cenu a cenu v době předpovědi. Cenu v době předpovědi potřebujeme k určení, zda bylo doporučeno nakoupit nebo prodat. + +```python + error = abs(predicted_price - real_price) + total_error += error + print (f"Předpověď pro {prediction_time}: předpovězeno {predicted_price} USD, skutečná {real_price} USD, chyba {error} USD") +``` + +Zjistěte chybu a přičtěte ji k celkové. + +```python + 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) +``` + +Pro `changes` chceme peněžní dopad nákupu nebo prodeje jednoho ETH. Nejprve tedy musíme určit doporučení, poté posoudit, jak se skutečná cena změnila, a zda doporučení vydělalo peníze (pozitivní změna) nebo stálo peníze (negativní změna). + +```python +print (f"Průměrná chyba předpovědi pro {len(wethusdc_quotes)-CYCLES_BACK} předpovědí: {total_error / Decimal(len(wethusdc_quotes)-CYCLES_BACK)} USD") + +length_changes = Decimal(len(changes)) +mean_change = sum(changes, Decimal(0)) / length_changes +print (f"Průměrná změna na doporučení: {mean_change} USD") +var = sum((x - mean_change) ** 2 for x in changes) / length_changes +print (f"Standardní odchylka změn: {var.sqrt().quantize(Decimal("0.01"))} USD") +``` + +Vypište výsledky. + +```python +print (f"Ziskové dny: {len(list(filter(lambda x: x > 0, changes)))/length_changes:.2%}") +print (f"Ztrátové dny: {len(list(filter(lambda x: x < 0, changes)))/length_changes:.2%}") +``` + +Použijte [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) k počítání počtu ziskových a ztrátových dnů. Výsledkem je objekt filtru, který je třeba převést na seznam, abychom získali jeho délku. + +### Odesílání transakcí {#submit-txn} + +Nyní musíme skutečně odesílat transakce. Nechci však v tomto okamžiku utrácet skutečné peníze, než se systém osvědčí. Místo toho vytvoříme lokální větev hlavní sítě a „obchodovat“ budeme v této síti. + +Zde jsou kroky k vytvoření lokální větve a povolení obchodování. + +1. Nainstalujte si [Foundry](https://getfoundry.sh/introduction/installation) + +2. Spusťte [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` naslouchá na výchozí URL pro Foundry, http://localhost:8545, takže nemusíme specifikovat URL pro příkaz [`cast`](https://getfoundry.sh/cast/overview), který používáme k manipulaci s blockchainem. + +3. Při běhu v `anvil` je k dispozici deset testovacích účtů, které mají ETH — nastavte proměnné prostředí pro první z nich + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. Toto jsou smlouvy, které musíme použít. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) je smlouva Uniswap v3, kterou používáme k samotnému obchodování. Mohli bychom obchodovat přímo přes fond, ale toto je mnohem jednodušší. + + Spodní dvě proměnné jsou cesty Uniswap v3 potřebné pro směnu mezi WETH a USDC. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. Každý z testovacích účtů má 10 000 ETH. Použijte smlouvu WETH k zabalení 1000 ETH a získejte 1000 WETH pro obchodování. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. Použijte `SwapRouter` k obchodování 500 WETH za USDC. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + Volání `approve` vytvoří příspěvek, který umožní `SwapRouter` utratit některé z našich tokenů. Smlouvy nemohou sledovat události, takže pokud bychom převedli tokeny přímo na smlouvu `SwapRouter`, nevěděla by, že byla zaplacena. Místo toho povolíme smlouvě `SwapRouter` utratit určitou částku a poté to `SwapRouter` udělá. To se provádí pomocí funkce volané `SwapRouter`, takže ví, zda byla úspěšná. + +7. Ověřte si, že máte dostatek obou tokenů. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +Nyní, když máme WETH a USDC, můžeme skutečně spustit agenta. + +```sh +git checkout 05-trade +uv run agent.py +``` + +Výstup bude vypadat podobně jako: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +Aktuální cena: 1843.16 +V 2026-02-06T23:07, očekávaná cena: 1724.41 USD +Stavy účtů před obchodem: +USDC Zůstatek: 927301.578272 +WETH Zůstatek: 500 +Prodej, očekávám, že cena klesne o 118.75 USD +Schvalovací transakce odeslána: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +Schvalovací transakce vytěžena. +Prodejní transakce odeslána: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +Prodejní transakce vytěžena. +Stavy účtů po obchodě: +USDC Zůstatek: 929143.797116 +WETH Zůstatek: 499 +``` + +Pro skutečné použití potřebujete několik drobných změn. + +- Na řádku 14 změňte `MAINNET_URL` na skutečný přístupový bod, například `https://eth.drpc.org` +- Na řádku 28 změňte `PRIVATE_KEY` na váš vlastní privátní klíč +- Pokud nejste velmi bohatí a nemůžete si dovolit kupovat nebo prodávat 1 ETH každý den pro neprověřeného agenta, možná budete chtít změnit řádek 29 a snížit `WETH_TRADE_AMOUNT` + +#### Vysvětlení kódu {#trading-code} + +Zde je nový kód. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +Stejné proměnné, které jsme použili v kroku 4. + +```python +WETH_TRADE_AMOUNT=1 +``` + +Částka k obchodování. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +K samotnému obchodování potřebujeme funkci `approve`. Chceme také zobrazit zůstatky před a po, takže potřebujeme také `balanceOf`. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +V ABI `SwapRouter` potřebujeme pouze `exactInput`. Existuje příbuzná funkce `exactOutput`, kterou bychom mohli použít k nákupu přesně jednoho WETH, ale pro jednoduchost používáme `exactInput` v obou případech. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +Definice Web3 pro [`účet`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) a smlouvu `SwapRouter`. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +Parametry transakce. Potřebujeme zde funkci, protože [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) se musí pokaždé měnit. + +```python +def approve_token(contract: Contract, amount: int): +``` + +Schvalte povolenku tokenu pro `SwapRouter`. + +```python + 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) +``` + +Takto posíláme transakci v Web3. Nejprve použijeme [objekt `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) k vytvoření transakce. Poté použijeme [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) k podepsání transakce pomocí `PRIVATE_KEY`. Nakonec použijeme [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) k odeslání transakce. + +```python + print(f"Schvalovací transakce odeslána: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Schvalovací transakce vytěžena.") +``` + +[`w3.eth.wait_for_transaction_receipt`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) čeká, dokud transakce není vytěžena. V případě potřeby vrátí potvrzení. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +Toto jsou parametry pro prodej WETH. + +```python +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, + } +``` + +Na rozdíl od `SELL_PARAMS`, parametry pro nákup se mohou měnit. Vstupní částka je cena 1 WETH, jak je uvedeno v `quote`. + +```python +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"Nákupní transakce odeslána: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Nákupní transakce vytěžena.") + + +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"Prodejní transakce odeslána: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Prodejní transakce vytěžena.") +``` + +Funkce `buy()` a `sell()` jsou téměř identické. Nejprve schválíme dostatečnou povolenku pro `SwapRouter` a poté ho zavoláme se správnou cestou a částkou. + +```python +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} Zůstatek: {Decimal(token0_balance) / Decimal(10 ** wethusdc_pool.token0.decimals)}") + print(f"{wethusdc_pool.token1.symbol} Zůstatek: {Decimal(token1_balance) / Decimal(10 ** wethusdc_pool.token1.decimals)}") +``` + +Hlásit zůstatky uživatelů v obou měnách. + +```python +print("Stav účtu před obchodem:") +balances() + +if (expected_price > current_price): + print(f"Nákup, očekávám, že cena vzroste o {expected_price - current_price} USD") + buy(wethusdc_quotes[-1]) +else: + print(f"Prodej, očekávám, že cena klesne o {current_price - expected_price} USD") + sell() + +print("Stav účtu po obchodě:") +balances() +``` + +Tento agent v současné době funguje pouze jednou. Můžete ho však upravit tak, aby pracoval nepřetržitě, buď spuštěním z [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) nebo zabalením řádků 368–400 do smyčky a použitím [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) k čekání, dokud nenastane čas na další cyklus. + +## Možná vylepšení {#improvements} + +Toto není plná produkční verze; je to pouze příklad pro naučení základů. Zde jsou některé nápady na vylepšení. + +### Chytřejší obchodování {#smart-trading} + +Existují dvě důležité skutečnosti, které agent ignoruje při rozhodování, co dělat. + +- _Velikost očekávané změny_. Agent prodává pevnou částku `WETH`, pokud se očekává pokles ceny, bez ohledu na velikost poklesu. + Dalo by se namítnout, že by bylo lepší ignorovat drobné změny a prodávat na základě toho, jak moc očekáváme pokles ceny. +- _Současné portfolio_. Pokud je 10 % vašeho portfolia v WETH a myslíte si, že cena poroste, pravděpodobně má smysl koupit více. Pokud je ale 90 % vašeho portfolia v WETH, můžete být dostatečně exponovaní a není třeba kupovat více. Opačně to platí, pokud očekáváte pokles ceny. + +### Co když chcete udržet svou obchodní strategii v tajnosti? {#secret} + +Prodejci AI mohou vidět dotazy, které posíláte jejich LLM, což by mohlo odhalit geniální obchodní systém, který jste vyvinuli se svým agentem. Obchodní systém, který používá příliš mnoho lidí, je bezcenný, protože příliš mnoho lidí se snaží nakupovat, když chcete nakupovat (a cena stoupá) a snaží se prodávat, když chcete prodávat (a cena klesá). + +Tomuto problému se můžete vyhnout spuštěním LLM lokálně, například pomocí [LM-Studio](https://lmstudio.ai/). + +### Od AI bota k AI agentovi {#bot-to-agent} + +Můžete dobře argumentovat, že se jedná o [AI bota, nikoli AI agenta](/ai-agents/#ai-agents-vs-ai-bots). Implementuje relativně jednoduchou strategii, která se opírá o předdefinované informace. Můžeme umožnit sebezdokonalování, například poskytnutím seznamu fondů Uniswap v3 a jejich nejnovějších hodnot a zeptat se, která kombinace má nejlepší prediktivní hodnotu. + +### Ochrana proti prokluzu {#slippage-protection} + +V současné době neexistuje žádná [ochrana proti prokluzu](https://uniswapv3book.com/milestone_3/slippage-protection.html). Pokud je aktuální nabídka 2000 $ a očekávaná cena je 2100 $, agent nakoupí. Pokud však předtím, než agent nakoupí, cena vzroste na 2200 $, už nemá smysl nakupovat. + +Pro implementaci ochrany proti prokluzu zadejte hodnotu `amountOutMinimum` na řádcích 325 a 334 v [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325). + +## Závěr {#conclusion} + +Doufejme, že nyní víte dost na to, abyste mohli začít s agenty AI. Toto není komplexní přehled tématu; jsou o tom celé knihy, ale to stačí na to, abyste mohli začít. Hodně štěstí! + +[Více z mé práce najdete zde](https://cryptodocguy.pro/). diff --git a/public/content/translations/de/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/de/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..69180e6e974 --- /dev/null +++ b/public/content/translations/de/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: Make your own AI trading agent on Ethereum +description: In this tutorial you learn how to make a simple AI trading agent. This agent reads information from the blockchain, asks an LLM for a recommendation based on that information, performs the trade the LLM recommends, and then waits and repeats. +author: Ori Pomerantz +tags: [ "AI", "trading", "Agent", "Python" ] +skill: intermediate +published: 2026-02-13 +lang: de +sidebarDepth: 3 +--- + +In this tutorial you learn how to build a simple AI trading agent. This agent works using these steps: + +1. Read the current and past prices of a token, as well as other potentially relevant information +2. Build a query with this information, along with background information to explain how it might be relevant +3. Submit the query and receive back a projected price +4. Trade based on the recommendation +5. Wait and repeat + +This agent demonstrates how to read information, translate it into a query that yields a usable answer, and use that answer. All of these are steps required for an AI agent. This agent is implemented in Python because it is the most common language used in AI. + +## Why do this? {#why-do-this} + +Automated trading agents allow developers to select and execute a trading strategy. [AI agents](/ai-agents) allow for more complex and dynamic trading strategies, potentially using information and algorithms the developer has not even considered using. + +## The tools {#tools} + +This tutorial uses [Python](https://www.python.org/), the [Web3 library](https://web3py.readthedocs.io/en/stable/), and [Uniswap v3](https://github.com/Uniswap/v3-periphery) for quotes and trading. + +### Why Python? {#python} + +The most widely used language for AI is [Python](https://www.python.org/), so we use it here. Don't worry if you don't know Python. The language is very clear, and I will explain exactly what it does. + +The [Web3 library](https://web3py.readthedocs.io/en/stable/) is the most common Python Ethereum API. It is pretty easy to use. + +### Trading on the blockchain {#trading-on-blockchain} + +There are [many distributed exchanges (DEX)](/apps/categories/defi/) that let you trade tokens on Ethereum. However, they tend to have similar exchange rates due to [arbitrage](/developers/docs/smart-contracts/composability/#better-user-experience). + +[Uniswap](https://app.uniswap.org/) is a widely used DEX that we can use for both quotes (to see token relative values) and trades. + +### OpenAI {#openai} + +For a large language model, I chose to get started with [OpenAI](https://openai.com/). To run the application in this tutorial you'll need to pay for API access. The minimum payment of $5 is more than sufficient. + +## Development, step by step {#step-by-step} + +To simplify development, we proceed in stages. Each step is a branch in GitHub. + +### Erste Schritte {#getting-started} + +There are steps to get started under UNIX or Linux (including [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) + +1. If you don't already have it, download and install [Python](https://www.python.org/downloads/). + +2. Klonen Sie das GitHub-Repository. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. Install [`uv`](https://docs.astral.sh/uv/getting-started/installation/). The command on your system might be different. + + ```sh + pipx install uv + ``` + +4. Download the libraries. + + ```sh + uv sync + ``` + +5. Activate the virtual environment. + + ```sh + source .venv/bin/activate + ``` + +6. To verify Python and Web3 are working correctly, run `python3` and provide it with this program. You can enter it at the `>>>` prompt; there is no need to create a file. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### Reading from the blockchain {#read-blockchain} + +The next step is to read from the blockchain. To do that, you need to change to the `02-read-quote` branch and then use `uv` to run the program. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +You should receive a list of `Quote` objects, each with a timestamp, a price, and the asset (currently always `WETH/USDC`). + +Here is a line-by-line explanation. + +```python +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 +``` + +Importieren Sie die Bibliotheken, die wir benötigen. They are explained below when used. + +```python +print = functools.partial(print, flush=True) +``` + +Replaces Python’s `print` with a version that always flushes output immediately. This is useful in a long-running script because we don't want to wait for status updates or debugging output. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +A URL to get to mainnet. You can get one from [Node as a service](/developers/docs/nodes-and-clients/nodes-as-a-service/) or use one of those advertised in [Chainlist](https://chainlist.org/chain/1). + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +An Ethereum mainnet block typically happens every twelve seconds, so these are the number of blocks we'd expect to happen in a time period. Note that this is not an exact figure. When the [block proposer](/developers/docs/consensus-mechanisms/pos/block-proposal/) is down, that block is skipped, and the time for the next block is 24 seconds. If we wanted to get the exact block for a timestamp, we'd use [binary search](https://en.wikipedia.org/wiki/Binary_search). However, this is close enough for our purposes. Predicting the future is not an exact science. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +The size of the cycle. We review quotes once per cycle and try to estimate the value at the end of the next cycle. + +```python +# The address of the pool we're reading +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +The quote values are taken from the Uniswap 3 USDC/WETH pool at address [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract). This address is already in checksum form, but it's better to use [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) to make the code reusable. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +These are the [ABIs](https://docs.soliditylang.org/en/latest/abi-spec.html) for the two contracts we need to contact. To keep the code concise, we include only the functions we need to call. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +Initiate the [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) library and connect to an Ethereum node. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +This is one way to create a data class in Python. The [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) data type is used to connect to the contract. Note the `(frozen=True)`. In Python [booleans](https://en.wikipedia.org/wiki/Boolean_data_type) are defined as `True` or `False`, capitalized. This data class is `frozen`, meaning the fields cannot be modified. + +Note the indentation. In contrast to [C-derived languages](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages), Python uses indentation to denote blocks. The Python interpreter knows that the following definition is not part of this data class because it doesn't start at the same indentation as the data class fields. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +The [`Decimal`](https://docs.python.org/3/library/decimal.html) type is used for accurately handling decimal fractions. + +```python + def get_price(self, block: int) -> Decimal: +``` + +This is the way to define a function in Python. The definition is indented to show it is still part of `PoolInfo`. + +In a function that is part of a data class the first parameter is always `self`, the data class instance that called here. Here there is another parameter, the block number. + +```python + assert block <= w3.eth.block_number, "Block is in the future" +``` + +If we could read the future, we wouldn't need AI for trading. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +The syntax for calling a function on the EVM from Web3 is this: `.functions.().call()`. The parameters can be the EVM function's parameters (if any; here there aren't) or [named parameters](https://en.wikipedia.org/wiki/Named_parameter) for modifying blockchain behavior. Here we use one, `block_identifier`, to specify [the block number](/developers/docs/apis/json-rpc/#default-block) we wish to run in. + +The result is [this struct, in array form](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). The first value is a function of the exchange rate between the two tokens. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +To reduce onchain calculations, Uniswap v3 does not store the actual exchange factor but rather its square root. Because the EVM does not support floating point math or fractions, instead of the actual value, the response is price296 + +```python + # (token1 per token0) + return 1/(raw_price * self.decimal_factor) +``` + +The raw price we get is the number of `token0` we get for each `token1`. In our pool `token0` is USDC (stablecoin with the same value as a US dollar) and `token1` is [WETH](https://opensea.io/learn/blockchain/what-is-weth). The value we really want is the number of dollars per WETH, not the inverse. + +The decimal factor is the ratio between the [decimal factors](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) for the two tokens. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +This data class represents a quote: the price of a specific asset at a given point in time. At this point, the `asset` field is irrelevant because we use a single pool and therefore have a single asset. However, we will add more assets later. + +```python +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 + ) +``` + +This function takes an address and returns information about the token contract at that address. To create a new [Web3 `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html), we provide the address and ABI to `w3.eth.contract`. + +```python +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) + ) +``` + +This function returns everything we need about [a specific pool](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol). The syntax `f""` is a [formatted string](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +Get a `Quote` object. The default value for `block_number` is `None` (no value). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +If a block number was not specified, use `w3.eth.block_number`, which is the latest block number. This is the syntax for [an `if` statement](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement). + +It might look as if it would have been better to just set the default to `w3.eth.block_number`, but that doesn't work well because it would be the block number at the time the function is defined. In a long-running agent, this would be a problem. + +```python + 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 + ) +``` + +Use [the `datetime` library](https://docs.python.org/3/library/datetime.html) to format it to a format readable for humans and large language models (LLMs). Use [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) to round the value to two decimal places. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +In Python you define a [list](https://docs.python.org/3/library/stdtypes.html#typesseq-list) that can only contain a specific type using `list[]`. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +In Python a [`for` loop](https://docs.python.org/3/tutorial/controlflow.html#for-statements) typically iterates over a list. The list of block numbers to find quotes in comes from [`range`](https://docs.python.org/3/library/stdtypes.html#range). + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +For each block number, get a `Quote` object and append it to the `quotes` list. Then return that list. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +This is the main code of the script. Read the pool information, get twelve quotes, and [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) them. + +### Creating a prompt {#prompt} + +Next, we need to convert this list of quotes into a prompt for an LLM and obtain an expected future value. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +The output is now going to be a prompt to an LLM, similar to: + +``` +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. +``` + +Notice that there are quotes for two assets here, `WETH/USDC` and `WBTC/WETH`. Adding quotes from another asset might improve the prediction accuracy. + +#### What a prompt looks like {#prompt-explanation} + +This prompt contains three sections, which are pretty common in LLM prompts. + +1. Information. LLMs have a lot of information from their training, but they usually don't have the latest. This is the reason we need to retrieve the latest quotes here. Adding information to a prompt is called [retrieval augmented generation (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +2. The actual question. This is what we want to know. + +3. Output formatting instructions. Normally, an LLM will give us an estimate with an explanation of how it arrived at it. This is better for humans, but a computer program just needs the bottom line. + +#### Code explanation {#prompt-code} + +Here is the new code. + +```python +from datetime import datetime, timezone, timedelta +``` + +We need to provide the LLM with the time for which we want an estimate. To get a time "n minutes/hours/days" in the future, we use [the `timedelta` class](https://docs.python.org/3/library/datetime.html#datetime.timedelta). + +```python +# The addresses of the pools we're reading +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +We have two pools we need to read. + +```python +@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 # (token1 per token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +In the WETH/USDC pool, we want to know how many of `token0` (USDC) we need to buy one of `token1` (WETH). In the WETH/WBTC pool, we want to know how many `token1` (WETH) we need to buy one `token0` (WBTC, which is wrapped Bitcoin). We need to track whether the pool’s ratio needs to be reversed. + +```python +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 + ) +``` + +To know if a pool needs to be reversed, we get to get that as input to `read_pool`. Also, the asset symbol needs to be set up correctly. + +The syntax ` if else ` is the Python equivalent of the [ternary conditional operator](https://en.wikipedia.org/wiki/Ternary_conditional_operator), which in a C-derived language would be ` ? : `. + +```python +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 +``` + +This function builds a string that formats a list of `Quote` objects, assuming they all apply to the same asset. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +In Python [multi-line string literals](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) are written as `"""` .... `"""`. + +```python +Given these quotes: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +Here, we use the [MapReduce](https://en.wikipedia.org/wiki/MapReduce) pattern to generate a string for each quote list with `format_quotes`, then reduce them into a single string for use in the prompt. + +```python +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. + """ +``` + +The rest of the prompt is as expected. + +```python +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 +) +``` + +Review the two pools and obtain quotes from both. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +Determine the future time point for which we want the estimate, and create the prompt. + +### Interfacing with an LLM {#interface-llm} + +Next, we prompt an actual LLM and receive an expected future value. I wrote this program using OpenAI, so if you want to use a different provider, you'll need to adjust it. + +1. Get an [OpenAI account](https://auth.openai.com/create-account) + +2. [Fund the account](https://platform.openai.com/settings/organization/billing/overview)—the minimum amount at the time of writing is $5 + +3. [Create an API key](https://platform.openai.com/settings/organization/api-keys) + +4. In the command line, export the API key so your program can use it + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. Checkout and run the agent + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +Here is the new code. + +```python +from openai import OpenAI + +open_ai = OpenAI() # The client reads the OPENAI_API_KEY environment variable +``` + +Import and instantiate the OpenAI API. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +Call the OpenAI API (`open_ai.chat.completions.create`) to create the response. + +```python +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") +``` + +Output the price and provide a buy or sell recommendation. + +#### Testing the predictions {#testing-the-predictions} + +Now that we can generate predictions, we can also use historical data to assess whether we produce useful predictions. + +```sh +uv run test-predictor.py +``` + +The expected result is similar to: + +``` +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% +``` + +Most of the tester is identical to the agent, but here are the parts that are new or modified. + +```python +CYCLES_FOR_TEST = 40 # For the backtest, how many cycles we test over + +# Get lots of quotes +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 +) +``` + +We look at `CYCLES_FOR_TEST` (specified as 40 here) days back. + +```python +# Create predictions and check them against real history + +total_error = Decimal(0) +changes = [] +``` + +There are two types of errors we are interested in. The first, `total_error`, is simply the sum of errors the predictor made. + +To understand the second, `changes`, we need to remember the agent's purpose. It's not to predict the WETH/USDC ratio (ETH price). It's to issue sell and buy recommendations. If the price is currently $2000 and it predicts $2010 tomorrow, we don't mind if the actual result is $2020 and we earn extra money. But we _do_ mind if it predicted $2010, and bought ETH based on that recommendation, and the price drops to $1990. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +We can only look at cases where the complete history (the values used for the prediction and the real-world value to compare it to) is available. This means the newest case must be the one that started `CYCLES_BACK` ago. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +Use [slices](https://www.w3schools.com/python/ref_func_slice.asp) to get the same number of samples as the number the agent uses. The code between here and the next segment is the same get-a-prediction code we have in the agent. + +```python + 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 +``` + +Get the predicted price, the real price, and the price at the time of the prediction. We need the price at the time of the prediction to determine whether the recommendation was to buy or sell. + +```python + 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") +``` + +Figure the error, and add it to the total. + +```python + 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) +``` + +For `changes`, we want the monetary impact of buying or selling one ETH. So first, we need to determine the recommendation, then assess how the actual price changed, and whether the recommendation made money (positive change) or cost money (negative change). + +```python +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") +``` + +Report the results. + +```python +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%}") +``` + +Use [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) to count the number of profitable days and the number of costly days. The result is a filter object, which we need to convert to a list to get the length. + +### Submitting transactions {#submit-txn} + +Now we need to actually submit transactions. However, I don't want to spend real money at this point, before the system is proven. Instead, we will create a local fork of mainnet, and "trade" on that network. + +Here are the steps to create a local fork and enable trading. + +1. Install [Foundry](https://getfoundry.sh/introduction/installation) + +2. Start [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` is listening on the default URL for Foundry, http://localhost:8545, so we don't need to specify the URL for [the `cast` command](https://getfoundry.sh/cast/overview) we use to manipulate the blockchain. + +3. When running in `anvil`, there are ten test accounts that have ETH—set the environment variables for the first one + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. These are the contracts we need to use. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) is the Uniswap v3 contract we use to actually trade. We could trade directly through the pool, but this is much easier. + + The two bottom variables are the Uniswap v3 paths required to swap between WETH and USDC. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. Each of the test accounts has 10,000 ETH. Use the WETH contract to wrap 1000 ETH to obtain 1000 WETH for trading. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. Use `SwapRouter` to trade 500 WETH for USDC. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + The `approve` call creates an allowance that allows `SwapRouter` to spend some of our tokens. Contracts cannot monitor events, so if we transfer tokens directly to the `SwapRouter` contract, it wouldn't know it was paid. Instead, we permit the `SwapRouter` contract to spend a certain amount, and then `SwapRouter` does it. This is done through a function called by `SwapRouter`, so it knows if it was successful. + +7. Verify you have enough of both tokens. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +Now that we have WETH and USDC, we can actually run the agent. + +```sh +git checkout 05-trade +uv run agent.py +``` + +The output will look similar to: + +``` +(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 +``` + +To actually use it, you need a few minor changes. + +- In line 14, change `MAINNET_URL` to a real access point, such as `https://eth.drpc.org` +- In line 28, change `PRIVATE_KEY` to your own private key +- Unless you are very wealthy and can buy or sell 1 ETH each day for an unproven agent, you might want to change 29 to decrease `WETH_TRADE_AMOUNT` + +#### Code explanation {#trading-code} + +Here is the new code. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +The same variables we used in step 4. + +```python +WETH_TRADE_AMOUNT=1 +``` + +The amount to trade. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +To actually trade, we need the `approve` function. We also want to show balances before and after, so we also need `balanceOf`. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +In the `SwapRouter` ABI we just need `exactInput`. There is a related function, `exactOutput`, we could use to buy exactly one WETH, but for simplicity we just use `exactInput` in both cases. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +The Web3 definitions for the [`account`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) and the `SwapRouter` contract. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +The transaction parameters. We need a function here because [the nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) must change each time. + +```python +def approve_token(contract: Contract, amount: int): +``` + +Approve a token allowance for `SwapRouter`. + +```python + 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) +``` + +This is how we send a transaction in Web3. First we use [the `Contract` object](https://web3py.readthedocs.io/en/stable/web3.contract.html) to build the transaction. Then we use [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) to sign the transaction, using `PRIVATE_KEY`. Finally, we use [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) to send the transaction. + +```python + 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`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) waits until the transaction is mined. It returns the receipt if needed. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +These are the parameters when selling WETH. + +```python +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, + } +``` + +In contrast to `SELL_PARAMS`, the buy parameters can change. The input amount is the cost of 1 WETH, as available in `quote`. + +```python +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.") +``` + +The `buy()` and `sell()` functions are nearly identical. First we approve a sufficient allowance for `SwapRouter`, and then we call it with the correct path and amount. + +```python +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)}") +``` + +Report user balances in both currencies. + +```python +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() +``` + +This agent currently only works once. However, you can change it to work continuously either by running it from [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) or by wrapping lines 368-400 in a loop and using [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) to wait until it is time for the next cycle. + +## Possible improvements {#improvements} + +This is not a full production version; it is merely an example to teach the basics. Here are some ideas for improvements. + +### Smarter trading {#smart-trading} + +There are two important facts the agent ignores when deciding what to do. + +- _The magnitude of anticipated change_. The agent sells a fixed amount of `WETH` if the price is expected to decline, regardless of the magnitude of the decline. + Arguably, it would be better to ignore minor changes and sell based on how much we expect the price to decline. +- _The current portfolio_. If 10% of your portfolio is in WETH and you think the price will go up, it probably makes sense to buy more. But if 90% of your portfolio is in WETH, you may be sufficiently exposed, and there is no need to buy more. The reverse is true if you expect the price to go down. + +### What if you want to keep your trading strategy a secret? {#secret} + +AI vendors can see the queries you send to their LLMs, which could expose the genius trading system you developed with your agent. A trading system that too many people use is worthless because too many people try to buy when you want to buy (and the price goes up) and try to sell when you want to sell (and the price goes down). + +You can run an LLM locally, for example, using [LM-Studio](https://lmstudio.ai/), to avoid this problem. + +### From AI bot to AI agent {#bot-to-agent} + +You can make a good case that this is [an AI bot, not an AI agent](/ai-agents/#ai-agents-vs-ai-bots). It implements a relatively simple strategy that relies on predefined information. We can enable self-improvement, for example, by providing a list of Uniswap v3 pools and their latest values and asking which combination has the best predictive value. + +### Slippage protection {#slippage-protection} + +Currently there is no [slippage protection](https://uniswapv3book.com/milestone_3/slippage-protection.html). If the current quote is $2000, and the expected price is $2100, the agent will buy. However, if before the agent buys the cost rises to $2200, it makes no sense to buy anymore. + +To implement slippage protection, specify an `amountOutMinimum` value in lines 325 and 334 of [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325). + +## Fazit {#conclusion} + +Hopefully, now you know enough to get started with AI agents. This is not a comprehensive overview of the subject; there are whole books dedicated to that, but this is enough to get you started. Viel Erfolg! + +[Hier finden Sie mehr von meiner Arbeit](https://cryptodocguy.pro/). diff --git a/public/content/translations/es/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/es/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..f1a2295f116 --- /dev/null +++ b/public/content/translations/es/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "Crea tu propio agente de negociación con IA en Ethereum" +description: "En este tutorial, aprenderás a crear un agente de negociación con IA simple. Este agente lee información de la cadena de bloques, solicita a un LLM una recomendación basada en esa información, realiza la operación que el LLM recomienda, luego espera y repite el proceso." +author: Ori Pomerantz +tags: [ "IA", "negociación", "agente", "python" ] +skill: intermediate +published: 2026-02-13 +lang: es +sidebarDepth: 3 +--- + +En este tutorial, aprenderás a crear un agente de negociación con IA simple. Este agente funciona siguiendo estos pasos: + +1. Lee los precios actuales y pasados de un token, así como otra información potencialmente relevante +2. Crea una consulta con esta información, junto con información de fondo para explicar cómo podría ser relevante +3. Envía la consulta y recibe un precio proyectado +4. Opera en función de la recomendación +5. Espera y repite + +Este agente demuestra cómo leer información, traducirla en una consulta que produzca una respuesta útil y utilizar esa respuesta. Todos estos son pasos necesarios para un agente de IA. Este agente está implementado en Python porque es el lenguaje más común utilizado en IA. + +## ¿Por qué hacer esto? {#why-do-this} + +Los agentes de negociación automatizados permiten a los desarrolladores seleccionar y ejecutar una estrategia de negociación. [Agentes de IA](/ai-agents) permiten estrategias de negociación más complejas y dinámicas, utilizando potencialmente información y algoritmos que el desarrollador ni siquiera ha considerado usar. + +## Las herramientas {#tools} + +Este tutorial utiliza [Python](https://www.python.org/), la [biblioteca Web3](https://web3py.readthedocs.io/en/stable/) y [Uniswap v3](https://github.com/Uniswap/v3-periphery) para cotizaciones y negociación. + +### ¿Por qué Python? {#python} + +El lenguaje más utilizado para la IA es [Python](https://www.python.org/), por lo que lo usamos aquí. No te preocupes si no sabes Python. El lenguaje es muy claro y explicaré exactamente lo que hace. + +La [biblioteca Web3](https://web3py.readthedocs.io/en/stable/) es la API de Python para Ethereum más común. Es bastante fácil de usar. + +### Operar en la cadena de bloques {#trading-on-blockchain} + +Existen [muchos intercambios descentralizados (DEX)](/apps/categories/defi/) que te permiten operar con tokens en Ethereum. Sin embargo, tienden a tener tasas de cambio similares debido al [arbitraje](/developers/docs/smart-contracts/composability/#better-user-experience). + +[Uniswap](https://app.uniswap.org/) es un DEX muy utilizado que podemos usar tanto para cotizaciones (para ver los valores relativos de los tokens) como para operaciones. + +### OpenAI {#openai} + +Para un modelo de lenguaje grande, elegí empezar con [OpenAI](https://openai.com/). Para ejecutar la aplicación de este tutorial, necesitarás pagar por el acceso a la API. El pago mínimo de 5 $ es más que suficiente. + +## Desarrollo, paso a paso {#step-by-step} + +Para simplificar el desarrollo, procederemos por etapas. Cada paso es una rama en GitHub. + +### Primeros pasos {#getting-started} + +Estos son los pasos para empezar en UNIX o Linux (incluido [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) + +1. Si aún no lo tienes, descarga e instala [Python](https://www.python.org/downloads/). + +2. Clone el repositorio de GitHub. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. Instala [`uv`](https://docs.astral.sh/uv/getting-started/installation/). El comando en tu sistema podría ser diferente. + + ```sh + pipx install uv + ``` + +4. Descarga las bibliotecas. + + ```sh + uv sync + ``` + +5. Activa el entorno virtual. + + ```sh + source .venv/bin/activate + ``` + +6. Para verificar que Python y Web3 funcionan correctamente, ejecuta `python3` y proporciónale este programa. Puedes introducirlo en el indicador `>>>`; no es necesario crear un archivo. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### Lectura desde la cadena de bloques {#read-blockchain} + +El siguiente paso es leer desde la cadena de bloques. Para ello, debes cambiar a la rama `02-read-quote` y luego usar `uv` para ejecutar el programa. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +Deberías recibir una lista de objetos `Quote`, cada uno con una marca de tiempo, un precio y el activo (actualmente siempre `WETH/USDC`). + +Aquí tienes una explicación línea por línea. + +```python +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 +``` + +Importe las librerías que necesitamos. Se explican a continuación cuando se utilizan. + +```python +print = functools.partial(print, flush=True) +``` + +Reemplaza el `print` de Python con una versión que siempre vacía la salida inmediatamente. Esto es útil en un script de larga duración porque no queremos esperar a las actualizaciones de estado ni a la salida de depuración. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +Una URL para acceder a la red principal. Puedes obtener una de [Nodo como servicio](/developers/docs/nodes-and-clients/nodes-as-a-service/) o usar una de las anunciadas en [Chainlist](https://chainlist.org/chain/1). + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +Un bloque de la red principal de Ethereum normalmente se produce cada doce segundos, así que este es el número de bloques que esperaríamos que se produjeran en un período de tiempo. Ten en cuenta que esta no es una cifra exacta. Cuando el [proponente de bloque](/developers/docs/consensus-mechanisms/pos/block-proposal/) está inactivo, ese bloque se salta y el tiempo para el siguiente bloque es de 24 segundos. Si quisiéramos obtener el bloque exacto para una marca de tiempo, usaríamos la [búsqueda binaria](https://en.wikipedia.org/wiki/Binary_search). Sin embargo, esto es lo suficientemente aproximado para nuestros propósitos. Predecir el futuro no es una ciencia exacta. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +El tamaño del ciclo. Revisamos las cotizaciones una vez por ciclo e intentamos estimar el valor al final del siguiente ciclo. + +```python +# La dirección del fondo de liquidez que estamos leyendo +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +Los valores de cotización se toman del fondo de liquidez Uniswap 3 USDC/WETH en la dirección [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract). Esta dirección ya está en formato de suma de verificación, pero es mejor usar [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) para que el código sea reutilizable. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +Estas son las [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html) para los dos contratos que necesitamos contactar. Para mantener el código conciso, incluimos solo las funciones que necesitamos llamar. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +Inicia la biblioteca [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) y conéctate a un nodo de Ethereum. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +Esta es una forma de crear una clase de datos en Python. El tipo de datos [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) se usa para conectarse al contrato. Observa el `(frozen=True)`. En Python, los [booleanos](https://en.wikipedia.org/wiki/Boolean_data_type) se definen como `True` o `False`, con mayúscula inicial. Esta clase de datos es `frozen` (congelada), lo que significa que los campos no se pueden modificar. + +Observa la sangría. A diferencia de los [lenguajes derivados de C](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages), Python utiliza la sangría para denotar bloques. El intérprete de Python sabe que la siguiente definición no forma parte de esta clase de datos porque no comienza con la misma sangría que los campos de la clase de datos. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +El tipo [`Decimal`](https://docs.python.org/3/library/decimal.html) se utiliza para manejar con precisión las fracciones decimales. + +```python + def get_price(self, block: int) -> Decimal: +``` + +Esta es la forma de definir una función en Python. La definición está sangrada para mostrar que todavía es parte de `PoolInfo`. + +En una función que forma parte de una clase de datos, el primer parámetro es siempre `self`, la instancia de la clase de datos que la llamó. Aquí hay otro parámetro, el número de bloque. + +```python + assert block <= w3.eth.block_number, "El bloque está en el futuro" +``` + +Si pudiéramos leer el futuro, no necesitaríamos IA para operar. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +La sintaxis para llamar a una función en la EVM desde Web3 es esta: `.functions."().call()`. Los parámetros pueden ser los parámetros de la función de la EVM (si los hay; aquí no hay) o [parámetros con nombre](https://en.wikipedia.org/wiki/Named_parameter) para modificar el comportamiento de la cadena de bloques. Aquí usamos uno, `block_identifier`, para especificar [el número de bloque](/developers/docs/apis/json-rpc/#default-block) en el que deseamos ejecutar. + +El resultado es [esta estructura, en forma de matriz](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). El primer valor es una función de la tasa de cambio entre los dos tokens. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +Para reducir los cálculos en la cadena, Uniswap v3 no almacena el factor de cambio real, sino su raíz cuadrada. Debido a que la EVM no admite matemáticas de punto flotante ni fracciones, en lugar del valor real, la respuesta es price296 + +```python + # (token1 por token0) + return 1/(raw_price * self.decimal_factor) +``` + +El precio bruto que obtenemos es el número de `token0` que obtenemos por cada `token1`. En nuestro fondo de liquidez, `token0` es USDC (una moneda estable con el mismo valor que un dólar estadounidense) y `token1` es [WETH](https://opensea.io/learn/blockchain/what-is-weth). El valor que realmente queremos es el número de dólares por WETH, no el inverso. + +El factor decimal es la relación entre los [factores decimales](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) de los dos tokens. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +Esta clase de datos representa una cotización: el precio de un activo específico en un momento dado. En este punto, el campo `asset` es irrelevante porque usamos un único fondo de liquidez y, por lo tanto, tenemos un único activo. Sin embargo, añadiremos más activos más adelante. + +```python +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 + ) +``` + +Esta función toma una dirección y devuelve información sobre el contrato del token en esa dirección. Para crear un nuevo [`Contrato` de Web3](https://web3py.readthedocs.io/en/stable/web3.contract.html), proporcionamos la dirección y la ABI a `w3.eth.contract`. + +```python +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) + ) +``` + +Esta función devuelve todo lo que necesitamos sobre [un fondo de liquidez específico](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol). La sintaxis `f""` es una [cadena formateada](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +Obtiene un objeto `Quote`. El valor predeterminado para `block_number` es `None` (sin valor). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +Si no se especificó un número de bloque, usa `w3.eth.block_number`, que es el último número de bloque. Esta es la sintaxis para [una instrucción `if`](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement). + +Podría parecer que hubiera sido mejor simplemente establecer el valor predeterminado en `w3.eth.block_number`, pero eso no funciona bien porque sería el número de bloque en el momento en que se define la función. En un agente de larga duración, esto sería un problema. + +```python + 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 + ) +``` + +Usa [la biblioteca `datetime`](https://docs.python.org/3/library/datetime.html) para darle un formato legible para humanos y modelos de lenguaje grandes (LLM). Usa [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) para redondear el valor a dos decimales. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +En Python, se define una [lista](https://docs.python.org/3/library/stdtypes.html#typesseq-list) que solo puede contener un tipo específico usando `list[]`. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +En Python, un [bucle `for`](https://docs.python.org/3/tutorial/controlflow.html#for-statements) normalmente itera sobre una lista. La lista de números de bloque en los que buscar cotizaciones proviene de [`range`](https://docs.python.org/3/library/stdtypes.html#range). + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +Para cada número de bloque, obtén un objeto `Quote` y añádelo a la lista `quotes`. Luego, devuelve esa lista. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +Este es el código principal del script. Lee la información del fondo de liquidez, obtén doce cotizaciones y usa [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) para imprimirlas. + +### Crear una indicación {#prompt} + +A continuación, necesitamos convertir esta lista de cotizaciones en una indicación para un LLM y obtener un valor futuro esperado. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +La salida ahora será una indicación para un LLM, similar a: + +``` +Dadas estas cotizaciones: +Activo: WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +Activo: WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +¿Qué valor esperarías que tuviera WETH/USDC en la fecha 2026-02-02T17:56? + +Proporciona tu respuesta como un único número redondeado a dos decimales, +sin ningún otro texto. +``` + +Observa que aquí hay cotizaciones para dos activos, `WETH/USDC` y `WBTC/WETH`. Añadir cotizaciones de otro activo podría mejorar la precisión de la predicción. + +#### Cómo es una indicación {#prompt-explanation} + +Esta indicación contiene tres secciones, que son bastante comunes en las indicaciones para LLM. + +1. Información. Los LLM tienen mucha información de su entrenamiento, pero generalmente no tienen la más reciente. Esta es la razón por la que necesitamos recuperar las últimas cotizaciones aquí. Añadir información a una indicación se llama [generación aumentada por recuperación (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +2. La pregunta real. Esto es lo que queremos saber. + +3. Instrucciones de formato de salida. Normalmente, un LLM nos dará una estimación con una explicación de cómo llegó a ella. Esto es mejor para los humanos, pero un programa informático solo necesita el resultado final. + +#### Explicación del código {#prompt-code} + +Este es el nuevo código. + +```python +from datetime import datetime, timezone, timedelta +``` + +Necesitamos proporcionar al LLM la hora para la que queremos una estimación. Para obtener una hora "n minutos/horas/días" en el futuro, usamos [la clase `timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta). + +```python +# Las direcciones de los fondos de liquidez que estamos leyendo +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +Tenemos dos fondos de liquidez que necesitamos leer. + +```python +@dataclass(frozen=True) +class PoolInfo: + . + . + . + reverse: bool = False + + def get_price(self, block: int) -> Decimal: + assert block <= w3.eth.block_number, "El bloque está en el futuro" + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (token1 por token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +En el fondo de liquidez WETH/USDC, queremos saber cuántos de `token0` (USDC) necesitamos para comprar uno de `token1` (WETH). En el fondo de liquidez WETH/WBTC, queremos saber cuántos `token1` (WETH) necesitamos para comprar un `token0` (WBTC, que es Bitcoin envuelto). Necesitamos hacer un seguimiento de si la proporción del fondo de liquidez debe invertirse. + +```python +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 + ) +``` + +Para saber si un fondo de liquidez debe invertirse, debemos obtener eso como entrada para `read_pool`. Además, el símbolo del activo debe configurarse correctamente. + +La sintaxis ` if else ` es el equivalente en Python del [operador condicional ternario](https://en.wikipedia.org/wiki/Ternary_conditional_operator), que en un lenguaje derivado de C sería ` ? : `. + +```python +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 +``` + +Esta función crea una cadena que formatea una lista de objetos `Quote`, asumiendo que todos se aplican al mismo activo. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +En Python, los [literales de cadena multilínea](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) se escriben como `"""` .... `"""`. + +```python +Dadas estas cotizaciones: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +Aquí, usamos el patrón [MapReduce](https://en.wikipedia.org/wiki/MapReduce) para generar una cadena para cada lista de cotizaciones con `format_quotes`, y luego las reducimos a una sola cadena para usarla en la indicación. + +```python +¿Qué valor esperarías que tuviera {asset} en la fecha {expected_time}? + +Proporciona tu respuesta como un único número redondeado a dos decimales, +sin ningún otro texto. + """ +``` + +El resto de la indicación es como se esperaba. + +```python +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 +) +``` + +Revisa los dos fondos de liquidez y obtén cotizaciones de ambos. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +Determina el punto de tiempo futuro para el que queremos la estimación y crea la indicación. + +### Interfaz con un LLM {#interface-llm} + +A continuación, le daremos una indicación a un LLM real y recibiremos un valor futuro esperado. Escribí este programa usando OpenAI, así que si quieres usar un proveedor diferente, tendrás que ajustarlo. + +1. Obtén una [cuenta de OpenAI](https://auth.openai.com/create-account) + +2. [Financia la cuenta](https://platform.openai.com/settings/organization/billing/overview): la cantidad mínima en el momento de escribir este artículo es de 5 $ + +3. [Crea una clave de API](https://platform.openai.com/settings/organization/api-keys) + +4. En la línea de comandos, exporta la clave de API para que tu programa pueda usarla + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. Haz un «checkout» y ejecuta el agente + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +Este es el nuevo código. + +```python +from openai import OpenAI + +open_ai = OpenAI() # El cliente lee la variable de entorno OPENAI_API_KEY +``` + +Importa e instancia la API de OpenAI. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +Llama a la API de OpenAI (`open_ai.chat.completions.create`) para crear la respuesta. + +```python +expected_price = Decimal(response.choices[0].message.content.strip()) +current_price = wethusdc_quotes[-1].price + +print ("Precio actual:", wethusdc_quotes[-1].price) +print(f"En {future_time}, precio esperado: {expected_price} USD") + +if (expected_price > current_price): + print(f"Comprar, espero que el precio suba en {expected_price - current_price} USD") +else: + print(f"Vender, espero que el precio baje en {current_price - expected_price} USD") +``` + +Muestra el precio y proporciona una recomendación de compra o venta. + +#### Prueba de las predicciones {#testing-the-predictions} + +Ahora que podemos generar predicciones, también podemos usar datos históricos para evaluar si producimos predicciones útiles. + +```sh +uv run test-predictor.py +``` + +El resultado esperado es similar a: + +``` +Predicción para 2026-01-05T19:50: predicho 3138.93 USD, real 3218.92 USD, error 79.99 USD +Predicción para 2026-01-06T19:56: predicho 3243.39 USD, real 3221.08 USD, error 22.31 USD +Predicción para 2026-01-07T20:02: predicho 3223.24 USD, real 3146.89 USD, error 76.35 USD +Predicción para 2026-01-08T20:11: predicho 3150.47 USD, real 3092.04 USD, error 58.43 USD +. +. +. +Predicción para 2026-01-31T22:33: predicho 2637.73 USD, real 2417.77 USD, error 219.96 USD +Predicción para 2026-02-01T22:41: predicho 2381.70 USD, real 2318.84 USD, error 62.86 USD +Predicción para 2026-02-02T22:49: predicho 2234.91 USD, real 2349.28 USD, error 114.37 USD +Error de predicción medio en 29 predicciones: 83.87103448275862068965517241 USD +Cambio medio por recomendación: 4.787931034482758620689655172 USD +Varianza estándar de los cambios: 104.42 USD +Días rentables: 51.72% +Días con pérdidas: 48.28% +``` + +La mayor parte del probador es idéntica al agente, pero aquí están las partes que son nuevas o modificadas. + +```python +CYCLES_FOR_TEST = 40 # Para la prueba retrospectiva, cuántos ciclos probamos + +# Obtener muchas cotizaciones +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 +) +``` + +Miramos hacia atrás `CYCLES_FOR_TEST` (especificado como 40 aquí) días. + +```python +# Crear predicciones y verificarlas con el historial real + +total_error = Decimal(0) +changes = [] +``` + +Hay dos tipos de errores que nos interesan. El primero, `total_error`, es simplemente la suma de los errores que cometió el predictor. + +Para entender el segundo, `changes`, debemos recordar el propósito del agente. No es para predecir la relación WETH/USDC (precio de ETH). Es para emitir recomendaciones de compra y venta. Si el precio es actualmente de 2000 $ y predice 2010 $ para mañana, no nos importa si el resultado real es 2020 $ y ganamos dinero extra. Pero _sí_ nos importa si predijo 2010 $, y compramos ETH basándonos en esa recomendación, y el precio baja a 1990 $. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +Solo podemos ver los casos en los que el historial completo (los valores utilizados para la predicción y el valor del mundo real para compararlo) está disponible. Esto significa que el caso más reciente debe ser el que comenzó hace `CYCLES_BACK`. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +Usa [divisiones](https://www.w3schools.com/python/ref_func_slice.asp) para obtener el mismo número de muestras que el número que usa el agente. El código entre aquí y el siguiente segmento es el mismo código para obtener una predicción que tenemos en el agente. + +```python + 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 +``` + +Obtén el precio previsto, el precio real y el precio en el momento de la predicción. Necesitamos el precio en el momento de la predicción para determinar si la recomendación era comprar o vender. + +```python + error = abs(predicted_price - real_price) + total_error += error + print (f"Predicción para {prediction_time}: predicho {predicted_price} USD, real {real_price} USD, error {error} USD") +``` + +Calcula el error y súmalo al total. + +```python + 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) +``` + +Para `changes`, queremos el impacto monetario de comprar o vender un ETH. Así que primero, necesitamos determinar la recomendación, luego evaluar cómo cambió el precio real y si la recomendación generó ganancias (cambio positivo) o pérdidas (cambio negativo). + +```python +print (f"Error de predicción medio en {len(wethusdc_quotes)-CYCLES_BACK} predicciones: {total_error / Decimal(len(wethusdc_quotes)-CYCLES_BACK)} USD") + +length_changes = Decimal(len(changes)) +mean_change = sum(changes, Decimal(0)) / length_changes +print (f"Cambio medio por recomendación: {mean_change} USD") +var = sum((x - mean_change) ** 2 for x in changes) / length_changes +print (f"Varianza estándar de los cambios: {var.sqrt().quantize(Decimal("0.01"))} USD") +``` + +Informa de los resultados. + +```python +print (f"Días rentables: {len(list(filter(lambda x: x > 0, changes)))/length_changes:.2%}") +print (f"Días con pérdidas: {len(list(filter(lambda x: x < 0, changes)))/length_changes:.2%}") +``` + +Usa [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) para contar el número de días rentables y el número de días con pérdidas. El resultado es un objeto de filtro, que debemos convertir en una lista para obtener la longitud. + +### Envío de transacciones {#submit-txn} + +Ahora necesitamos enviar transacciones reales. Sin embargo, no quiero gastar dinero real en este punto, antes de que el sistema esté probado. En su lugar, crearemos una bifurcación local de la red principal y «operaremos» en esa red. + +Aquí están los pasos para crear una bifurcación local y habilitar la negociación. + +1. Instala [Foundry](https://getfoundry.sh/introduction/installation) + +2. Inicia [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` está escuchando en la URL predeterminada de Foundry, http://localhost:8545, por lo que no necesitamos especificar la URL para [el comando `cast`](https://getfoundry.sh/cast/overview) que usamos para manipular la cadena de bloques. + +3. Cuando se ejecuta en `anvil`, hay diez cuentas de prueba que tienen ETH: establece las variables de entorno para la primera + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. Estos son los contratos que necesitamos usar. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) es el contrato de Uniswap v3 que usamos para operar realmente. Podríamos operar directamente a través del fondo de liquidez, pero esto es mucho más fácil. + + Las dos variables inferiores son las rutas de Uniswap v3 necesarias para intercambiar entre WETH y USDC. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. Cada una de las cuentas de prueba tiene 10.000 ETH. Usa el contrato WETH para envolver 1000 ETH y obtener 1000 WETH para operar. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. Usa `SwapRouter` para intercambiar 500 WETH por USDC. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + La llamada `approve` crea una asignación que permite a `SwapRouter` gastar algunos de nuestros tokens. Los contratos no pueden supervisar eventos, por lo que si transferimos tokens directamente al contrato `SwapRouter`, no sabría que se le ha pagado. En cambio, permitimos que el contrato `SwapRouter` gaste una cierta cantidad, y luego `SwapRouter` lo hace. Esto se hace a través de una función llamada por `SwapRouter`, para que sepa si tuvo éxito. + +7. Verifica que tienes suficientes de ambos tokens. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +Ahora que tenemos WETH y USDC, podemos ejecutar el agente. + +```sh +git checkout 05-trade +uv run agent.py +``` + +La salida será similar a: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +Precio actual: 1843.16 +En 2026-02-06T23:07, precio esperado: 1724.41 USD +Saldos de la cuenta antes de la operación: +Saldo de USDC: 927301.578272 +Saldo de WETH: 500 +Vender, espero que el precio baje en 118.75 USD +Transacción de aprobación enviada: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +Transacción de aprobación minada. +Transacción de venta enviada: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +Transacción de venta minada. +Saldos de la cuenta después de la operación: +Saldo de USDC: 929143.797116 +Saldo de WETH: 499 +``` + +Para usarlo realmente, necesitas algunos cambios menores. + +- En la línea 14, cambia `MAINNET_URL` a un punto de acceso real, como `https://eth.drpc.org` +- En la línea 28, cambia `PRIVATE_KEY` a tu propia clave privada +- A menos que seas muy rico y puedas comprar o vender 1 ETH cada día para un agente no probado, es posible que quieras cambiar la línea 29 para disminuir `WETH_TRADE_AMOUNT` + +#### Explicación del código {#trading-code} + +Este es el nuevo código. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +Las mismas variables que usamos en el paso 4. + +```python +WETH_TRADE_AMOUNT=1 +``` + +La cantidad a operar. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +Para operar realmente, necesitamos la función `approve`. También queremos mostrar los saldos antes y después, así que también necesitamos `balanceOf`. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +En la ABI `SwapRouter`, solo necesitamos `exactInput`. Hay una función relacionada, `exactOutput`, que podríamos usar para comprar exactamente un WETH, pero por simplicidad solo usamos `exactInput` en ambos casos. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +Las definiciones de Web3 para la [`cuenta`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) y el contrato `SwapRouter`. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +Los parámetros de la transacción. Necesitamos una función aquí porque [el nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) debe cambiar cada vez. + +```python +def approve_token(contract: Contract, amount: int): +``` + +Aprobar una asignación de token para `SwapRouter`. + +```python + 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) +``` + +Así es como enviamos una transacción en Web3. Primero usamos [el objeto `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) para construir la transacción. Luego usamos [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) para firmar la transacción, usando `PRIVATE_KEY`. Finalmente, usamos [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) para enviar la transacción. + +```python + print(f"Transacción de aprobación enviada: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Transacción de aprobación minada.") +``` + +[`w3.eth.wait_for_transaction_receipt`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) espera hasta que la transacción sea minada. Devuelve el recibo si es necesario. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +Estos son los parámetros al vender WETH. + +```python +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, + } +``` + +A diferencia de `SELL_PARAMS`, los parámetros de compra pueden cambiar. La cantidad de entrada es el costo de 1 WETH, como está disponible en `quote`. + +```python +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"Transacción de compra enviada: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Transacción de compra minada.") + + +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"Transacción de venta enviada: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Transacción de venta minada.") +``` + +Las funciones `buy()` y `sell()` son casi idénticas. Primero aprobamos una asignación suficiente para `SwapRouter` y luego lo llamamos con la ruta y la cantidad correctas. + +```python +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} Saldo: {Decimal(token0_balance) / Decimal(10 ** wethusdc_pool.token0.decimals)}") + print(f"{wethusdc_pool.token1.symbol} Saldo: {Decimal(token1_balance) / Decimal(10 ** wethusdc_pool.token1.decimals)}") +``` + +Informa de los saldos de los usuarios en ambas monedas. + +```python +print("Saldos de la cuenta antes de la operación:") +balances() + +if (expected_price > current_price): + print(f"Comprar, espero que el precio suba en {expected_price - current_price} USD") + buy(wethusdc_quotes[-1]) +else: + print(f"Vender, espero que el precio baje en {current_price - expected_price} USD") + sell() + +print("Saldos de la cuenta después de la operación:") +balances() +``` + +Este agente actualmente solo funciona una vez. Sin embargo, puedes cambiarlo para que funcione continuamente, ya sea ejecutándolo desde [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) o envolviendo las líneas 368-400 en un bucle y usando [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) para esperar hasta que sea el momento del siguiente ciclo. + +## Posibles mejoras {#improvements} + +Esta no es una versión de producción completa; es simplemente un ejemplo para enseñar los conceptos básicos. Aquí hay algunas ideas para mejoras. + +### Negociación más inteligente {#smart-trading} + +Hay dos hechos importantes que el agente ignora al decidir qué hacer. + +- _La magnitud del cambio anticipado_. El agente vende una cantidad fija de `WETH` si se espera que el precio baje, independientemente de la magnitud de la caída. + Podría decirse que sería mejor ignorar los cambios menores y vender en función de cuánto esperamos que baje el precio. +- _La cartera actual_. Si el 10 % de tu cartera está en WETH y crees que el precio subirá, probablemente tenga sentido comprar más. Pero si el 90 % de tu cartera está en WETH, es posible que ya estés suficientemente expuesto y no haya necesidad de comprar más. Lo contrario es cierto si esperas que el precio baje. + +### ¿Y si quieres mantener tu estrategia de negociación en secreto? {#secret} + +Los proveedores de IA pueden ver las consultas que envías a sus LLM, lo que podría exponer el genial sistema de negociación que desarrollaste con tu agente. Un sistema de negociación que demasiada gente usa no tiene valor porque demasiada gente intenta comprar cuando tú quieres comprar (y el precio sube) e intenta vender cuando tú quieres vender (y el precio baja). + +Puedes ejecutar un LLM localmente, por ejemplo, usando [LM-Studio](https://lmstudio.ai/), para evitar este problema. + +### De bot de IA a agente de IA {#bot-to-agent} + +Se puede argumentar que este es [un bot de IA, no un agente de IA](/ai-agents/#ai-agents-vs-ai-bots). Implementa una estrategia relativamente simple que se basa en información predefinida. Podemos habilitar la automejora, por ejemplo, proporcionando una lista de fondos de liquidez de Uniswap v3 y sus últimos valores y preguntando qué combinación tiene el mejor valor predictivo. + +### Protección contra el deslizamiento {#slippage-protection} + +Actualmente no hay [protección contra el deslizamiento](https://uniswapv3book.com/milestone_3/slippage-protection.html). Si la cotización actual es de 2000 $ y el precio esperado es de 2100 $, el agente comprará. Sin embargo, si antes de que el agente compre el costo sube a 2200 $, ya no tiene sentido comprar. + +Para implementar la protección contra el deslizamiento, especifica un valor de `amountOutMinimum` en las líneas 325 y 334 de [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325). + +## Conclusión {#conclusion} + +Con suerte, ahora sabes lo suficiente para empezar con los agentes de IA. Esta no es una descripción exhaustiva del tema; hay libros enteros dedicados a eso, pero esto es suficiente para que empieces. ¡Buena suerte! + +[Vea aquí más de mi trabajo](https://cryptodocguy.pro/). diff --git a/public/content/translations/fr/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/fr/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..2cfcd48f5a9 --- /dev/null +++ b/public/content/translations/fr/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "Créez votre propre agent de trading IA sur Ethereum" +description: "Dans ce tutoriel, vous apprendrez comment créer un agent de trading IA simple. Cet agent lit les informations de la blockchain, demande une recommandation à un LLM sur la base de ces informations, effectue la transaction que le LLM recommande, puis attend et répète le processus." +author: Ori Pomerantz +tags: [ "IA", "trading", "agent", "python" ] +skill: intermediate +published: 2026-02-13 +lang: fr +sidebarDepth: 3 +--- + +Dans ce tutoriel, vous apprendrez comment créer un agent de trading IA simple. Cet agent fonctionne en suivant ces étapes : + +1. Lire les prix actuels et passés d'un jeton, ainsi que d'autres informations potentiellement pertinentes +2. Construire une requête avec ces informations, ainsi que des informations de base pour expliquer en quoi elles pourraient être pertinentes +3. Soumettre la requête et recevoir en retour un prix projeté +4. Trader en fonction de la recommandation +5. Attendre et répéter + +Cet agent montre comment lire des informations, les traduire en une requête qui produit une réponse utilisable, et utiliser cette réponse. Toutes ces étapes sont nécessaires pour un agent IA. Cet agent est implémenté en Python car c'est le langage le plus courant utilisé en IA. + +## Pourquoi faire cela ? {#why-do-this} + +Les agents de trading automatisés permettent aux développeurs de sélectionner et d'exécuter une stratégie de trading. [Les agents IA](/ai-agents) permettent des stratégies de trading plus complexes et dynamiques, en utilisant potentiellement des informations et des algorithmes que le développeur n'a même pas envisagé d'utiliser. + +## Les outils {#tools} + +Ce tutoriel utilise [Python](https://www.python.org/), la [bibliothèque Web3](https://web3py.readthedocs.io/en/stable/), et [Uniswap v3](https://github.com/Uniswap/v3-periphery) pour les cotations et le trading. + +### Pourquoi Python ? {#python} + +Le langage le plus utilisé pour l'IA est [Python](https://www.python.org/), c'est pourquoi nous l'utilisons ici. Ne vous inquiétez pas si vous ne connaissez pas Python. Le langage est très clair, et j'expliquerai exactement ce qu'il fait. + +La [bibliothèque Web3](https://web3py.readthedocs.io/en/stable/) est l'API Python pour Ethereum la plus courante. Elle est assez facile à utiliser. + +### Trader sur la blockchain {#trading-on-blockchain} + +Il existe [de nombreux échanges décentralisés (DEX)](/apps/categories/defi/) qui vous permettent de trader des jetons sur Ethereum. Cependant, ils ont tendance à avoir des taux de change similaires en raison de [l'arbitrage](/developers/docs/smart-contracts/composability/#better-user-experience). + +[Uniswap](https://app.uniswap.org/) est un DEX largement utilisé que nous pouvons utiliser à la fois pour les cotations (pour voir les valeurs relatives des jetons) et pour les transactions. + +### OpenAI {#openai} + +Pour un grand modèle de langage, j'ai choisi de commencer avec [OpenAI](https://openai.com/). Pour exécuter l'application de ce tutoriel, vous devrez payer pour l'accès à l'API. Le paiement minimum de 5 $ est plus que suffisant. + +## Développement, étape par étape {#step-by-step} + +Pour simplifier le développement, nous procédons par étapes. Chaque étape est une branche dans GitHub. + +### Mise en route {#getting-started} + +Voici les étapes pour commencer sous UNIX ou Linux (y compris [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) + +1. Si vous ne l'avez pas déjà, téléchargez et installez [Python](https://www.python.org/downloads/). + +2. Clonez le dépôt GitHub. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. Installez [`uv`](https://docs.astral.sh/uv/getting-started/installation/). La commande sur votre système pourrait être différente. + + ```sh + pipx install uv + ``` + +4. Téléchargez les bibliothèques. + + ```sh + uv sync + ``` + +5. Activez l'environnement virtuel. + + ```sh + source .venv/bin/activate + ``` + +6. Pour vérifier que Python et Web3 fonctionnent correctement, exécutez `python3` et fournissez-lui ce programme. Vous pouvez le saisir à l'invite `>>>` ; il n'est pas nécessaire de créer un fichier. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### Lecture depuis la blockchain {#read-blockchain} + +L'étape suivante consiste à lire les données de la blockchain. Pour ce faire, vous devez passer à la branche `02-read-quote`, puis utiliser `uv` pour exécuter le programme. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +Vous devriez recevoir une liste d'objets `Quote`, chacun avec un horodatage, un prix et l'actif (actuellement toujours `WETH/USDC`). + +Voici une explication ligne par ligne. + +```python +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 +``` + +Importez les bibliothèques dont nous avons besoin. Ils sont expliqués ci-dessous lorsqu'ils sont utilisés. + +```python +print = functools.partial(print, flush=True) +``` + +Remplace le `print` de Python par une version qui vide toujours la sortie immédiatement. Ceci est utile dans un script de longue durée car nous ne voulons pas attendre les mises à jour de statut ou les sorties de débogage. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +Une URL pour accéder au réseau principal. Vous pouvez en obtenir un auprès d'un [nœud en tant que service](/developers/docs/nodes-and-clients/nodes-as-a-service/) ou utiliser l'un de ceux annoncés dans [Chainlist](https://chainlist.org/chain/1). + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +Un bloc du réseau principal Ethereum est généralement créé toutes les douze secondes, il s'agit donc du nombre de blocs que nous nous attendons à voir créés sur une période donnée. Notez que ce chiffre n'est pas exact. Lorsque le [proposeur de bloc](/developers/docs/consensus-mechanisms/pos/block-proposal/) est hors service, ce bloc est sauté, et le temps pour le bloc suivant est de 24 secondes. Si nous voulions obtenir le bloc exact pour un horodatage, nous utiliserions une [recherche binaire](https://en.wikipedia.org/wiki/Binary_search). Cependant, c'est assez proche pour nos besoins. Prédire l'avenir n'est pas une science exacte. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +La taille du cycle. Nous examinons les cotations une fois par cycle et essayons d'estimer la valeur à la fin du cycle suivant. + +```python +# L'adresse du pool que nous lisons +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +Les valeurs de cotation sont extraites du pool Uniswap 3 USDC/WETH à l'adresse [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract). Cette adresse est déjà au format checksum, mais il est préférable d'utiliser [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) pour rendre le code réutilisable. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +Ce sont les [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html) pour les deux contrats que nous devons contacter. Pour que le code reste concis, nous n'incluons que les fonctions que nous devons appeler. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +Initialisez la bibliothèque [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) et connectez-vous à un nœud Ethereum. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +C'est une façon de créer une classe de données en Python. Le type de données [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) est utilisé pour se connecter au contrat. Notez le `(frozen=True)`. En Python, les [booléens](https://en.wikipedia.org/wiki/Boolean_data_type) sont définis comme `True` ou `False`, avec une majuscule. Cette classe de données est `figée` (`frozen`), ce qui signifie que les champs ne peuvent pas être modifiés. + +Notez l'indentation. Contrairement aux [langages dérivés de C](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages), Python utilise l'indentation pour délimiter les blocs. L'interpréteur Python sait que la définition suivante ne fait pas partie de cette classe de données car elle ne commence pas à la même indentation que les champs de la classe de données. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +Le type [`Decimal`](https://docs.python.org/3/library/decimal.html) est utilisé pour gérer avec précision les fractions décimales. + +```python + def get_price(self, block: int) -> Decimal: +``` + +C'est ainsi que l'on définit une fonction en Python. La définition est indentée pour montrer qu'elle fait toujours partie de `PoolInfo`. + +Dans une fonction qui fait partie d'une classe de données, le premier paramètre est toujours `self`, l'instance de la classe de données qui a appelé ici. Ici, il y a un autre paramètre, le numéro de bloc. + +```python + assert block <= w3.eth.block_number, "Le bloc est dans le futur" +``` + +Si nous pouvions lire l'avenir, nous n'aurions pas besoin de l'IA pour le trading. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +La syntaxe pour appeler une fonction sur l'EVM depuis Web3 est la suivante : `.functions.().call()`. Les paramètres peuvent être les paramètres de la fonction EVM (s'il y en a ; ici, il n'y en a pas) ou des [paramètres nommés](https://en.wikipedia.org/wiki/Named_parameter) pour modifier le comportement de la blockchain. Ici, nous en utilisons un, `block_identifier`, pour spécifier [le numéro de bloc](/developers/docs/apis/json-rpc/#default-block) dans lequel nous souhaitons exécuter. + +Le résultat est [cette structure, sous forme de tableau](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). La première valeur est une fonction du taux de change entre les deux jetons. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +Pour réduire les calculs en chaîne, Uniswap v3 ne stocke pas le facteur de change réel mais plutôt sa racine carrée. Comme l'EVM ne prend pas en charge les calculs à virgule flottante ou les fractions, au lieu de la valeur réelle, la réponse est prix296 + +```python + # (jeton1 par jeton0) + return 1/(raw_price * self.decimal_factor) +``` + +Le prix brut que nous obtenons est le nombre de `token0` que nous recevons pour chaque `token1`. Dans notre pool, `token0` est l'USDC (un stablecoin ayant la même valeur qu'un dollar américain) et `token1` est le [WETH](https://opensea.io/learn/blockchain/what-is-weth). La valeur que nous voulons vraiment est le nombre de dollars par WETH, et non l'inverse. + +Le facteur décimal est le rapport entre les [facteurs décimaux](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) pour les deux jetons. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +Cette classe de données représente une cotation : le prix d'un actif spécifique à un moment donné. À ce stade, le champ `asset` n'est pas pertinent car nous utilisons un seul pool et nous n'avons donc qu'un seul actif. Cependant, nous ajouterons d'autres actifs plus tard. + +```python +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 + ) +``` + +Cette fonction prend une adresse et renvoie des informations sur le contrat de jeton à cette adresse. Pour créer un nouveau [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) Web3, nous fournissons l'adresse et l'ABI à `w3.eth.contract`. + +```python +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) + ) +``` + +Cette fonction renvoie tout ce dont nous avons besoin à propos d'[un pool spécifique](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol). La syntaxe `f""` est une [chaîne de caractères formatée](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +Obtenez un objet `Quote`. La valeur par défaut pour `block_number` est `None` (aucune valeur). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +Si un numéro de bloc n'a pas été spécifié, utilisez `w3.eth.block_number`, qui est le dernier numéro de bloc. Voici la syntaxe d'[une instruction `if`](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement). + +On pourrait penser qu'il aurait été préférable de définir la valeur par défaut à `w3.eth.block_number`, mais cela ne fonctionne pas bien car ce serait le numéro de bloc au moment où la fonction est définie. Dans un agent fonctionnant en continu, cela poserait problème. + +```python + 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 + ) +``` + +Utilisez [la bibliothèque `datetime`](https://docs.python.org/3/library/datetime.html) pour la formater dans un format lisible par les humains et les grands modèles de langage (LLM). Utilisez [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) pour arrondir la valeur à deux décimales. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +En Python, vous définissez une [liste](https://docs.python.org/3/library/stdtypes.html#typesseq-list) qui ne peut contenir qu'un type spécifique en utilisant `list[]`. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +En Python, une boucle [`for`](https://docs.python.org/3/tutorial/controlflow.html#for-statements) itère généralement sur une liste. La liste des numéros de bloc dans lesquels trouver des cotations provient de [`range`](https://docs.python.org/3/library/stdtypes.html#range). + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +Pour chaque numéro de bloc, obtenez un objet `Quote` et ajoutez-le à la liste `quotes`. Retournez ensuite cette liste. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +Ceci est le code principal du script. Lisez les informations du pool, obtenez douze cotations et [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint)-les. + +### Créer une invite {#prompt} + +Ensuite, nous devons convertir cette liste de cotations en une invite pour un LLM et obtenir une valeur future attendue. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +La sortie sera désormais une invite pour un LLM, similaire à : + +``` +Compte tenu de ces cotations : +Actif : WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +Actif : WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +Quelle serait, selon vous, la valeur de WETH/USDC à la date 2026-02-02T17:56 ? + +Fournissez votre réponse sous la forme d'un nombre unique arrondi à deux décimales, +sans aucun autre texte. +``` + +Notez qu'il y a ici des cotations pour deux actifs, `WETH/USDC` et `WBTC/WETH`. L'ajout de cotations d'un autre actif pourrait améliorer la précision de la prédiction. + +#### À quoi ressemble une invite {#prompt-explanation} + +Cette invite contient trois sections, qui sont assez courantes dans les invites de LLM. + +1. Informations. Les LLM disposent de beaucoup d'informations provenant de leur entraînement, mais ils n'ont généralement pas les plus récentes. C'est la raison pour laquelle nous devons récupérer les dernières cotations ici. L'ajout d'informations à une invite est appelé [génération augmentée par récupération (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +2. La question proprement dite. C'est ce que nous voulons savoir. + +3. Instructions de formatage de la sortie. Normalement, un LLM nous donnera une estimation avec une explication sur la façon dont il y est parvenu. C'est mieux pour les humains, mais un programme informatique n'a besoin que du résultat final. + +#### Explication du code {#prompt-code} + +Voici le nouveau code. + +```python +from datetime import datetime, timezone, timedelta +``` + +Nous devons fournir au LLM le moment pour lequel nous voulons une estimation. Pour obtenir un temps « n minutes/heures/jours » dans le futur, nous utilisons [la classe `timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta). + +```python +# Les adresses des pools que nous lisons +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +Nous avons deux pools à lire. + +```python +@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 # (token1 per token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +Dans le pool WETH/USDC, nous voulons savoir combien de `token0` (USDC) nous avons besoin pour acheter un `token1` (WETH). Dans le pool WETH/WBTC, nous voulons savoir combien de `token1` (WETH) nous avons besoin pour acheter un `token0` (WBTC, qui est du Bitcoin encapsulé). Nous devons savoir si le ratio du pool doit être inversé. + +```python +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 + ) +``` + +Pour savoir si un pool doit être inversé, nous devons obtenir cela en entrée de `read_pool`. De plus, le symbole de l'actif doit être correctement configuré. + +La syntaxe ` if else ` est l'équivalent Python de l'[opérateur conditionnel ternaire](https://en.wikipedia.org/wiki/Ternary_conditional_operator), qui dans un langage dérivé de C serait ` ? : `. + +```python +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 +``` + +Cette fonction construit une chaîne de caractères qui formate une liste d'objets `Quote`, en supposant qu'ils s'appliquent tous au même actif. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +En Python, les [littéraux de chaînes de caractères sur plusieurs lignes](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) s'écrivent comme `"""` .... `"""`. + +```python +Compte tenu de ces cotations : +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +Ici, nous utilisons le modèle [MapReduce](https://en.wikipedia.org/wiki/MapReduce) pour générer une chaîne de caractères pour chaque liste de cotations avec `format_quotes`, puis nous les réduisons en une seule chaîne de caractères à utiliser dans l'invite. + +```python +Quelle serait la valeur attendue pour {asset} au moment {expected_time} ? + +Fournissez votre réponse sous la forme d'un nombre unique arrondi à deux décimales, +sans aucun autre texte. + """ +``` + +Le reste de l'invite est conforme aux attentes. + +```python +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 +) +``` + +Examinez les deux pools et obtenez des cotations de chacun. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +Déterminez le moment futur pour lequel nous voulons l'estimation, et créez l'invite. + +### Interaction avec un LLM {#interface-llm} + +Ensuite, nous interrogeons un LLM réel et recevons une valeur future attendue. J'ai écrit ce programme en utilisant OpenAI, donc si vous voulez utiliser un autre fournisseur, vous devrez l'ajuster. + +1. Créez un [compte OpenAI](https://auth.openai.com/create-account) + +2. [Approvisionnez le compte](https://platform.openai.com/settings/organization/billing/overview)—le montant minimum au moment de la rédaction est de 5 $ + +3. [Créez une clé d'API](https://platform.openai.com/settings/organization/api-keys) + +4. Dans la ligne de commande, exportez la clé d'API pour que votre programme puisse l'utiliser + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. Récupérez et exécutez l'agent + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +Voici le nouveau code. + +```python +from openai import OpenAI + +open_ai = OpenAI() # Le client lit la variable d'environnement OPENAI_API_KEY +``` + +Importez et instanciez l'API OpenAI. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +Appelez l'API OpenAI (`open_ai.chat.completions.create`) pour créer la réponse. + +```python +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") +``` + +Affichez le prix et fournissez une recommandation d'achat ou de vente. + +#### Tester les prédictions {#testing-the-predictions} + +Maintenant que nous pouvons générer des prédictions, nous pouvons également utiliser des données historiques pour évaluer si nous produisons des prédictions utiles. + +```sh +uv run test-predictor.py +``` + +Le résultat attendu est similaire à : + +``` +Prédiction pour 2026-01-05T19:50 : prédit 3138.93 USD, réel 3218.92 USD, erreur 79.99 USD +Prédiction pour 2026-01-06T19:56 : prédit 3243.39 USD, réel 3221.08 USD, erreur 22.31 USD +Prédiction pour 2026-01-07T20:02 : prédit 3223.24 USD, réel 3146.89 USD, erreur 76.35 USD +Prédiction pour 2026-01-08T20:11 : prédit 3150.47 USD, réel 3092.04 USD, erreur 58.43 USD +. +. +. +Prédiction pour 2026-01-31T22:33 : prédit 2637.73 USD, réel 2417.77 USD, erreur 219.96 USD +Prédiction pour 2026-02-01T22:41 : prédit 2381.70 USD, réel 2318.84 USD, erreur 62.86 USD +Prédiction pour 2026-02-02T22:49 : prédit 2234.91 USD, réel 2349.28 USD, erreur 114.37 USD +Erreur moyenne de prédiction sur 29 prédictions : 83.87103448275862068965517241 USD +Variation moyenne par recommandation : 4.787931034482758620689655172 USD +Variance standard des variations : 104.42 USD +Jours rentables : 51,72% +Jours de perte : 48,28% +``` + +La majeure partie du testeur est identique à l'agent, mais voici les parties qui sont nouvelles ou modifiées. + +```python +CYCLES_FOR_TEST = 40 # Pour le backtest, combien de cycles nous testons + +# Obtenir beaucoup de cotations +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 +) +``` + +Nous regardons en arrière sur `CYCLES_FOR_TEST` (spécifié à 40 ici) jours. + +```python +# Créer des prédictions et les vérifier par rapport à l'historique réel + +total_error = Decimal(0) +changes = [] +``` + +Il y a deux types d'erreurs qui nous intéressent. La première, `total_error`, est simplement la somme des erreurs commises par le prédicteur. + +Pour comprendre la seconde, `changes`, nous devons nous rappeler le but de l'agent. Il ne s'agit pas de prédire le ratio WETH/USDC (prix de l'ETH). Il s'agit d'émettre des recommandations de vente et d'achat. Si le prix est actuellement de 2000 $ et qu'il prédit 2010 $ demain, peu nous importe si le résultat réel est de 2020 $ et que nous gagnons de l'argent supplémentaire. Mais nous nous _soucions_ s'il a prédit 2010 $, et a acheté de l'ETH sur la base de cette recommandation, et que le prix chute à 1990 $. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +Nous ne pouvons examiner que les cas où l'historique complet (les valeurs utilisées pour la prédiction et la valeur réelle pour la comparer) est disponible. Cela signifie que le cas le plus récent doit être celui qui a commencé il y a `CYCLES_BACK`. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +Utilisez des [tranches](https://www.w3schools.com/python/ref_func_slice.asp) pour obtenir le même nombre d'échantillons que celui utilisé par l'agent. Le code entre ici et le segment suivant est le même code d'obtention de prédiction que nous avons dans l'agent. + +```python + 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 +``` + +Obtenez le prix prédit, le prix réel et le prix au moment de la prédiction. Nous avons besoin du prix au moment de la prédiction pour déterminer si la recommandation était d'acheter ou de vendre. + +```python + 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") +``` + +Calculez l'erreur et ajoutez-la au total. + +```python + 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) +``` + +Pour les `changements`, nous voulons l'impact monétaire de l'achat ou de la vente d'un ETH. Donc, d'abord, nous devons déterminer la recommandation, puis évaluer comment le prix réel a changé, et si la recommandation a fait gagner de l'argent (changement positif) ou a coûté de l'argent (changement négatif). + +```python +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") +``` + +Rapportez les résultats. + +```python +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%}") +``` + +Utilisez [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) pour compter le nombre de jours rentables et le nombre de jours coûteux. Le résultat est un objet de filtre, que nous devons convertir en liste pour en obtenir la longueur. + +### Soumettre des transactions {#submit-txn} + +Maintenant, nous devons réellement soumettre des transactions. Cependant, je ne veux pas dépenser de l'argent réel à ce stade, avant que le système ne soit éprouvé. Au lieu de cela, nous allons créer une fourche locale du réseau principal, et « trader » sur ce réseau. + +Voici les étapes pour créer une fourche locale et permettre le trading. + +1. Installez [Foundry](https://getfoundry.sh/introduction/installation) + +2. Démarrez [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` écoute sur l'URL par défaut de Foundry, http://localhost:8545, nous n'avons donc pas besoin de spécifier l'URL pour [la commande `cast`](https://getfoundry.sh/cast/overview) que nous utilisons pour manipuler la blockchain. + +3. Lors de l'exécution dans `anvil`, il y a dix comptes de test qui ont de l'ETH—définissez les variables d'environnement pour le premier + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. Ce sont les contrats que nous devons utiliser. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) est le contrat Uniswap v3 que nous utilisons pour réellement trader. Nous pourrions trader directement via le pool, mais c'est beaucoup plus facile. + + Les deux variables du bas sont les chemins Uniswap v3 requis pour échanger entre le WETH et l'USDC. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. Chacun des comptes de test dispose de 10 000 ETH. Utilisez le contrat WETH pour envelopper 1000 ETH afin d'obtenir 1000 WETH pour le trading. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. Utilisez `SwapRouter` pour échanger 500 WETH contre des USDC. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + L'appel `approve` crée une autorisation qui permet à `SwapRouter` de dépenser certains de nos jetons. Les contrats ne peuvent pas surveiller les événements, donc si nous transférons des jetons directement au contrat `SwapRouter`, il ne saurait pas qu'il a été payé. Au lieu de cela, nous autorisons le contrat `SwapRouter` à dépenser un certain montant, puis `SwapRouter` le fait. Cela se fait par une fonction appelée par `SwapRouter`, afin qu'il sache si cela a réussi. + +7. Vérifiez que vous avez assez des deux jetons. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +Maintenant que nous avons des WETH et des USDC, nous pouvons réellement exécuter l'agent. + +```sh +git checkout 05-trade +uv run agent.py +``` + +La sortie ressemblera à ceci : + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +Prix actuel : 1843.16 +En 2026-02-06T23:07, prix attendu : 1724.41 USD +Soldes du compte avant la transaction : +Solde USDC : 927301.578272 +Solde WETH : 500 +Vendre, je m'attends à ce que le prix baisse de 118.75 USD +Transaction d'approbation envoyée : 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +Transaction d'approbation minée. +Transaction de vente envoyée : fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +Transaction de vente minée. +Soldes du compte après la transaction : +Solde USDC : 929143.797116 +Solde WETH : 499 +``` + +Pour l'utiliser réellement, vous avez besoin de quelques modifications mineures. + +- À la ligne 14, changez `MAINNET_URL` en un point d'accès réel, comme `https://eth.drpc.org` +- À la ligne 28, changez `PRIVATE_KEY` par votre propre clé privée +- À moins que vous ne soyez très riche et que vous puissiez acheter ou vendre 1 ETH chaque jour pour un agent non éprouvé, vous voudrez peut-être modifier 29 pour diminuer `WETH_TRADE_AMOUNT` + +#### Explication du code {#trading-code} + +Voici le nouveau code. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +Les mêmes variables que nous avons utilisées à l'étape 4. + +```python +WETH_TRADE_AMOUNT=1 +``` + +Le montant à trader. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +Pour réellement trader, nous avons besoin de la fonction `approve`. Nous voulons également afficher les soldes avant et après, nous avons donc également besoin de `balanceOf`. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +Dans l'ABI `SwapRouter`, nous n'avons besoin que de `exactInput`. Il existe une fonction connexe, `exactOutput`, que nous pourrions utiliser pour acheter exactement un WETH, mais par souci de simplicité, nous utilisons simplement `exactInput` dans les deux cas. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +Les définitions Web3 pour le [`compte`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) et le contrat `SwapRouter`. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +Les paramètres de la transaction. Nous avons besoin d'une fonction ici car [le nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) doit changer à chaque fois. + +```python +def approve_token(contract: Contract, amount: int): +``` + +Approuvez une autorisation de jeton pour `SwapRouter`. + +```python + 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) +``` + +Voici comment nous envoyons une transaction dans Web3. D'abord, nous utilisons [l'objet `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) pour construire la transaction. Ensuite, nous utilisons [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) pour signer la transaction, en utilisant `PRIVATE_KEY`. Enfin, nous utilisons [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) pour envoyer la transaction. + +```python + 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`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) attend que la transaction soit minée. Il renvoie le reçu si nécessaire. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +Ce sont les paramètres lors de la vente de WETH. + +```python +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, + } +``` + +Contrairement à `SELL_PARAMS`, les paramètres d'achat peuvent changer. Le montant d'entrée est le coût de 1 WETH, tel qu'il est disponible dans la `cotation`. + +```python +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.") +``` + +Les fonctions `buy()` et `sell()` sont presque identiques. D'abord, nous approuvons une autorisation suffisante pour `SwapRouter`, puis nous l'appelons avec le bon chemin et le bon montant. + +```python +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)}") +``` + +Rapportez les soldes des utilisateurs dans les deux devises. + +```python +print("Soldes du compte avant la transaction :") +balances() + +if (expected_price > current_price): + print(f"Acheter, je m'attends à ce que le prix augmente de {expected_price - current_price} USD") + buy(wethusdc_quotes[-1]) +else: + print(f"Vendre, je m'attends à ce que le prix baisse de {current_price - expected_price} USD") + sell() + +print("Soldes du compte après la transaction :") +balances() +``` + +Cet agent ne fonctionne actuellement qu'une seule fois. Cependant, vous pouvez le modifier pour qu'il fonctionne en continu, soit en l'exécutant à partir de [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html), soit en encapsulant les lignes 368-400 dans une boucle et en utilisant [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) pour attendre qu'il soit temps de passer au cycle suivant. + +## Améliorations possibles {#improvements} + +Ce n'est pas une version de production complète ; c'est simplement un exemple pour enseigner les bases. Voici quelques idées d'améliorations. + +### Trading plus intelligent {#smart-trading} + +Il y a deux faits importants que l'agent ignore lorsqu'il décide quoi faire. + +- _L'ampleur du changement anticipé_. L'agent vend un montant fixe de `WETH` si le prix est censé baisser, quelle que soit l'ampleur de la baisse. + On pourrait soutenir qu'il serait préférable d'ignorer les changements mineurs et de vendre en fonction de l'ampleur de la baisse attendue du prix. +- _Le portefeuille actuel_. Si 10 % de votre portefeuille est en WETH et que vous pensez que le prix va augmenter, il est probablement judicieux d'en acheter davantage. Mais si 90 % de votre portefeuille est en WETH, vous êtes peut-être suffisamment exposé, et il n'est pas nécessaire d'en acheter davantage. L'inverse est vrai si vous vous attendez à ce que le prix baisse. + +### Et si vous voulez garder votre stratégie de trading secrète ? {#secret} + +Les fournisseurs d'IA peuvent voir les requêtes que vous envoyez à leurs LLM, ce qui pourrait exposer le système de trading de génie que vous avez développé avec votre agent. Un système de trading que trop de gens utilisent est sans valeur car trop de gens essaient d'acheter quand vous voulez acheter (et le prix monte) et essaient de vendre quand vous voulez vendre (et le prix baisse). + +Vous pouvez exécuter un LLM localement, par exemple en utilisant [LM-Studio](https://lmstudio.ai/), pour éviter ce problème. + +### Du bot IA à l'agent IA {#bot-to-agent} + +On peut affirmer qu'il s'agit [d'un bot IA, et non d'un agent IA](/ai-agents/#ai-agents-vs-ai-bots). Il met en œuvre une stratégie relativement simple qui repose sur des informations prédéfinies. Nous pouvons permettre l'auto-amélioration, par exemple, en fournissant une liste de pools Uniswap v3 et leurs dernières valeurs et en demandant quelle combinaison a la meilleure valeur prédictive. + +### Protection contre le glissement de prix {#slippage-protection} + +Actuellement, il n'y a pas de [protection contre le glissement de prix](https://uniswapv3book.com/milestone_3/slippage-protection.html). Si la cotation actuelle est de 2 000 $ et que le prix attendu est de 2 100 $, l'agent achètera. Cependant, si avant que l'agent n'achète, le coût monte à 2 200 $, il n'est plus logique d'acheter. + +Pour mettre en œuvre une protection contre le glissement de prix, spécifiez une valeur `amountOutMinimum` aux lignes 325 et 334 de [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325). + +## Conclusion {#conclusion} + +Espérons que vous en savez maintenant assez pour commencer avec les agents IA. Ce n'est pas un aperçu complet du sujet ; des livres entiers y sont consacrés, mais c'est suffisant pour vous lancer. Bonne chance ! + +[Voir ici pour plus de mon travail](https://cryptodocguy.pro/). diff --git a/public/content/translations/hi/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/hi/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..05d03d9e26f --- /dev/null +++ b/public/content/translations/hi/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "एथेरियम पर अपना खुद का AI ट्रेडिंग एजेंट बनाएं" +description: "इस ट्यूटोरियल में आप सीखते हैं कि एक सरल AI ट्रेडिंग एजेंट कैसे बनाया जाए। यह एजेंट ब्लॉकचेन से जानकारी पढ़ता है, उस जानकारी के आधार पर LLM से सिफारिश मांगता है, LLM द्वारा अनुशंसित ट्रेड करता है, और फिर इंतजार करता है और दोहराता है।" +author: Ori Pomerantz +tags: [ "AI", "ट्रेडिंग", "एजेंट", "python" ] +skill: intermediate +published: 2026-02-13 +lang: hi +sidebarDepth: 3 +--- + +इस ट्यूटोरियल में आप सीखते हैं कि एक सरल AI ट्रेडिंग एजेंट कैसे बनाया जाए। यह एजेंट इन चरणों का उपयोग करके काम करता है: + +1. एक टोकन की वर्तमान और पिछली कीमतों को पढ़ें, साथ ही अन्य संभावित रूप से प्रासंगिक जानकारी +2. इस जानकारी के साथ एक क्वेरी बनाएं, साथ ही यह समझाने के लिए पृष्ठभूमि की जानकारी दें कि यह कैसे प्रासंगिक हो सकता है +3. क्वेरी सबमिट करें और एक अनुमानित मूल्य वापस प्राप्त करें +4. सिफारिश के आधार पर ट्रेड करें +5. प्रतीक्षा करें और दोहराएं + +यह एजेंट प्रदर्शित करता है कि जानकारी कैसे पढ़ें, इसे एक ऐसी क्वेरी में अनुवाद करें जो एक उपयोगी उत्तर देती है, और उस उत्तर का उपयोग करें। ये सभी एक AI एजेंट के लिए आवश्यक कदम हैं। यह एजेंट Python में लागू किया गया है क्योंकि यह AI में इस्तेमाल होने वाली सबसे आम भाषा है। + +## ऐसा क्यों करें? {#why-do-this} + +स्वचालित ट्रेडिंग एजेंट डेवलपर्स को एक ट्रेडिंग रणनीति चुनने और निष्पादित करने की अनुमति देते हैं। [AI एजेंट](/ai-agents) अधिक जटिल और गतिशील ट्रेडिंग रणनीतियों की अनुमति देते हैं, संभावित रूप से उन सूचनाओं और एल्गोरिदम का उपयोग करते हैं जिनका उपयोग करने पर डेवलपर ने विचार भी नहीं किया है। + +## उपकरण {#tools} + +यह ट्यूटोरियल कोट्स और ट्रेडिंग के लिए [Python](https://www.python.org/), [Web3 लाइब्रेरी](https://web3py.readthedocs.io/en/stable/), और [Uniswap v3](https://github.com/Uniswap/v3-periphery) का उपयोग करता है। + +### Python क्यों? {#python} + +AI के लिए सबसे व्यापक रूप से इस्तेमाल की जाने वाली भाषा [Python](https://www.python.org/) है, इसलिए हम इसे यहाँ इस्तेमाल करते हैं। अगर आप Python नहीं जानते हैं तो चिंता न करें। भाषा बहुत स्पष्ट है, और मैं ठीक से समझाऊंगा कि यह क्या करती है। + +[Web3 लाइब्रेरी](https://web3py.readthedocs.io/en/stable/) सबसे आम Python एथेरियम API है। इसका उपयोग करना बहुत आसान है। + +### ब्लॉकचेन पर ट्रेडिंग {#trading-on-blockchain} + +कई [वितरित एक्सचेंज (DEX)](/apps/categories/defi/) हैं जो आपको एथेरियम पर टोकन ट्रेड करने की सुविधा देते हैं। हालांकि, [आर्बिट्रेज](/developers/docs/smart-contracts/composability/#better-user-experience) के कारण उनकी विनिमय दरें समान होती हैं। + +[Uniswap](https://app.uniswap.org/) एक व्यापक रूप से उपयोग किया जाने वाला DEX है जिसका उपयोग हम कोट्स (टोकन के सापेक्ष मान देखने के लिए) और ट्रेड्स दोनों के लिए कर सकते हैं। + +### OpenAI {#openai} + +एक बड़े भाषा मॉडल के लिए, मैंने [OpenAI](https://openai.com/) के साथ शुरुआत करने का फैसला किया। इस ट्यूटोरियल में एप्लिकेशन को चलाने के लिए आपको API एक्सेस के लिए भुगतान करना होगा। $5 का न्यूनतम भुगतान पर्याप्त से अधिक है। + +## विकास, चरण-दर-चरण {#step-by-step} + +विकास को सरल बनाने के लिए, हम चरणों में आगे बढ़ते हैं। प्रत्येक चरण GitHub में एक शाखा है। + +### शुरुआत करना {#getting-started} + +UNIX या Linux (जिसमें [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) शामिल है) के तहत आरंभ करने के लिए कुछ चरण हैं + +1. यदि आपके पास यह पहले से नहीं है, तो [Python](https://www.python.org/downloads/) डाउनलोड और इंस्टॉल करें। + +2. GitHub रिपॉजिटरी को क्लोन करें। + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. [`uv`](https://docs.astral.sh/uv/getting-started/installation/) इंस्टॉल करें। आपके सिस्टम पर कमांड अलग हो सकता है। + + ```sh + pipx install uv + ``` + +4. लाइब्रेरी डाउनलोड करें। + + ```sh + uv sync + ``` + +5. वर्चुअल एनवायरनमेंट को सक्रिय करें। + + ```sh + source .venv/bin/activate + ``` + +6. यह सत्यापित करने के लिए कि Python और Web3 सही ढंग से काम कर रहे हैं, `python3` चलाएं और इसे यह प्रोग्राम प्रदान करें। आप इसे `>>>` प्रॉम्प्ट पर दर्ज कर सकते हैं; फ़ाइल बनाने की कोई आवश्यकता नहीं है। + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### ब्लॉकचेन से पढ़ना {#read-blockchain} + +अगला कदम ब्लॉकचेन से पढ़ना है। ऐसा करने के लिए, आपको `02-read-quote` शाखा में बदलना होगा और फिर प्रोग्राम चलाने के लिए `uv` का उपयोग करना होगा। + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +आपको `Quote` ऑब्जेक्ट की एक सूची मिलनी चाहिए, प्रत्येक में एक टाइमस्टैम्प, एक मूल्य और एसेट (वर्तमान में हमेशा `WETH/USDC`) होता है। + +यहाँ पंक्ति-दर-पंक्ति स्पष्टीकरण है। + +```python +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 +``` + +हमें जिन पुस्तकालयों की आवश्यकता है, उन्हें आयात करें। उपयोग किए जाने पर उन्हें नीचे समझाया गया है। + +```python +print = functools.partial(print, flush=True) +``` + +Python के `print` को एक ऐसे संस्करण से बदल देता है जो हमेशा आउटपुट को तुरंत फ्लश करता है। यह एक लंबे समय तक चलने वाली स्क्रिप्ट में उपयोगी है क्योंकि हम स्थिति अपडेट या डिबगिंग आउटपुट के लिए इंतजार नहीं करना चाहते हैं। + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +मेननेट तक पहुंचने के लिए एक URL। आप [नोड एज़ ए सर्विस](/developers/docs/nodes-and-clients/nodes-as-a-service/) से एक प्राप्त कर सकते हैं या [चेनलिस्ट](https://chainlist.org/chain/1) में विज्ञापित लोगों में से एक का उपयोग कर सकते हैं। + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +एथेरियम मेननेट ब्लॉक आमतौर पर हर बारह सेकंड में होता है, इसलिए यह उन ब्लॉकों की संख्या है जिनकी हम एक समय अवधि में होने की उम्मीद करते हैं। ध्यान दें कि यह एक सटीक आंकड़ा नहीं है। जब [ब्लॉक प्रस्तावक](/developers/docs/consensus-mechanisms/pos/block-proposal/) डाउन होता है, तो उस ब्लॉक को छोड़ दिया जाता है, और अगले ब्लॉक का समय 24 सेकंड होता है। यदि हम एक टाइमस्टैम्प के लिए सटीक ब्लॉक प्राप्त करना चाहते हैं, तो हम [बाइनरी सर्च](https://en.wikipedia.org/wiki/Binary_search) का उपयोग करेंगे। हालांकि, यह हमारे उद्देश्यों के लिए काफी करीब है। भविष्य की भविष्यवाणी करना कोई सटीक विज्ञान नहीं है। + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +चक्र का आकार। हम प्रति चक्र एक बार कोट्स की समीक्षा करते हैं और अगले चक्र के अंत में मूल्य का अनुमान लगाने का प्रयास करते हैं। + +```python +# उस पूल का पता जिसे हम पढ़ रहे हैं +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +कोट मूल्य Uniswap 3 USDC/WETH पूल से पते [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract) पर लिए गए हैं। यह पता पहले से ही चेकसम प्रारूप में है, लेकिन कोड को पुन: प्रयोज्य बनाने के लिए [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) का उपयोग करना बेहतर है। + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +ये उन दो कॉन्ट्रैक्ट के लिए [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html) हैं जिनसे हमें संपर्क करने की आवश्यकता है। कोड को संक्षिप्त रखने के लिए, हम केवल उन कार्यों को शामिल करते हैं जिन्हें हमें कॉल करने की आवश्यकता है। + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +[`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) लाइब्रेरी शुरू करें और एथेरियम नोड से कनेक्ट करें। + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +यह Python में डेटा क्लास बनाने का एक तरीका है। [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) डेटा प्रकार का उपयोग कॉन्ट्रैक्ट से कनेक्ट करने के लिए किया जाता है। ` (frozen=True)` पर ध्यान दें। Python में [बूलियन](https://en.wikipedia.org/wiki/Boolean_data_type) को `True` या `False`, कैपिटलाइज़्ड के रूप में परिभाषित किया गया है। यह डेटा क्लास `frozen` है, जिसका अर्थ है कि फ़ील्ड को संशोधित नहीं किया जा सकता है। + +इंडेंटेशन पर ध्यान दें। [C-व्युत्पन्न भाषाओं](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages) के विपरीत, Python ब्लॉक को दर्शाने के लिए इंडेंटेशन का उपयोग करता है। Python इंटरप्रेटर जानता है कि निम्नलिखित परिभाषा इस डेटा क्लास का हिस्सा नहीं है क्योंकि यह डेटा क्लास फ़ील्ड के समान इंडेंटेशन पर शुरू नहीं होती है। + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +[`Decimal`](https://docs.python.org/3/library/decimal.html) प्रकार का उपयोग दशमलव भिन्नों को सटीक रूप से संभालने के लिए किया जाता है। + +```python + def get_price(self, block: int) -> Decimal: +``` + +यह Python में फ़ंक्शन को परिभाषित करने का तरीका है। परिभाषा यह दिखाने के लिए इंडेंट की गई है कि यह अभी भी `PoolInfo` का हिस्सा है। + +डेटा क्लास का हिस्सा होने वाले फ़ंक्शन में पहला पैरामीटर हमेशा `self` होता है, जो डेटा क्लास इंस्टेंस है जिसे यहाँ कॉल किया गया है। यहाँ एक और पैरामीटर है, ब्लॉक नंबर। + +```python + assert block <= w3.eth.block_number, "Block is in the future" +``` + +अगर हम भविष्य पढ़ सकते, तो हमें ट्रेडिंग के लिए AI की ज़रूरत नहीं होती। + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +Web3 से EVM पर किसी फ़ंक्शन को कॉल करने का सिंटैक्स यह है: `.functions."().call()`। पैरामीटर EVM फ़ंक्शन के पैरामीटर हो सकते हैं (यदि कोई हो; यहाँ नहीं हैं) या ब्लॉकचेन व्यवहार को संशोधित करने के लिए [नामित पैरामीटर](https://en.wikipedia.org/wiki/Named_parameter) हो सकते हैं। यहाँ हम एक, `block_identifier`, का उपयोग [ब्लॉक नंबर](/developers/docs/apis/json-rpc/#default-block) को निर्दिष्ट करने के लिए करते हैं जिसमें हम चलाना चाहते हैं। + +परिणाम [यह स्ट्रक्ट, ऐरे रूप में](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72) है। पहला मान दो टोकन के बीच विनिमय दर का एक कार्य है। + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +ऑन-चेन गणनाओं को कम करने के लिए, Uniswap v3 वास्तविक विनिमय कारक को संग्रहीत नहीं करता है, बल्कि इसके वर्गमूल को संग्रहीत करता है। क्योंकि EVM फ्लोटिंग पॉइंट गणित या भिन्नों का समर्थन नहीं करता है, वास्तविक मान के बजाय, प्रतिक्रिया price296 है + +```python + # (टोकन1 प्रति टोकन0) + return 1/(raw_price * self.decimal_factor) +``` + +जो कच्चा मूल्य हमें मिलता है, वह प्रत्येक `token1` के लिए मिलने वाले `token0` की संख्या है। हमारे पूल में `token0` USDC (एक अमेरिकी डॉलर के समान मूल्य वाला स्थिर मुद्रा) है और `token1` [WETH](https://opensea.io/learn/blockchain/what-is-weth) है। जो मूल्य हम वास्तव में चाहते हैं वह प्रति WETH डॉलर की संख्या है, न कि इसका व्युत्क्रम। + +दशमलव कारक दो टोकन के [दशमलव कारकों](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) के बीच का अनुपात है। + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +यह डेटा क्लास एक उद्धरण का प्रतिनिधित्व करता है: किसी दिए गए समय पर किसी विशिष्ट संपत्ति की कीमत। इस बिंदु पर, `एसेट` फ़ील्ड अप्रासंगिक है क्योंकि हम एक ही पूल का उपयोग करते हैं और इसलिए हमारे पास एक ही एसेट है। हालांकि, हम बाद में और एसेट्स जोड़ेंगे। + +```python +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 + ) +``` + +यह फ़ंक्शन एक पता लेता है और उस पते पर टोकन अनुबंध के बारे में जानकारी देता है। एक नया [Web3 `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) बनाने के लिए, हम `w3.eth.contract` को पता और ABI प्रदान करते हैं। + +```python +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) + ) +``` + +यह फ़ंक्शन [एक विशिष्ट पूल](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol) के बारे में हमें जो कुछ भी चाहिए वह सब कुछ देता है। सिंटैक्स `f""` एक [फ़ॉर्मेटेड स्ट्रिंग](https://docs.python.org/3/reference/lexical_analysis.html#f-strings) है। + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +`Quote` ऑब्जेक्ट प्राप्त करें। `block_number` का डिफ़ॉल्ट मान `None` (कोई मान नहीं) है। + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +यदि कोई ब्लॉक नंबर निर्दिष्ट नहीं किया गया था, तो `w3.eth.block_number` का उपयोग करें, जो नवीनतम ब्लॉक नंबर है। यह [एक `if` स्टेटमेंट](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement) का सिंटैक्स है। + +ऐसा लग सकता है कि डिफ़ॉल्ट को `w3.eth.block_number` पर सेट करना बेहतर होता, लेकिन यह अच्छी तरह से काम नहीं करता क्योंकि यह फ़ंक्शन को परिभाषित करते समय ब्लॉक नंबर होगा। एक लंबे समय तक चलने वाले एजेंट में, यह एक समस्या होगी। + +```python + 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 + ) +``` + +इसे मनुष्यों और बड़े भाषा मॉडल (LLM) के लिए पठनीय प्रारूप में प्रारूपित करने के लिए [`datetime` लाइब्रेरी](https://docs.python.org/3/library/datetime.html) का उपयोग करें। मान को दो दशमलव स्थानों तक गोल करने के लिए [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) का उपयोग करें। + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +Python में आप एक [सूची](https://docs.python.org/3/library/stdtypes.html#typesseq-list) परिभाषित करते हैं जिसमें `list[]` का उपयोग करके केवल एक विशिष्ट प्रकार हो सकता है। + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +Python में एक [`for` लूप](https://docs.python.org/3/tutorial/controlflow.html#for-statements) आमतौर पर एक सूची पर पुनरावृति करता है। उद्धरणों को खोजने के लिए ब्लॉक नंबरों की सूची [`रेंज`](https://docs.python.org/3/library/stdtypes.html#range) से आती है। + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +प्रत्येक ब्लॉक नंबर के लिए, एक `Quote` ऑब्जेक्ट प्राप्त करें और इसे `quotes` सूची में जोड़ें। फिर उस सूची को वापस करें। + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +यह स्क्रिप्ट का मुख्य कोड है। पूल की जानकारी पढ़ें, बारह उद्धरण प्राप्त करें, और उन्हें [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) करें। + +### एक प्रॉम्प्ट बनाना {#prompt} + +अगला, हमें उद्धरणों की इस सूची को एक एलएलएम के लिए एक संकेत में बदलने और भविष्य के अपेक्षित मूल्य प्राप्त करने की आवश्यकता है। + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +आउटपुट अब LLM के लिए एक संकेत होने जा रहा है, इसके समान: + +``` +इन उद्धरणों को देखते हुए: +एसेट: WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +एसेट: WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +समय 2026-02-02T17:56 पर WETH/USDC का मूल्य क्या होने की उम्मीद करेंगे? + +अपना उत्तर दो दशमलव स्थानों पर गोल की गई एक ही संख्या के रूप में प्रदान करें, +बिना किसी अन्य पाठ के। +``` + +ध्यान दें कि यहां दो संपत्तियों, `WETH/USDC` और `WBTC/WETH` के लिए उद्धरण हैं। किसी अन्य संपत्ति से उद्धरण जोड़ने से भविष्यवाणी की सटीकता में सुधार हो सकता है। + +#### प्रॉम्प्ट कैसा दिखता है {#prompt-explanation} + +इस प्रॉम्प्ट में तीन खंड हैं, जो एलएलएम प्रॉम्प्ट में बहुत आम हैं। + +1. जानकारी। LLMs के पास अपने प्रशिक्षण से बहुत सारी जानकारी होती है, लेकिन उनके पास आमतौर पर नवीनतम जानकारी नहीं होती है। यही कारण है कि हमें यहां नवीनतम उद्धरण प्राप्त करने की आवश्यकता है। प्रॉम्प्ट में जानकारी जोड़ने को [पुनर्प्राप्ति संवर्धित पीढ़ी (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation) कहा जाता है। + +2. वास्तविक प्रश्न। यही हम जानना चाहते हैं। + +3. आउटपुट स्वरूपण निर्देश। आम तौर पर, एक LLM हमें इस बात के स्पष्टीकरण के साथ एक अनुमान देगा कि यह इस पर कैसे पहुंचा। यह मनुष्यों के लिए बेहतर है, लेकिन एक कंप्यूटर प्रोग्राम को केवल बॉटम लाइन की आवश्यकता होती है। + +#### कोड स्पष्टीकरण {#prompt-code} + +यहाँ नया कोड है। + +```python +from datetime import datetime, timezone, timedelta +``` + +हमें LLM को उस समय के बारे में बताने की आवश्यकता है जिसके लिए हम एक अनुमान चाहते हैं। भविष्य में "n मिनट/घंटे/दिन" का समय प्राप्त करने के लिए, हम [`timedelta` वर्ग](https://docs.python.org/3/library/datetime.html#datetime.timedelta) का उपयोग करते हैं। + +```python +# उन पूलों के पते जिन्हें हम पढ़ रहे हैं +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +हमें दो पूल पढ़ने हैं। + +```python +@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 # (token1 per token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +WETH/USDC पूल में, हम जानना चाहते हैं कि `token1` (WETH) में से एक को खरीदने के लिए हमें कितने `token0` (USDC) की आवश्यकता है। WETH/WBTC पूल में, हम जानना चाहते हैं कि एक `token0` (WBTC, जो रैप्ड बिटकॉइन है) खरीदने के लिए हमें कितने `token1` (WETH) की आवश्यकता है। हमें यह ट्रैक करने की आवश्यकता है कि क्या पूल के अनुपात को उलटने की आवश्यकता है। + +```python +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 + ) +``` + +यह जानने के लिए कि क्या किसी पूल को उलटने की आवश्यकता है, हम इसे `read_pool` में इनपुट के रूप में प्राप्त करते हैं। इसके अलावा, संपत्ति प्रतीक को सही ढंग से स्थापित करने की आवश्यकता है। + +वाक्यविन्यास ` if else ` [टर्नेरी कंडीशनल ऑपरेटर](https://en.wikipedia.org/wiki/Ternary_conditional_operator) के पायथन समकक्ष है, जो सी-व्युत्पन्न भाषा में ` ? : ` होगा। + +```python +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 +``` + +यह फ़ंक्शन एक स्ट्रिंग बनाता है जो `Quote` ऑब्जेक्ट की एक सूची को प्रारूपित करता है, यह मानते हुए कि वे सभी एक ही संपत्ति पर लागू होते हैं। + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +Python में [मल्टी-लाइन स्ट्रिंग लिटरल](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) `"""` के रूप में लिखे जाते हैं .... `"""`. + +```python +इन उद्धरणों को देखते हुए: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +यहां, हम प्रत्येक उद्धरण सूची के लिए `format_quotes` के साथ एक स्ट्रिंग उत्पन्न करने के लिए [MapReduce](https://en.wikipedia.org/wiki/MapReduce) पैटर्न का उपयोग करते हैं, फिर उन्हें प्रॉम्प्ट में उपयोग के लिए एक एकल स्ट्रिंग में कम करते हैं। + +```python +{asset} का मान समय {expected_time} पर क्या होने की उम्मीद करेंगे? + +अपना उत्तर दो दशमलव स्थानों पर गोल की गई एक ही संख्या के रूप में प्रदान करें, +बिना किसी अन्य पाठ के। + """ +``` + +प्रॉम्प्ट का बाकी हिस्सा अपेक्षित है। + +```python +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 +) +``` + +दो पूलों की समीक्षा करें और दोनों से उद्धरण प्राप्त करें। + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +भविष्य का समय बिंदु निर्धारित करें जिसके लिए हम अनुमान चाहते हैं, और प्रॉम्प्ट बनाएं। + +### एक LLM के साथ इंटरफेसिंग {#interface-llm} + +अगला, हम एक वास्तविक एलएलएम को संकेत देते हैं और एक अपेक्षित भविष्य मूल्य प्राप्त करते हैं। मैंने यह प्रोग्राम OpenAI का उपयोग करके लिखा है, इसलिए यदि आप किसी भिन्न प्रदाता का उपयोग करना चाहते हैं, तो आपको इसे समायोजित करने की आवश्यकता होगी। + +1. [OpenAI खाता](https://auth.openai.com/create-account) प्राप्त करें + +2. [खाता फंड करें](https://platform.openai.com/settings/organization/billing/overview)—लिखने के समय न्यूनतम राशि $5 है + +3. [API कुंजी बनाएँ](https://platform.openai.com/settings/organization/api-keys) + +4. कमांड लाइन में, एपीआई कुंजी को निर्यात करें ताकि आपका प्रोग्राम इसका उपयोग कर सके + + ```sh + export OPENAI_API_KEY=sk-<बाकी कुंजी यहाँ जाती है> + ``` + +5. एजेंट को चेकआउट करें और चलाएं + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +यहाँ नया कोड है। + +```python +from openai import OpenAI + +open_ai = OpenAI() # क्लाइंट OPENAI_API_KEY पर्यावरण चर पढ़ता है +``` + +OpenAI API को आयात करें और इंस्टैंशिएट करें। + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +प्रतिक्रिया बनाने के लिए OpenAI API (`open_ai.chat.completions.create`) को कॉल करें। + +```python +expected_price = Decimal(response.choices[0].message.content.strip()) +current_price = wethusdc_quotes[-1].price + +print ("वर्तमान मूल्य:", wethusdc_quotes[-1].price) +print(f"भविष्य में {future_time}, अपेक्षित मूल्य: {expected_price} USD") + +if (expected_price > current_price): + print(f"खरीदें, मुझे उम्मीद है कि कीमत {expected_price - current_price} USD तक बढ़ जाएगी") +else: + print(f"बेचें, मुझे उम्मीद है कि कीमत {current_price - expected_price} USD तक गिर जाएगी") +``` + +कीमत का आउटपुट दें और खरीदें या बेचें की सिफारिश करें। + +#### भविष्यवाणियों का परीक्षण {#testing-the-predictions} + +अब जब हम भविष्यवाणियां उत्पन्न कर सकते हैं, तो हम यह आकलन करने के लिए ऐतिहासिक डेटा का भी उपयोग कर सकते हैं कि क्या हम उपयोगी भविष्यवाणियां उत्पन्न करते हैं। + +```sh +uv run test-predictor.py +``` + +अपेक्षित परिणाम इसके समान है: + +``` +2026-01-05T19:50 के लिए भविष्यवाणी: भविष्यवाणी 3138.93 USD, वास्तविक 3218.92 USD, त्रुटि 79.99 USD +2026-01-06T19:56 के लिए भविष्यवाणी: भविष्यवाणी 3243.39 USD, वास्तविक 3221.08 USD, त्रुटि 22.31 USD +2026-01-07T20:02 के लिए भविष्यवाणी: भविष्यवाणी 3223.24 USD, वास्तविक 3146.89 USD, त्रुटि 76.35 USD +2026-01-08T20:11 के लिए भविष्यवाणी: भविष्यवाणी 3150.47 USD, वास्तविक 3092.04 USD, त्रुटि 58.43 USD +. +. +. +2026-01-31T22:33 के लिए भविष्यवाणी: भविष्यवाणी 2637.73 USD, वास्तविक 2417.77 USD, त्रुटि 219.96 USD +2026-02-01T22:41 के लिए भविष्यवाणी: भविष्यवाणी 2381.70 USD, वास्तविक 2318.84 USD, त्रुटि 62.86 USD +2026-02-02T22:49 के लिए भविष्यवाणी: भविष्यवाणी 2234.91 USD, वास्तविक 2349.28 USD, त्रुटि 114.37 USD +29 भविष्यवाणियों पर औसत भविष्यवाणी त्रुटि: 83.87103448275862068965517241 USD +प्रति अनुशंसा औसत परिवर्तन: 4.787931034482758620689655172 USD +परिवर्तनों का मानक विचरण: 104.42 USD +लाभदायक दिन: 51.72% +नुकसान वाले दिन: 48.28% +``` + +परीक्षक का अधिकांश भाग एजेंट के समान है, लेकिन यहां वे हिस्से हैं जो नए या संशोधित हैं। + +```python +CYCLES_FOR_TEST = 40 # बैकटेस्ट के लिए, हम कितने चक्रों का परीक्षण करते हैं + +# बहुत सारे उद्धरण प्राप्त करें +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 +) +``` + +हम `CYCLES_FOR_TEST` (यहाँ 40 के रूप में निर्दिष्ट) दिन पीछे देखते हैं। + +```python +# भविष्यवाणियां बनाएं और उन्हें वास्तविक इतिहास के विरुद्ध जांचें + +total_error = Decimal(0) +changes = [] +``` + +दो प्रकार की त्रुटियां हैं जिनमें हम रुचि रखते हैं। पहला, `टोटल_एरर`, भविष्यवक्ता द्वारा की गई त्रुटियों का योग है। + +दूसरा, `परिवर्तन` को समझने के लिए, हमें एजेंट के उद्देश्य को याद रखने की आवश्यकता है। यह WETH/USDC अनुपात (ETH मूल्य) की भविष्यवाणी करने के लिए नहीं है। यह बेचने और खरीदने की सिफारिशें जारी करने के लिए है। यदि कीमत वर्तमान में $2000 है और यह कल $2010 की भविष्यवाणी करता है, तो हमें कोई आपत्ति नहीं है यदि वास्तविक परिणाम $2020 है और हम अतिरिक्त पैसा कमाते हैं। लेकिन हमें _ आपत्ति है _ यदि इसने $2010 की भविष्यवाणी की, और उस सिफारिश के आधार पर ETH खरीदा, और कीमत $1990 तक गिर जाती है। + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +हम केवल उन मामलों को देख सकते हैं जहां पूरा इतिहास (भविष्यवाणी के लिए उपयोग किए गए मान और वास्तविक-विश्व मान जिसके साथ इसकी तुलना की जानी है) उपलब्ध है। इसका मतलब है कि सबसे नया मामला वह होना चाहिए जो `CYCLES_BACK` पहले शुरू हुआ था। + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +एजेंट द्वारा उपयोग किए जाने वाले नमूनों की संख्या के समान संख्या प्राप्त करने के लिए [स्लाइस](https://www.w3schools.com/python/ref_func_slice.asp) का उपयोग करें। यहां और अगले खंड के बीच का कोड वही गेट-ए-प्रेडिक्शन कोड है जो हमारे पास एजेंट में है। + +```python + 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 +``` + +अनुमानित मूल्य, वास्तविक मूल्य और भविष्यवाणी के समय मूल्य प्राप्त करें। यह निर्धारित करने के लिए कि क्या सिफारिश खरीदने या बेचने की थी, हमें भविष्यवाणी के समय मूल्य की आवश्यकता है। + +```python + error = abs(predicted_price - real_price) + total_error += error + print (f"भविष्यवाणी के लिए {prediction_time}: भविष्यवाणी की गई {predicted_price} USD, वास्तविक {real_price} USD, त्रुटि {error} USD") +``` + +त्रुटि का पता लगाएं, और इसे कुल में जोड़ें। + +```python + 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) +``` + +`बदलाव` के लिए, हम एक ETH खरीदने या बेचने के मौद्रिक प्रभाव चाहते हैं। इसलिए सबसे पहले, हमें सिफारिश निर्धारित करने की आवश्यकता है, फिर यह आकलन करें कि वास्तविक मूल्य कैसे बदला, और क्या सिफारिश ने पैसा बनाया (सकारात्मक परिवर्तन) या पैसे की लागत (नकारात्मक परिवर्तन)। + +```python +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") +``` + +परिणामों की रिपोर्ट करें। + +```python +print (f"लाभदायक दिन: {len(list(filter(lambda x: x > 0, changes)))/length_changes:.2%}") +print (f"नुकसान वाले दिन: {len(list(filter(lambda x: x < 0, changes)))/length_changes:.2%}") +``` + +लाभदायक दिनों की संख्या और महंगे दिनों की संख्या की गणना करने के लिए [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) का उपयोग करें। परिणाम एक फ़िल्टर ऑब्जेक्ट है, जिसे लंबाई प्राप्त करने के लिए हमें एक सूची में बदलने की आवश्यकता है। + +### लेन-देन जमा करना {#submit-txn} + +अब हमें वास्तव में लेन-देन जमा करने की आवश्यकता है। हालांकि, मैं इस समय असली पैसा खर्च नहीं करना चाहता, जब तक कि सिस्टम साबित न हो जाए। इसके बजाय, हम मेननेट का एक स्थानीय फोर्क बनाएंगे, और उस नेटवर्क पर "ट्रेड" करेंगे। + +यहां एक स्थानीय फोर्क बनाने और ट्रेडिंग को सक्षम करने के चरण दिए गए हैं। + +1. [फाउंड्री](https://getfoundry.sh/introduction/installation) इंस्टॉल करें + +2. [`अनविल`](https://getfoundry.sh/anvil/overview) शुरू करें + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `अनविल` फाउंड्री के लिए डिफ़ॉल्ट URL, http://localhost:8545 पर सुन रहा है, इसलिए हमें [ `कास्ट` कमांड](https://getfoundry.sh/cast/overview) के लिए URL निर्दिष्ट करने की आवश्यकता नहीं है जिसका उपयोग हम ब्लॉकचेन में हेरफेर करने के लिए करते हैं। + +3. `अनविल` में चलने पर, दस परीक्षण खाते होते हैं जिनमें ETH होता है - पहले वाले के लिए पर्यावरण चर सेट करें + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. ये वे अनुबंध हैं जिनका हमें उपयोग करने की आवश्यकता है। [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) Uniswap v3 कॉन्ट्रैक्ट है जिसका उपयोग हम वास्तव में व्यापार करने के लिए करते हैं। हम सीधे पूल के माध्यम से व्यापार कर सकते हैं, लेकिन यह बहुत आसान है। + + दो नीचे के चर WETH और USDC के बीच स्वैप करने के लिए आवश्यक Uniswap v3 पथ हैं। + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. प्रत्येक परीक्षण खाते में 10,000 ETH हैं। ट्रेडिंग के लिए 1000 WETH प्राप्त करने के लिए WETH अनुबंध का उपयोग करके 1000 ETH को रैप करें। + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. USDC के लिए 500 WETH का व्यापार करने के लिए `SwapRouter` का उपयोग करें। + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + `अनुमोदन` कॉल एक भत्ता बनाता है जो `SwapRouter` को हमारे कुछ टोकन खर्च करने की अनुमति देता है। कॉन्ट्रैक्ट्स इवेंट्स की निगरानी नहीं कर सकते हैं, इसलिए यदि हम सीधे `SwapRouter` कॉन्ट्रैक्ट में टोकन स्थानांतरित करते हैं, तो उसे पता नहीं चलेगा कि उसे भुगतान किया गया है। इसके बजाय, हम `SwapRouter` कॉन्ट्रैक्ट को एक निश्चित राशि खर्च करने की अनुमति देते हैं, और फिर `SwapRouter` ऐसा करता है। यह `SwapRouter` द्वारा बुलाए गए फ़ंक्शन के माध्यम से किया जाता है, इसलिए यह जानता है कि यह सफल रहा या नहीं। + +7. सत्यापित करें कि आपके पास दोनों टोकन पर्याप्त हैं। + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +अब जब हमारे पास WETH और USDC हैं, तो हम वास्तव में एजेंट चला सकते हैं। + +```sh +git checkout 05-trade +uv run agent.py +``` + +आउटपुट इसके समान दिखेगा: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +वर्तमान मूल्य: 1843.16 +2026-02-06T23:07 में, अपेक्षित मूल्य: 1724.41 USD +ट्रेड से पहले खाते की शेष राशि: +USDC शेष: 927301.578272 +WETH शेष: 500 +बेचें, मुझे उम्मीद है कि कीमत 118.75 USD तक गिर जाएगी +अनुमोदन लेनदेन भेजा गया: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +अनुमोदन लेनदेन खनन किया गया। +विक्रय लेनदेन भेजा गया: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +विक्रय लेनदेन खनन किया गया। +व्यापार के बाद खाता शेष: +USDC शेष: 929143.797116 +WETH शेष: 499 +``` + +वास्तव में इसका उपयोग करने के लिए, आपको कुछ छोटे बदलावों की आवश्यकता है। + +- लाइन 14 में, `MAINNET_URL` को वास्तविक एक्सेस प्वाइंट में बदलें, जैसे `https://eth.drpc.org` +- लाइन 28 में, `PRIVATE_KEY` को अपनी निजी चाबी में बदलें +- जब तक आप बहुत अमीर नहीं हैं और एक अप्रमाणित एजेंट के लिए हर दिन 1 ETH खरीद या बेच सकते हैं, तब तक आप `WETH_TRADE_AMOUNT` को कम करने के लिए 29 को बदलना चाह सकते हैं। + +#### कोड स्पष्टीकरण {#trading-code} + +यहाँ नया कोड है। + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +वही चर जो हमने चरण 4 में उपयोग किए थे। + +```python +WETH_TRADE_AMOUNT=1 +``` + +ट्रेड करने की राशि। + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +वास्तव में व्यापार करने के लिए, हमें `अनुमोदन` फ़ंक्शन की आवश्यकता है। हम पहले और बाद में शेष राशि भी दिखाना चाहते हैं, इसलिए हमें `बैलेंसऑफ़` की भी आवश्यकता है। + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +`SwapRouter` ABI में हमें बस `exactInput` की आवश्यकता है। एक संबंधित फ़ंक्शन है, `exactOutput`, जिसका उपयोग हम ठीक एक WETH खरीदने के लिए कर सकते हैं, लेकिन सरलता के लिए हम दोनों मामलों में `exactInput` का उपयोग करते हैं। + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +[`खाता`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) और `SwapRouter` अनुबंध के लिए Web3 परिभाषाएँ। + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +लेन-देन के पैरामीटर। हमें यहां एक फ़ंक्शन की आवश्यकता है क्योंकि [नोंस](https://en.wikipedia.org/wiki/Cryptographic_nonce) हर बार बदलना चाहिए। + +```python +def approve_token(contract: Contract, amount: int): +``` + +`SwapRouter` के लिए एक टोकन भत्ते को मंजूरी दें। + +```python + txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params()) + signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY) + tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction) +``` + +यह है कि हम Web3 में एक लेनदेन कैसे भेजते हैं। सबसे पहले हम लेन-देन बनाने के लिए [`कॉन्ट्रैक्ट` ऑब्जेक्ट](https://web3py.readthedocs.io/en/stable/web3.contract.html) का उपयोग करते हैं। फिर हम लेन-देन पर हस्ताक्षर करने के लिए [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) का उपयोग करते हैं, `PRIVATE_KEY` का उपयोग करके। अंत में, हम लेन-देन भेजने के लिए [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) का उपयोग करते हैं। + +```python + print(f"अनुमोदन लेनदेन भेजा गया: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("अनुमोदन लेनदेन खनन किया गया।") +``` + +[`w3.eth.wait_for_transaction_receipt`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) तब तक प्रतीक्षा करता है जब तक कि लेनदेन खनन न हो जाए। यदि आवश्यक हो तो यह रसीद लौटाता है। + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +ये WETH बेचते समय के पैरामीटर हैं। + +```python +def make_buy_params(quote: Quote) -> dict: + return { + "path": USDC_TO_WETH, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals, + "amountOutMinimum": 0, + } +``` + +`SELL_PARAMS` के विपरीत, खरीद पैरामीटर बदल सकते हैं। इनपुट राशि 1 WETH की लागत है, जैसा कि `उद्धरण` में उपलब्ध है। + +```python +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.") +``` + +`buy()` और `sell()` फ़ंक्शन लगभग समान हैं। पहले हम `SwapRouter` के लिए पर्याप्त भत्ते को मंजूरी देते हैं, और फिर हम इसे सही पथ और राशि के साथ बुलाते हैं। + +```python +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)}") +``` + +दोनों मुद्राओं में यूज़र शेष राशि की रिपोर्ट करें। + +```python +print("ट्रेड से पहले खाते की शेष राशि:") +शेष राशि() + +if (expected_price > current_price): + print(f"खरीदें, मुझे उम्मीद है कि कीमत {expected_price - current_price} USD तक बढ़ जाएगी") + buy(wethusdc_quotes[-1]) +else: + print(f"बेचें, मुझे उम्मीद है कि कीमत {current_price - expected_price} USD तक गिर जाएगी") + sell() + +print("ट्रेड के बाद खाते की शेष राशि:") +balances() +``` + +यह एजेंट वर्तमान में केवल एक बार काम करता है। हालांकि, आप इसे [`क्रॉन्टैब`](https://man7.org/linux/man-pages/man1/crontab.1.html) से चलाकर या लूप में 368-400 लाइनों को लपेटकर और अगले चक्र के लिए समय होने तक प्रतीक्षा करने के लिए [`टाइम.स्लीप`](https://docs.python.org/3/library/time.html#time.sleep) का उपयोग करके इसे लगातार काम करने के लिए बदल सकते हैं। + +## संभावित सुधार {#improvements} + +यह एक पूर्ण उत्पादन संस्करण नहीं है; यह केवल मूल बातें सिखाने के लिए एक उदाहरण है। यहां सुधार के लिए कुछ विचार दिए गए हैं। + +### स्मार्ट ट्रेडिंग {#smart-trading} + +दो महत्वपूर्ण तथ्य हैं जिन्हें एजेंट क्या करना है यह तय करते समय अनदेखा करता है। + +- _प्रत्याशित परिवर्तन की भयावहता_। एजेंट `WETH` की एक निश्चित राशि बेचता है यदि कीमत में गिरावट की उम्मीद है, गिरावट की भयावहता की परवाह किए बिना। + तर्कसंगत रूप से, मामूली बदलावों को अनदेखा करना और कीमत में गिरावट की उम्मीद के आधार पर बेचना बेहतर होगा। +- _वर्तमान पोर्टफोलियो_। यदि आपके पोर्टफोलियो का 10% WETH में है और आपको लगता है कि कीमत बढ़ेगी, तो शायद अधिक खरीदना समझ में आता है। लेकिन अगर आपके पोर्टफोलियो का 90% WETH में है, तो आप पर्याप्त रूप से उजागर हो सकते हैं, और अधिक खरीदने की कोई आवश्यकता नहीं है। यदि आप कीमत में गिरावट की उम्मीद करते हैं तो विपरीत सच है। + +### क्या होगा यदि आप अपनी ट्रेडिंग रणनीति को गुप्त रखना चाहते हैं? {#secret} + +AI विक्रेता आपके द्वारा उनके LLMs को भेजी गई क्वेरी देख सकते हैं, जो आपके एजेंट के साथ विकसित की गई जीनियस ट्रेडिंग प्रणाली को उजागर कर सकती है। एक ट्रेडिंग सिस्टम जिसका बहुत से लोग उपयोग करते हैं वह बेकार है क्योंकि बहुत से लोग तब खरीदने की कोशिश करते हैं जब आप खरीदना चाहते हैं (और कीमत बढ़ जाती है) और तब बेचने की कोशिश करते हैं जब आप बेचना चाहते हैं (और कीमत गिर जाती है)। + +आप इस समस्या से बचने के लिए स्थानीय रूप से एक LLM चला सकते हैं, उदाहरण के लिए, [LM-Studio](https://lmstudio.ai/) का उपयोग करके। + +### AI बॉट से AI एजेंट तक {#bot-to-agent} + +आप एक अच्छा मामला बना सकते हैं कि यह [एक AI बॉट है, AI एजेंट नहीं](/ai-agents/#ai-agents-vs-ai-bots)। यह एक अपेक्षाकृत सरल रणनीति लागू करता है जो पूर्वनिर्धारित जानकारी पर निर्भर करती है। हम स्व-सुधार को सक्षम कर सकते हैं, उदाहरण के लिए, Uniswap v3 पूलों की एक सूची और उनके नवीनतम मूल्यों को प्रदान करके और यह पूछकर कि किस संयोजन का सबसे अच्छा पूर्वानुमान मूल्य है। + +### स्लिपेज सुरक्षा {#slippage-protection} + +वर्तमान में कोई [स्लिपेज सुरक्षा](https://uniswapv3book.com/milestone_3/slippage-protection.html) नहीं है। यदि वर्तमान उद्धरण $2000 है, और अपेक्षित मूल्य $2100 है, तो एजेंट खरीदेगा। हालांकि, यदि एजेंट खरीदने से पहले लागत $2200 तक बढ़ जाती है, तो अब और खरीदने का कोई मतलब नहीं है। + +स्लिपेज सुरक्षा को लागू करने के लिए, [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325) की पंक्तियों 325 और 334 में `amountOutMinimum` मान निर्दिष्ट करें। + +## निष्कर्ष {#conclusion} + +उम्मीद है, अब आप AI एजेंटों के साथ शुरुआत करने के लिए पर्याप्त जानते हैं। यह विषय का एक व्यापक अवलोकन नहीं है; इसके लिए पूरी किताबें समर्पित हैं, लेकिन यह आपको शुरू करने के लिए पर्याप्त है। शुभकामनाएँ! + +[मेरे और काम के लिए यहाँ देखें](https://cryptodocguy.pro/)। diff --git a/public/content/translations/id/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/id/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..a1cc717596f --- /dev/null +++ b/public/content/translations/id/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: Buat agen perdagangan AI Anda sendiri di Ethereum +description: Dalam tutorial ini Anda akan belajar cara membuat agen perdagangan AI sederhana. Agen ini membaca informasi dari rantai blok, meminta rekomendasi dari LLM berdasarkan informasi tersebut, melakukan perdagangan yang direkomendasikan LLM, lalu menunggu dan mengulangi. +author: Ori Pomerantz +tags: [ "AI", "perdagangan", "agen", "python" ] +skill: intermediate +published: 2026-02-13 +lang: id +sidebarDepth: 3 +--- + +Dalam tutorial ini Anda akan belajar cara membangun agen perdagangan AI sederhana. Agen ini bekerja menggunakan langkah-langkah berikut: + +1. Baca harga token saat ini dan yang lalu, serta informasi lain yang berpotensi relevan +2. Buat kueri dengan informasi ini, beserta informasi latar belakang untuk menjelaskan bagaimana informasi tersebut mungkin relevan +3. Kirimkan kueri dan terima kembali harga yang diproyeksikan +4. Perdagangkan berdasarkan rekomendasi +5. Tunggu dan ulangi + +Agen ini menunjukkan cara membaca informasi, menerjemahkannya ke dalam kueri yang menghasilkan jawaban yang dapat digunakan, dan menggunakan jawaban tersebut. Semua ini adalah langkah-langkah yang diperlukan untuk agen AI. Agen ini diimplementasikan dalam Python karena ini adalah bahasa yang paling umum digunakan dalam AI. + +## Mengapa melakukan ini? {#why-do-this} + +Agen perdagangan otomatis memungkinkan pengembang untuk memilih dan menjalankan strategi perdagangan. [Agen AI](/ai-agents) memungkinkan strategi perdagangan yang lebih kompleks dan dinamis, berpotensi menggunakan informasi dan algoritma yang bahkan belum dipertimbangkan oleh pengembang untuk digunakan. + +## Perangkat {#tools} + +Tutorial ini menggunakan [Python](https://www.python.org/), [pustaka Web3](https://web3py.readthedocs.io/en/stable/), dan [Uniswap v3](https://github.com/Uniswap/v3-periphery) untuk kuotasi dan perdagangan. + +### Mengapa Python? {#python} + +Bahasa yang paling banyak digunakan untuk AI adalah [Python](https://www.python.org/), jadi kami menggunakannya di sini. Jangan khawatir jika Anda tidak tahu Python. Bahasa ini sangat jelas, dan saya akan menjelaskan dengan tepat apa yang dilakukannya. + +[Pustaka Web3](https://web3py.readthedocs.io/en/stable/) adalah API Python Ethereum yang paling umum. Cukup mudah digunakan. + +### Berdagang di rantai blok {#trading-on-blockchain} + +Ada [banyak bursa terdesentralisasi (DEX)](/apps/categories/defi/) yang memungkinkan Anda memperdagangkan token di Ethereum. Namun, mereka cenderung memiliki nilai tukar yang serupa karena [arbitrase](/developers/docs/smart-contracts/composability/#better-user-experience). + +[Uniswap](https://app.uniswap.org/) adalah DEX yang banyak digunakan yang dapat kita gunakan untuk kuotasi (untuk melihat nilai relatif token) dan perdagangan. + +### OpenAI {#openai} + +Untuk model bahasa besar, saya memilih untuk memulai dengan [OpenAI](https://openai.com/). Untuk menjalankan aplikasi dalam tutorial ini, Anda harus membayar untuk akses API. Pembayaran minimum sebesar $5 sudah lebih dari cukup. + +## Pengembangan, langkah demi langkah {#step-by-step} + +Untuk menyederhanakan pengembangan, kita akan melanjutkannya secara bertahap. Setiap langkah adalah sebuah cabang di GitHub. + +### Memulai {#getting-started} + +Ada langkah-langkah untuk memulai di bawah UNIX atau Linux (termasuk [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) + +1. Jika Anda belum memilikinya, unduh dan instal [Python](https://www.python.org/downloads/). + +2. Kloning repositori GitHub. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. Instal [`uv`](https://docs.astral.sh/uv/getting-started/installation/). Perintah di sistem Anda mungkin berbeda. + + ```sh + pipx install uv + ``` + +4. Unduh pustaka. + + ```sh + uv sync + ``` + +5. Aktifkan lingkungan virtual. + + ```sh + source .venv/bin/activate + ``` + +6. Untuk memverifikasi Python dan Web3 berfungsi dengan benar, jalankan `python3` dan berikan program ini. Anda dapat memasukkannya di prompt `>>>`; tidak perlu membuat file. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### Membaca dari rantai blok {#read-blockchain} + +Langkah selanjutnya adalah membaca dari rantai blok. Untuk melakukannya, Anda perlu beralih ke cabang `02-read-quote` lalu menggunakan `uv` untuk menjalankan program. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +Anda akan menerima daftar objek `Quote`, masing-masing dengan stempel waktu, harga, dan aset (saat ini selalu `WETH/USDC`). + +Berikut adalah penjelasan baris demi baris. + +```python +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 +``` + +Impor pustaka yang kita butuhkan. Mereka dijelaskan di bawah ini saat digunakan. + +```python +print = functools.partial(print, flush=True) +``` + +Mengganti `print` Python dengan versi yang selalu membersihkan keluaran dengan segera. Ini berguna dalam skrip yang berjalan lama karena kita tidak ingin menunggu pembaruan status atau keluaran debugging. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +Sebuah URL untuk menuju ke Jaringan Utama. Anda bisa mendapatkan satu dari [Simpul sebagai layanan](/developers/docs/nodes-and-clients/nodes-as-a-service/) atau menggunakan salah satu yang diiklankan di [Chainlist](https://chainlist.org/chain/1). + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +Sebuah blok Jaringan Utama Ethereum biasanya terjadi setiap dua belas detik, jadi ini adalah jumlah blok yang kita harapkan terjadi dalam suatu periode waktu. Perhatikan bahwa ini bukan angka yang pasti. Ketika [pengusul blok](/developers/docs/consensus-mechanisms/pos/block-proposal/) tidak berfungsi, blok tersebut dilewati, dan waktu untuk blok berikutnya adalah 24 detik. Jika kita ingin mendapatkan blok yang tepat untuk stempel waktu, kita akan menggunakan [pencarian biner](https://en.wikipedia.org/wiki/Binary_search). Namun, ini sudah cukup dekat untuk tujuan kita. Memprediksi masa depan bukanlah ilmu pasti. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +Ukuran siklus. Kami meninjau kuotasi sekali per siklus dan mencoba memperkirakan nilai di akhir siklus berikutnya. + +```python +# Alamat pool yang kita baca +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +Nilai kuotasi diambil dari pool Uniswap 3 USDC/WETH di alamat [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract). Alamat ini sudah dalam bentuk checksum, tetapi lebih baik menggunakan [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) untuk membuat kode dapat digunakan kembali. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +Ini adalah [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html) untuk dua kontrak yang perlu kita hubungi. Untuk menjaga agar kode tetap ringkas, kami hanya menyertakan fungsi yang perlu kami panggil. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +Mulai pustaka [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) dan sambungkan ke simpul Ethereum. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +Ini adalah salah satu cara untuk membuat kelas data di Python. Tipe data [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) digunakan untuk terhubung ke kontrak. Perhatikan `(frozen=True)`. Dalam Python, [boolean](https://en.wikipedia.org/wiki/Boolean_data_type) didefinisikan sebagai `True` atau `False`, dengan huruf besar. Kelas data ini `beku` (frozen), artinya bidang-bidangnya tidak dapat dimodifikasi. + +Perhatikan indentasi. Berbeda dengan [bahasa turunan C](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages), Python menggunakan indentasi untuk menunjukkan blok. Interpreter Python tahu bahwa definisi berikut bukan bagian dari kelas data ini karena tidak dimulai pada indentasi yang sama dengan bidang kelas data. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +Tipe [`Decimal`](https://docs.python.org/3/library/decimal.html) digunakan untuk menangani pecahan desimal secara akurat. + +```python + def get_price(self, block: int) -> Decimal: +``` + +Ini adalah cara untuk mendefinisikan sebuah fungsi di Python. Definisi tersebut diberi indentasi untuk menunjukkan bahwa ia masih merupakan bagian dari `PoolInfo`. + +Dalam sebuah fungsi yang merupakan bagian dari kelas data, parameter pertama selalu `self`, yaitu instance kelas data yang memanggilnya. Di sini ada parameter lain, yaitu nomor blok. + +```python + assert block <= w3.eth.block_number, "Blok ada di masa depan" +``` + +Jika kita bisa membaca masa depan, kita tidak akan memerlukan AI untuk berdagang. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +Sintaks untuk memanggil fungsi di EVM dari Web3 adalah ini: `.functions.().call(). Parameter dapat berupa parameter fungsi EVM (jika ada; di sini tidak ada) atau [parameter bernama](https://en.wikipedia.org/wiki/Named_parameter) untuk mengubah perilaku rantai blok. Di sini kita menggunakan satu, `block_identifier`, untuk menentukan [nomor blok](/developers/docs/apis/json-rpc/#default-block) yang ingin kita jalankan. + +Hasilnya adalah [struct ini, dalam bentuk array](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). Nilai pertama adalah fungsi dari nilai tukar antara dua token. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +Untuk mengurangi kalkulasi di dalam rantai, Uniswap v3 tidak menyimpan faktor pertukaran aktual tetapi akar kuadratnya. Karena EVM tidak mendukung matematika titik mengambang atau pecahan, alih-alih nilai aktual, responsnya adalah harga296 + +```python + # (token1 per token0) + return 1/(raw_price * self.decimal_factor) +``` + +Harga mentah yang kita dapatkan adalah jumlah `token0` yang kita peroleh untuk setiap `token1`. Di pool kami, `token0` adalah USDC (Koin Stabil dengan nilai yang sama dengan dolar AS) dan `token1` adalah [WETH](https://opensea.io/learn/blockchain/what-is-weth). Nilai yang sebenarnya kita inginkan adalah jumlah dolar per WETH, bukan kebalikannya. + +Faktor desimal adalah rasio antara [faktor desimal](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) untuk kedua token tersebut. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +Kelas data ini mewakili sebuah kuotasi: harga aset tertentu pada titik waktu tertentu. Pada titik ini, bidang `asset` tidak relevan karena kita menggunakan satu pool dan oleh karena itu hanya memiliki satu aset. Namun, kami akan menambahkan lebih banyak aset nanti. + +```python +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 + ) +``` + +Fungsi ini mengambil sebuah alamat dan mengembalikan informasi tentang kontrak token di alamat tersebut. Untuk membuat [Kontrak Web3](https://web3py.readthedocs.io/en/stable/web3.contract.html) yang baru, kita memberikan alamat dan ABI ke `w3.eth.contract`. + +```python +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) + ) +``` + +Fungsi ini mengembalikan semua yang kita butuhkan tentang [pool tertentu](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol). Sintaks `f""` adalah [string yang diformat](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +Dapatkan objek `Quote`. Nilai default untuk `block_number` adalah `None` (tidak ada nilai). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +Jika nomor blok tidak ditentukan, gunakan `w3.eth.block_number`, yang merupakan nomor blok terbaru. Ini adalah sintaks untuk [pernyataan `if`](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement). + +Mungkin terlihat seolah-olah akan lebih baik jika hanya mengatur default ke `w3.eth.block_number`, tetapi itu tidak bekerja dengan baik karena itu akan menjadi nomor blok pada saat fungsi didefinisikan. Dalam agen yang berjalan lama, ini akan menjadi masalah. + +```python + 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 + ) +``` + +Gunakan [pustaka `datetime`](https://docs.python.org/3/library/datetime.html) untuk memformatnya ke format yang dapat dibaca oleh manusia dan model bahasa besar (LLM). Gunakan [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) untuk membulatkan nilai ke dua tempat desimal. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +Di Python Anda mendefinisikan sebuah [daftar](https://docs.python.org/3/library/stdtypes.html#typesseq-list) yang hanya dapat berisi tipe tertentu menggunakan `list[]`. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +Di Python sebuah [`for` loop](https://docs.python.org/3/tutorial/controlflow.html#for-statements) biasanya melakukan iterasi pada sebuah daftar. Daftar nomor blok untuk menemukan kutipan berasal dari [`range`](https://docs.python.org/3/library/stdtypes.html#range). + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +Untuk setiap nomor blok, dapatkan objek `Quote` dan tambahkan ke daftar `quotes`. Kemudian kembalikan daftar itu. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +Ini adalah kode utama dari skrip. Baca informasi pool, dapatkan dua belas kutipan, dan [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) mereka. + +### Membuat prompt {#prompt} + +Selanjutnya, kita perlu mengubah daftar kutipan ini menjadi sebuah prompt untuk LLM dan mendapatkan nilai masa depan yang diharapkan. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +Outputnya sekarang akan menjadi prompt untuk LLM, mirip dengan: + +``` +Mengingat kutipan ini: +Aset: WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +Aset: WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +Berapa nilai yang Anda harapkan untuk WETH/USDC pada waktu 2026-02-02T17:56? + +Berikan jawaban Anda sebagai satu angka yang dibulatkan ke dua tempat desimal, +tanpa teks lain. +``` + +Perhatikan bahwa ada kutipan untuk dua aset di sini, `WETH/USDC` dan `WBTC/WETH`. Menambahkan kutipan dari aset lain mungkin meningkatkan akurasi prediksi. + +#### Seperti apa bentuk sebuah prompt {#prompt-explanation} + +Prompt ini berisi tiga bagian, yang cukup umum dalam prompt LLM. + +1. Informasi. LLM memiliki banyak informasi dari pelatihan mereka, tetapi biasanya mereka tidak memiliki yang terbaru. Inilah alasan kita perlu mengambil kutipan terbaru di sini. Menambahkan informasi ke prompt disebut [retrieval augmented generation (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +2. Pertanyaan sebenarnya. Inilah yang ingin kita ketahui. + +3. Instruksi pemformatan output. Biasanya, LLM akan memberi kita perkiraan dengan penjelasan tentang bagaimana ia sampai pada perkiraan tersebut. Ini lebih baik untuk manusia, tetapi program komputer hanya membutuhkan hasil akhirnya. + +#### Penjelasan kode {#prompt-code} + +Berikut adalah kode barunya. + +```python +from datetime import datetime, timezone, timedelta +``` + +Kita perlu memberikan LLM waktu yang kita inginkan untuk perkiraan. Untuk mendapatkan waktu "n menit/jam/hari" di masa depan, kita menggunakan [kelas `timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta). + +```python +# Alamat pool yang kita baca +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +Kita memiliki dua pool yang perlu dibaca. + +```python +@dataclass(frozen=True) +class PoolInfo: + . + . + . + reverse: bool = False + + def get_price(self, block: int) -> Decimal: + assert block <= w3.eth.block_number, "Blok ada di masa depan" + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (token1 per token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +Di pool WETH/USDC, kami ingin tahu berapa banyak `token0` (USDC) yang kami butuhkan untuk membeli satu `token1` (WETH). Di pool WETH/WBTC, kami ingin tahu berapa banyak `token1` (WETH) yang kami butuhkan untuk membeli satu `token0` (WBTC, yaitu Bitcoin terbungkus). Kita perlu melacak apakah rasio pool perlu dibalik. + +```python +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 + ) +``` + +Untuk mengetahui apakah sebuah pool perlu dibalik, kita harus mendapatkannya sebagai input untuk `read_pool`. Selain itu, simbol aset perlu diatur dengan benar. + +Sintaks ` if else ` adalah padanan Python dari [operator kondisional terner](https://en.wikipedia.org/wiki/Ternary_conditional_operator), yang dalam bahasa turunan C adalah ` ? : `. + +```python +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 +``` + +Fungsi ini membangun sebuah string yang memformat daftar objek `Quote`, dengan asumsi semuanya berlaku untuk aset yang sama. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +Di Python, [literal string multibaris](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) ditulis sebagai `"""` .... `"""`. + +```python +Mengingat kutipan ini: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +Di sini, kami menggunakan pola [MapReduce](https://en.wikipedia.org/wiki/MapReduce) untuk menghasilkan string untuk setiap daftar kutipan dengan `format_quotes`, lalu mereduksinya menjadi satu string untuk digunakan dalam prompt. + +```python +Berapa nilai yang Anda harapkan untuk {asset} pada waktu {expected_time}? + +Berikan jawaban Anda sebagai satu angka yang dibulatkan ke dua tempat desimal, +tanpa teks lain. + """ +``` + +Sisa dari prompt adalah seperti yang diharapkan. + +```python +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 +) +``` + +Tinjau kedua pool dan dapatkan kuotasi dari keduanya. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +Tentukan titik waktu masa depan yang kita inginkan estimasinya, dan buat prompt. + +### Berinteraksi dengan LLM {#interface-llm} + +Selanjutnya, kita akan meminta LLM yang sebenarnya dan menerima nilai masa depan yang diharapkan. Saya menulis program ini menggunakan OpenAI, jadi jika Anda ingin menggunakan penyedia yang berbeda, Anda harus menyesuaikannya. + +1. Dapatkan [akun OpenAI](https://auth.openai.com/create-account) + +2. [Danai akun](https://platform.openai.com/settings/organization/billing/overview)—jumlah minimum pada saat penulisan adalah $5 + +3. [Buat kunci API](https://platform.openai.com/settings/organization/api-keys) + +4. Di baris perintah, ekspor kunci API agar program Anda dapat menggunakannya + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. Checkout dan jalankan agen + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +Berikut adalah kode barunya. + +```python +from openai import OpenAI + +open_ai = OpenAI() # Klien membaca variabel lingkungan OPENAI_API_KEY +``` + +Impor dan buat instance API OpenAI. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +Panggil API OpenAI (`open_ai.chat.completions.create`) untuk membuat respons. + +```python +expected_price = Decimal(response.choices[0].message.content.strip()) +current_price = wethusdc_quotes[-1].price + +print ("Harga saat ini:", wethusdc_quotes[-1].price) +print(f"Pada {future_time}, harga yang diharapkan: {expected_price} USD") + +if (expected_price > current_price): + print(f"Beli, saya perkirakan harga akan naik sebesar {expected_price - current_price} USD") +else: + print(f"Jual, saya perkirakan harga akan turun sebesar {current_price - expected_price} USD") +``` + +Keluarkan harga dan berikan rekomendasi beli atau jual. + +#### Menguji prediksi {#testing-the-predictions} + +Sekarang setelah kita dapat menghasilkan prediksi, kita juga dapat menggunakan data historis untuk menilai apakah kita menghasilkan prediksi yang berguna. + +```sh +uv run test-predictor.py +``` + +Hasil yang diharapkan serupa dengan: + +``` +Prediksi untuk 2026-01-05T19:50: diprediksi 3138.93 USD, riil 3218.92 USD, kesalahan 79.99 USD +Prediksi untuk 2026-01-06T19:56: diprediksi 3243.39 USD, riil 3221.08 USD, kesalahan 22.31 USD +Prediksi untuk 2026-01-07T20:02: diprediksi 3223.24 USD, riil 3146.89 USD, kesalahan 76.35 USD +Prediksi untuk 2026-01-08T20:11: diprediksi 3150.47 USD, riil 3092.04 USD, kesalahan 58.43 USD +. +. +. +Prediksi untuk 2026-01-31T22:33: diprediksi 2637.73 USD, riil 2417.77 USD, kesalahan 219.96 USD +Prediksi untuk 2026-02-01T22:41: diprediksi 2381.70 USD, riil 2318.84 USD, kesalahan 62.86 USD +Prediksi untuk 2026-02-02T22:49: diprediksi 2234.91 USD, riil 2349.28 USD, kesalahan 114.37 USD +Kesalahan prediksi rata-rata selama 29 prediksi: 83.87103448275862068965517241 USD +Perubahan rata-rata per rekomendasi: 4.787931034482758620689655172 USD +Varians standar perubahan: 104.42 USD +Hari menguntungkan: 51.72% +Hari merugi: 48.28% +``` + +Sebagian besar penguji identik dengan agen, tetapi berikut adalah bagian-bagian yang baru atau dimodifikasi. + +```python +CYCLES_FOR_TEST = 40 # Untuk backtest, berapa banyak siklus yang kita uji + +# Dapatkan banyak kutipan +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 +) +``` + +Kami melihat ke belakang sebanyak `CYCLES_FOR_TEST` (ditentukan sebagai 40 di sini) hari. + +```python +# Buat prediksi dan periksa terhadap riwayat nyata + +total_error = Decimal(0) +changes = [] +``` + +Ada dua jenis kesalahan yang kami minati. Yang pertama, `total_error`, hanyalah jumlah kesalahan yang dibuat oleh prediktor. + +Untuk memahami yang kedua, `changes`, kita perlu mengingat tujuan agen. Tujuannya bukan untuk memprediksi rasio WETH/USDC (harga ETH). Ini untuk mengeluarkan rekomendasi jual dan beli. Jika harga saat ini adalah $2000 dan memprediksi $2010 besok, kami tidak keberatan jika hasil sebenarnya adalah $2020 dan kami mendapatkan uang tambahan. Namun, kita _keberatan_ jika diprediksi $2010, dan membeli ETH berdasarkan rekomendasi tersebut, dan harganya turun menjadi $1990. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +Kami hanya dapat melihat kasus di mana riwayat lengkap (nilai yang digunakan untuk prediksi dan nilai dunia nyata untuk membandingkannya) tersedia. Ini berarti kasus terbaru haruslah yang dimulai `CYCLES_BACK` yang lalu. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +Gunakan [irisan](https://www.w3schools.com/python/ref_func_slice.asp) untuk mendapatkan jumlah sampel yang sama dengan jumlah yang digunakan agen. Kode antara di sini dan segmen berikutnya adalah kode get-a-prediction yang sama yang kami miliki di agen. + +```python + 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 +``` + +Dapatkan harga yang diprediksi, harga riil, dan harga pada saat prediksi. Kita memerlukan harga pada saat prediksi untuk menentukan apakah rekomendasi tersebut untuk membeli atau menjual. + +```python + error = abs(predicted_price - real_price) + total_error += error + print (f"Prediksi untuk {prediction_time}: diprediksi {predicted_price} USD, riil {real_price} USD, kesalahan {error} USD") +``` + +Hitung kesalahannya, dan tambahkan ke total. + +```python + 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) +``` + +Untuk `changes`, kami menginginkan dampak moneter dari membeli atau menjual satu ETH. Jadi pertama, kita perlu menentukan rekomendasi, kemudian menilai bagaimana harga sebenarnya berubah, dan apakah rekomendasi tersebut menghasilkan uang (perubahan positif) atau merugikan uang (perubahan negatif). + +```python +print (f"Kesalahan prediksi rata-rata selama {len(wethusdc_quotes)-CYCLES_BACK} prediksi: {total_error / Decimal(len(wethusdc_quotes)-CYCLES_BACK)} USD") + +length_changes = Decimal(len(changes)) +mean_change = sum(changes, Decimal(0)) / length_changes +print (f"Perubahan rata-rata per rekomendasi: {mean_change} USD") +var = sum((x - mean_change) ** 2 for x in changes) / length_changes +print (f"Varians standar perubahan: {var.sqrt().quantize(Decimal("0.01"))} USD") +``` + +Laporkan hasilnya. + +```python +print (f"Hari menguntungkan: {len(list(filter(lambda x: x > 0, changes)))/length_changes:.2%}") +print (f"Hari merugi: {len(list(filter(lambda x: x < 0, changes)))/length_changes:.2%}") +``` + +Gunakan [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) untuk menghitung jumlah hari yang menguntungkan dan jumlah hari yang merugikan. Hasilnya adalah objek filter, yang perlu kita ubah menjadi daftar untuk mendapatkan panjangnya. + +### Mengirimkan transaksi {#submit-txn} + +Sekarang kita perlu benar-benar mengirimkan transaksi. Namun, saya tidak ingin menghabiskan uang sungguhan pada saat ini, sebelum sistem terbukti. Sebaliknya, kita akan membuat fork lokal dari Jaringan Utama, dan "berdagang" di jaringan itu. + +Berikut adalah langkah-langkah untuk membuat fork lokal dan mengaktifkan perdagangan. + +1. Instal [Foundry](https://getfoundry.sh/introduction/installation) + +2. Mulai [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` mendengarkan di URL default untuk Foundry, http://localhost:8545, jadi kita tidak perlu menentukan URL untuk [perintah `cast`](https://getfoundry.sh/cast/overview) yang kita gunakan untuk memanipulasi rantai blok. + +3. Saat berjalan di `anvil`, ada sepuluh akun uji yang memiliki ETH—atur variabel lingkungan untuk yang pertama + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. Ini adalah kontrak yang perlu kita gunakan. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) adalah kontrak Uniswap v3 yang kami gunakan untuk benar-benar berdagang. Kita bisa berdagang langsung melalui pool, tetapi ini jauh lebih mudah. + + Dua variabel terbawah adalah jalur Uniswap v3 yang diperlukan untuk menukar antara WETH dan USDC. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. Setiap akun uji memiliki 10.000 ETH. Gunakan kontrak WETH untuk membungkus 1000 ETH untuk mendapatkan 1000 WETH untuk perdagangan. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. Gunakan `SwapRouter` untuk memperdagangkan 500 WETH untuk USDC. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + Panggilan `approve` membuat tunjangan yang memungkinkan `SwapRouter` untuk membelanjakan sebagian token kita. Kontrak tidak dapat memantau aksi, jadi jika kita mentransfer token langsung ke kontrak `SwapRouter`, kontrak tersebut tidak akan tahu bahwa ia telah dibayar. Sebaliknya, kami mengizinkan kontrak `SwapRouter` untuk membelanjakan sejumlah tertentu, dan kemudian `SwapRouter` melakukannya. Ini dilakukan melalui fungsi yang dipanggil oleh `SwapRouter`, sehingga ia tahu jika berhasil. + +7. Verifikasi Anda memiliki cukup kedua token. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +Sekarang setelah kita memiliki WETH dan USDC, kita dapat benar-benar menjalankan agen. + +```sh +git checkout 05-trade +uv run agent.py +``` + +Outputnya akan terlihat mirip dengan: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +Harga saat ini: 1843.16 +Pada 2026-02-06T23:07, harga yang diharapkan: 1724.41 USD +Saldo akun sebelum perdagangan: +Saldo USDC: 927301.578272 +Saldo WETH: 500 +Jual, saya perkirakan harga akan turun sebesar 118.75 USD +Transaksi approve terkirim: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +Transaksi approve ditambang. +Transaksi jual terkirim: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +Transaksi jual ditambang. +Saldo akun setelah perdagangan: +Saldo USDC: 929143.797116 +Saldo WETH: 499 +``` + +Untuk benar-benar menggunakannya, Anda memerlukan beberapa perubahan kecil. + +- Di baris 14, ubah `MAINNET_URL` ke titik akses nyata, seperti `https://eth.drpc.org` +- Pada baris 28, ubah `PRIVATE_KEY` ke kunci pribadi Anda sendiri +- Kecuali Anda sangat kaya dan dapat membeli atau menjual 1 ETH setiap hari untuk agen yang belum terbukti, Anda mungkin ingin mengubah 29 untuk mengurangi `WETH_TRADE_AMOUNT` + +#### Penjelasan kode {#trading-code} + +Berikut adalah kode barunya. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +Variabel yang sama yang kita gunakan di langkah 4. + +```python +WETH_TRADE_AMOUNT=1 +``` + +Jumlah yang akan diperdagangkan. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +Untuk benar-benar berdagang, kita memerlukan fungsi `approve`. Kami juga ingin menampilkan saldo sebelum dan sesudah, jadi kami juga memerlukan `balanceOf`. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +Di `SwapRouter` ABI, kita hanya perlu `exactInput`. Ada fungsi terkait, `exactOutput`, yang bisa kita gunakan untuk membeli tepat satu WETH, tetapi untuk kesederhanaan kita hanya menggunakan `exactInput` dalam kedua kasus. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +Definisi Web3 untuk [`akun`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) dan kontrak `SwapRouter`. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +Parameter transaksi. Kita memerlukan fungsi di sini karena [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) harus berubah setiap saat. + +```python +def approve_token(contract: Contract, amount: int): +``` + +Setujui alokasi token untuk `SwapRouter`. + +```python + 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) +``` + +Beginilah cara kami mengirim transaksi di Web3. Pertama kita menggunakan [objek `Kontrak`](https://web3py.readthedocs.io/en/stable/web3.contract.html) untuk membangun transaksi. Kemudian kita menggunakan [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) untuk menandatangani transaksi, menggunakan `PRIVATE_KEY`. Terakhir, kami menggunakan [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) untuk mengirim transaksi. + +```python + print(f"Transaksi approve terkirim: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Transaksi approve ditambang.") +``` + +[`w3.eth.wait_for_transaction_receipt`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) menunggu hingga transaksi ditambang. Ini mengembalikan tanda terima jika diperlukan. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +Ini adalah parameter saat menjual WETH. + +```python +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, + } +``` + +Berbeda dengan `SELL_PARAMS`, parameter pembelian bisa berubah. Jumlah masukan adalah biaya 1 WETH, seperti yang tersedia di `quote`. + +```python +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"Transaksi beli terkirim: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Transaksi beli ditambang.") + + +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"Transaksi jual terkirim: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Transaksi jual ditambang.") +``` + +Fungsi `buy()` dan `sell()` hampir identik. Pertama kita menyetujui alokasi yang cukup untuk `SwapRouter`, dan kemudian kita memanggilnya dengan jalur dan jumlah yang benar. + +```python +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)}") +``` + +Laporkan saldo pengguna dalam kedua mata uang. + +```python +print("Saldo akun sebelum perdagangan:") +balances() + +if (expected_price > current_price): + print(f"Beli, saya perkirakan harga akan naik sebesar {expected_price - current_price} USD") + buy(wethusdc_quotes[-1]) +else: + print(f"Jual, saya perkirakan harga akan turun sebesar {current_price - expected_price} USD") + sell() + +print("Saldo akun setelah perdagangan:") +balances() +``` + +Agen ini saat ini hanya bekerja sekali. Namun, Anda dapat mengubahnya agar berfungsi secara terus-menerus baik dengan menjalankannya dari [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) atau dengan membungkus baris 368-400 dalam sebuah loop dan menggunakan [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) untuk menunggu hingga tiba waktunya untuk siklus berikutnya. + +## Kemungkinan perbaikan {#improvements} + +Ini bukan versi produksi penuh; ini hanyalah contoh untuk mengajarkan dasar-dasarnya. Berikut adalah beberapa ide untuk perbaikan. + +### Perdagangan yang lebih cerdas {#smart-trading} + +Ada dua fakta penting yang diabaikan agen saat memutuskan apa yang harus dilakukan. + +- _Besarnya perubahan yang diantisipasi_. Agen menjual sejumlah `WETH` yang tetap jika harga diperkirakan akan menurun, terlepas dari besarnya penurunan. + Boleh dibilang, akan lebih baik untuk mengabaikan perubahan kecil dan menjual berdasarkan seberapa besar kita mengharapkan harga akan turun. +- _Portofolio saat ini_. Jika 10% dari portofolio Anda ada di WETH dan Anda pikir harganya akan naik, mungkin masuk akal untuk membeli lebih banyak. Tetapi jika 90% dari portofolio Anda ada di WETH, Anda mungkin sudah cukup terekspos, dan tidak perlu membeli lebih banyak. Kebalikannya benar jika Anda mengharapkan harga akan turun. + +### Bagaimana jika Anda ingin merahasiakan strategi perdagangan Anda? {#secret} + +Vendor AI dapat melihat kueri yang Anda kirim ke LLM mereka, yang dapat mengekspos sistem perdagangan jenius yang Anda kembangkan dengan agen Anda. Sistem perdagangan yang digunakan terlalu banyak orang tidak ada gunanya karena terlalu banyak orang mencoba membeli saat Anda ingin membeli (dan harga naik) dan mencoba menjual saat Anda ingin menjual (dan harga turun). + +Anda dapat menjalankan LLM secara lokal, misalnya, menggunakan [LM-Studio](https://lmstudio.ai/), untuk menghindari masalah ini. + +### Dari bot AI ke agen AI {#bot-to-agent} + +Anda dapat membuat argumen yang baik bahwa ini adalah [bot AI, bukan agen AI](/ai-agents/#ai-agents-vs-ai-bots). Ini mengimplementasikan strategi yang relatif sederhana yang mengandalkan informasi yang telah ditentukan sebelumnya. Kita dapat mengaktifkan perbaikan diri, misalnya, dengan menyediakan daftar pool Uniswap v3 dan nilai terbarunya dan menanyakan kombinasi mana yang memiliki nilai prediktif terbaik. + +### Perlindungan slippage {#slippage-protection} + +Saat ini tidak ada [perlindungan slippage](https://uniswapv3book.com/milestone_3/slippage-protection.html). Jika kutipan saat ini adalah $2000, dan harga yang diharapkan adalah $2100, agen akan membeli. Namun, jika sebelum agen membeli biayanya naik menjadi $2200, tidak masuk akal lagi untuk membeli. + +Untuk menerapkan perlindungan slippage, tentukan nilai `amountOutMinimum` di baris 325 dan 334 dari [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325). + +## Kesimpulan {#conclusion} + +Semoga, sekarang Anda cukup tahu untuk memulai dengan agen AI. Ini bukan gambaran umum yang komprehensif tentang subjek ini; ada seluruh buku yang didedikasikan untuk itu, tetapi ini cukup untuk membuat Anda memulai. Semoga beruntung! + +[Lihat di sini untuk lebih banyak pekerjaan saya](https://cryptodocguy.pro/). diff --git a/public/content/translations/it/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/it/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..44283b84a24 --- /dev/null +++ b/public/content/translations/it/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: Crea il tuo agente di trading IA su Ethereum +description: In questa guida imparerai a creare un semplice agente di trading IA. Questo agente legge le informazioni dalla blockchain, chiede una raccomandazione a un LLM in base a tali informazioni, esegue lo scambio consigliato dall'LLM, quindi attende e ripete. +author: Ori Pomerantz +tags: [ "IA", "trading", "agente", "python" ] +skill: intermediate +published: 2026-02-13 +lang: it +sidebarDepth: 3 +--- + +In questa guida imparerai a creare un semplice agente di trading IA. Questo agente funziona seguendo questi passaggi: + +1. Leggere i prezzi attuali e passati di un token, nonché altre informazioni potenzialmente pertinenti +2. Costruire una query con queste informazioni, insieme a informazioni di base per spiegare come potrebbero essere pertinenti +3. Inviare la query e ricevere in risposta un prezzo previsto +4. Effettuare uno scambio in base alla raccomandazione +5. Attendere e ripetere + +Questo agente dimostra come leggere le informazioni, tradurle in una query che fornisce una risposta utilizzabile e utilizzare tale risposta. Tutti questi sono passaggi richiesti per un agente IA. Questo agente è implementato in Python perché è il linguaggio più comune utilizzato nell'IA. + +## Perché farlo? {#why-do-this} + +Gli agenti di trading automatizzati consentono agli sviluppatori di selezionare ed eseguire una strategia di trading. Gli [agenti IA](/ai-agents) consentono strategie di trading più complesse e dinamiche, utilizzando potenzialmente informazioni e algoritmi che lo sviluppatore non ha nemmeno considerato di usare. + +## Gli strumenti {#tools} + +Questa guida utilizza [Python](https://www.python.org/), la [libreria Web3](https://web3py.readthedocs.io/en/stable/) e [Uniswap v3](https://github.com/Uniswap/v3-periphery) per quotazioni e trading. + +### Perché Python? {#python} + +Il linguaggio più utilizzato per l'IA è [Python](https://www.python.org/), quindi lo usiamo qui. Non ti preoccupare se non conosci Python. Il linguaggio è molto chiaro e spiegherò esattamente cosa fa. + +La [libreria Web3](https://web3py.readthedocs.io/en/stable/) è l'API Python di Ethereum più comune. È abbastanza facile da usare. + +### Trading sulla blockchain {#trading-on-blockchain} + +Ci sono [molti exchange decentralizzati (DEX)](/apps/categories/defi/) che ti permettono di scambiare token su Ethereum. Tuttavia, tendono ad avere tassi di cambio simili a causa dell'[arbitraggio](/developers/docs/smart-contracts/composability/#better-user-experience). + +[Uniswap](https://app.uniswap.org/) è un DEX ampiamente utilizzato che possiamo usare sia per le quotazioni (per vedere i valori relativi dei token) sia per gli scambi. + +### OpenAI {#openai} + +Per un modello linguistico di grandi dimensioni, ho scelto di iniziare con [OpenAI](https://openai.com/). Per eseguire l'applicazione in questa guida dovrai pagare per l'accesso all'API. Il pagamento minimo di 5$ è più che sufficiente. + +## Sviluppo, passo dopo passo {#step-by-step} + +Per semplificare lo sviluppo, procediamo per fasi. Ogni passaggio è un branch in GitHub. + +### Per iniziare {#getting-started} + +Ci sono passaggi per iniziare con UNIX o Linux (incluso [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) + +1. Se non lo hai già, scarica e installa [Python](https://www.python.org/downloads/). + +2. Clona la repository di GitHub. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. Installa [`uv`](https://docs.astral.sh/uv/getting-started/installation/). Il comando sul tuo sistema potrebbe essere diverso. + + ```sh + pipx install uv + ``` + +4. Scarica le librerie. + + ```sh + uv sync + ``` + +5. Attiva l'ambiente virtuale. + + ```sh + source .venv/bin/activate + ``` + +6. Per verificare che Python e Web3 funzionino correttamente, esegui `python3` e forniscigli questo programma. Puoi inserirlo al prompt `>>>`; non è necessario creare un file. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### Lettura dalla blockchain {#read-blockchain} + +Il passo successivo è leggere dalla blockchain. Per farlo, devi passare al branch `02-read-quote` e quindi usare `uv` per eseguire il programma. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +Dovresti ricevere un elenco di oggetti `Quote`, ognuno con un indicatore ora, un prezzo e l'asset (attualmente sempre `WETH/USDC`). + +Ecco una spiegazione riga per riga. + +```python +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 +``` + +Importa le librerie di cui abbiamo bisogno. Sono spiegati di seguito quando vengono utilizzati. + +```python +print = functools.partial(print, flush=True) +``` + +Sostituisce il `print` di Python con una versione che svuota sempre l'output immediatamente. Questo è utile in uno script a lunga esecuzione perché non vogliamo attendere aggiornamenti di stato o output di debug. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +Un URL per accedere alla Rete Principale. Puoi ottenerne uno da [Nodo come servizio](/developers/docs/nodes-and-clients/nodes-as-a-service/) o usare uno di quelli pubblicizzati in [Chainlist](https://chainlist.org/chain/1). + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +Un blocco della Rete Principale di Ethereum si verifica in genere ogni dodici secondi, quindi questo è il numero di blocchi che ci aspetteremmo si verifichino in un periodo di tempo. Nota che questa non è una cifra esatta. Quando il [proponente del blocco](/developers/docs/consensus-mechanisms/pos/block-proposal/) non è attivo, quel blocco viene saltato e il tempo per il blocco successivo è di 24 secondi. Se volessimo ottenere il blocco esatto per un indicatore ora, useremmo la [ricerca binaria](https://en.wikipedia.org/wiki/Binary_search). Tuttavia, questo è sufficientemente vicino per i nostri scopi. Prevedere il futuro non è una scienza esatta. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +La dimensione del ciclo. Esaminiamo le quotazioni una volta per ciclo e proviamo a stimare il valore alla fine del ciclo successivo. + +```python +# L'indirizzo del gruppo che stiamo leggendo +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +I valori delle quotazioni sono presi dal gruppo USDC/WETH di Uniswap 3 all'indirizzo [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract). Questo indirizzo è già in formato checksum, ma è meglio usare [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) per rendere il codice riutilizzabile. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +Queste sono le [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html) per i due contratti che dobbiamo contattare. Per mantenere il codice conciso, includiamo solo le funzioni che dobbiamo chiamare. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +Inizializza la libreria [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) e connettiti a un nodo Ethereum. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +Questo è un modo per creare una classe di dati in Python. Il tipo di dati [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) viene utilizzato per connettersi al contratto. Nota il `(frozen=True)`. In Python i [booleani](https://en.wikipedia.org/wiki/Boolean_data_type) sono definiti come `True` o `False`, con la lettera maiuscola. Questa classe di dati è `frozen`, il che significa che i campi non possono essere modificati. + +Nota l'indentazione. A differenza dei [linguaggi derivati dal C](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages), Python usa l'indentazione per denotare i blocchi. L'interprete Python sa che la seguente definizione non fa parte di questa classe di dati perché non inizia con la stessa indentazione dei campi della classe di dati. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +Il tipo [`Decimal`](https://docs.python.org/3/library/decimal.html) viene utilizzato per gestire accuratamente le frazioni decimali. + +```python + def get_price(self, block: int) -> Decimal: +``` + +Questo è il modo per definire una funzione in Python. La definizione è indentata per mostrare che fa ancora parte di `PoolInfo`. + +In una funzione che fa parte di una classe di dati il primo parametro è sempre `self`, l'istanza della classe di dati che ha chiamato qui. Qui c'è un altro parametro, il numero del blocco. + +```python + assert block <= w3.eth.block_number, "Il blocco è nel futuro" +``` + +Se potessimo leggere il futuro, non avremmo bisogno dell'IA per il trading. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +La sintassi per chiamare una funzione sulla EVM da Web3 è questa: `.functions.().call()`. I parametri possono essere i parametri della funzione EVM (se presenti; qui non ce ne sono) o [parametri nominativi](https://en.wikipedia.org/wiki/Named_parameter) per modificare il comportamento della blockchain. Qui ne usiamo uno, `block_identifier`, per specificare [il numero del blocco](/developers/docs/apis/json-rpc/#default-block) in cui desideriamo eseguire. + +Il risultato è [questa struct, in forma di array](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). Il primo valore è una funzione del tasso di cambio tra i due token. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +Per ridurre i calcoli sulla catena, Uniswap v3 non memorizza il fattore di cambio effettivo ma la sua radice quadrata. Poiché l'EVM non supporta la matematica in virgola mobile o le frazioni, invece del valore effettivo, la risposta è price296 + +```python + # (token1 per token0) + return 1/(raw_price * self.decimal_factor) +``` + +Il prezzo grezzo che otteniamo è il numero di `token0` che otteniamo per ogni `token1`. Nel nostro gruppo `token0` è USDC (stablecoin con lo stesso valore di un dollaro USA) e `token1` è [WETH](https://opensea.io/learn/blockchain/what-is-weth). Il valore che vogliamo veramente è il numero di dollari per WETH, non l'inverso. + +Il fattore decimale è il rapporto tra i [fattori decimali](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) per i due token. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +Questa classe di dati rappresenta una quotazione: il prezzo di un asset specifico in un dato momento. A questo punto, il campo `asset` è irrilevante perché usiamo un singolo gruppo e quindi abbiamo un singolo asset. Tuttavia, aggiungeremo altri asset in seguito. + +```python +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 + ) +``` + +Questa funzione accetta un indirizzo e restituisce informazioni sul contratto del token a quell'indirizzo. Per creare un nuovo [`Contract` Web3](https://web3py.readthedocs.io/en/stable/web3.contract.html), forniamo l'indirizzo e l'ABI a `w3.eth.contract`. + +```python +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) + ) +``` + +Questa funzione restituisce tutto ciò di cui abbiamo bisogno su [un gruppo specifico](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol). La sintassi `f""` è una [stringa formattata](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +Ottieni un oggetto `Quote`. Il valore predefinito per `block_number` è `None` (nessun valore). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +Se non è stato specificato un numero di blocco, usa `w3.eth.block_number`, che è l'ultimo numero di blocco. Questa è la sintassi per [un'istruzione `if`](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement). + +Potrebbe sembrare che sarebbe stato meglio impostare semplicemente il valore predefinito su `w3.eth.block_number`, ma non funziona bene perché sarebbe il numero di blocco al momento della definizione della funzione. In un agente a lunga esecuzione, questo sarebbe un problema. + +```python + 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 + ) +``` + +Usa [la libreria `datetime`](https://docs.python.org/3/library/datetime.html) per formattarlo in un formato leggibile per gli esseri umani e per i modelli linguistici di grandi dimensioni (LLM). Usa [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) per arrotondare il valore a due cifre decimali. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +In Python si definisce un [elenco](https://docs.python.org/3/library/stdtypes.html#typesseq-list) che può contenere solo un tipo specifico usando `list[]`. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +In Python un [`ciclo `for``](https://docs.python.org/3/tutorial/controlflow.html#for-statements) di solito itera su un elenco. L'elenco di numeri di blocco in cui trovare le quotazioni proviene da [`range`](https://docs.python.org/3/library/stdtypes.html#range). + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +Per ogni numero di blocco, ottieni un oggetto `Quote` e aggiungilo all'elenco `quotes`. Quindi restituisci quell'elenco. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +Questo è il codice principale dello script. Leggi le informazioni del gruppo, ottieni dodici quotazioni e visualizzale con [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint). + +### Creazione di un prompt {#prompt} + +Successivamente, dobbiamo convertire questo elenco di quotazioni in un prompt per un LLM e ottenere un valore futuro atteso. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +L'output ora sarà un prompt per un LLM, simile a: + +``` +Date queste quotazioni: +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 + + +Quale valore ti aspetteresti per WETH/USDC all'ora 2026-02-02T17:56? + +Fornisci la tua risposta come un singolo numero arrotondato a due cifre decimali, +senza alcun altro testo. +``` + +Nota che qui ci sono quotazioni per due asset, `WETH/USDC` e `WBTC/WETH`. L'aggiunta di quotazioni da un altro asset potrebbe migliorare l'accuratezza della previsione. + +#### Come appare un prompt {#prompt-explanation} + +Questo prompt contiene tre sezioni, che sono abbastanza comuni nei prompt LLM. + +1. Informazioni. Gli LLM hanno molte informazioni dal loro addestramento, ma di solito non hanno le più recenti. Questo è il motivo per cui dobbiamo recuperare le ultime quotazioni qui. L'aggiunta di informazioni a un prompt è chiamata [generazione aumentata dal recupero (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +2. La domanda vera e propria. Questo è ciò che vogliamo sapere. + +3. Istruzioni per la formattazione dell'output. Normalmente, un LLM ci darà una stima con una spiegazione di come ci è arrivato. Questo è meglio per gli esseri umani, ma un programma per computer ha solo bisogno del risultato finale. + +#### Spiegazione del codice {#prompt-code} + +Ecco il nuovo codice. + +```python +from datetime import datetime, timezone, timedelta +``` + +Dobbiamo fornire all'LLM l'ora per la quale vogliamo una stima. Per ottenere un'ora "n minuti/ore/giorni" nel futuro, usiamo [la classe `timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta). + +```python +# Gli indirizzi dei gruppi che stiamo leggendo +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +Abbiamo due gruppi che dobbiamo leggere. + +```python +@dataclass(frozen=True) +class PoolInfo: + . + . + . + reverse: bool = False + + def get_price(self, block: int) -> Decimal: + assert block <= w3.eth.block_number, "Il blocco è nel futuro" + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (token1 per token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +Nel gruppo WETH/USDC, vogliamo sapere quanti `token0` (USDC) ci servono per acquistare un `token1` (WETH). Nel gruppo WETH/WBTC, vogliamo sapere quanti `token1` (WETH) ci servono per acquistare un `token0` (WBTC, che è Bitcoin wrappato). Dobbiamo tenere traccia se il rapporto del gruppo deve essere invertito. + +```python +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 + ) +``` + +Per sapere se un gruppo deve essere invertito, dobbiamo ottenerlo come input per `read_pool`. Inoltre, il simbolo dell'asset deve essere impostato correttamente. + +La sintassi ` if else ` è l'equivalente Python dell'[operatore condizionale ternario](https://en.wikipedia.org/wiki/Ternary_conditional_operator), che in un linguaggio derivato dal C sarebbe ` ? : `. + +```python +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 +``` + +Questa funzione costruisce una stringa che formatta un elenco di oggetti `Quote`, assumendo che si applichino tutti allo stesso asset. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +In Python le [stringhe letterali multiriga](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) sono scritte come `"""` .... `"""`. + +```python +Date queste quotazioni: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +Qui usiamo il modello [MapReduce](https://en.wikipedia.org/wiki/MapReduce) per generare una stringa per ogni elenco di quotazioni con `format_quotes`, quindi li riduciamo in una singola stringa da usare nel prompt. + +```python +Quale valore ti aspetteresti per {asset} all'ora {expected_time}? + +Fornisci la tua risposta come un singolo numero arrotondato a due cifre decimali, +senza alcun altro testo. + """ +``` + +Il resto del prompt è come previsto. + +```python +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 +) +``` + +Esamina i due gruppi e ottieni quotazioni da entrambi. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +Determina il momento futuro per il quale vogliamo la stima e crea il prompt. + +### Interfacciarsi con un LLM {#interface-llm} + +Successivamente, inviamo un prompt a un LLM effettivo e riceviamo un valore futuro atteso. Ho scritto questo programma usando OpenAI, quindi se vuoi usare un fornitore diverso, dovrai adattarlo. + +1. Ottieni un [conto OpenAI](https://auth.openai.com/create-account) + +2. [Ricarica il conto](https://platform.openai.com/settings/organization/billing/overview)—l'importo minimo al momento della scrittura è di 5$ + +3. [Crea una chiave API](https://platform.openai.com/settings/organization/api-keys) + +4. Nella riga di comando, esporta la chiave API in modo che il tuo programma possa usarla + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. Fai il checkout ed esegui l'agente + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +Ecco il nuovo codice. + +```python +from openai import OpenAI + +open_ai = OpenAI() # Il client legge la variabile d'ambiente OPENAI_API_KEY +``` + +Importa e istanzia l'API di OpenAI. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +Chiama l'API di OpenAI (`open_ai.chat.completions.create`) per creare la risposta. + +```python +expected_price = Decimal(response.choices[0].message.content.strip()) +current_price = wethusdc_quotes[-1].price + +print ("Prezzo attuale:", wethusdc_quotes[-1].price) +print(f"In {future_time}, prezzo previsto: {expected_price} USD") + +if (expected_price > current_price): + print(f"Compra, mi aspetto che il prezzo salga di {expected_price - current_price} USD") +else: + print(f"Vendi, mi aspetto che il prezzo scenda di {current_price - expected_price} USD") +``` + +Mostra il prezzo e fornisci una raccomandazione di acquisto o vendita. + +#### Testare le previsioni {#testing-the-predictions} + +Ora che possiamo generare previsioni, possiamo anche utilizzare dati storici per valutare se produciamo previsioni utili. + +```sh +uv run test-predictor.py +``` + +Il risultato atteso è simile a: + +``` +Previsione per 2026-01-05T19:50: previsto 3138.93 USD, reale 3218.92 USD, errore 79.99 USD +Previsione per 2026-01-06T19:56: previsto 3243.39 USD, reale 3221.08 USD, errore 22.31 USD +Previsione per 2026-01-07T20:02: previsto 3223.24 USD, reale 3146.89 USD, errore 76.35 USD +Previsione per 2026-01-08T20:11: previsto 3150.47 USD, reale 3092.04 USD, errore 58.43 USD +. +. +. +Previsione per 2026-01-31T22:33: previsto 2637.73 USD, reale 2417.77 USD, errore 219.96 USD +Previsione per 2026-02-01T22:41: previsto 2381.70 USD, reale 2318.84 USD, errore 62.86 USD +Previsione per 2026-02-02T22:49: previsto 2234.91 USD, reale 2349.28 USD, errore 114.37 USD +Errore medio di previsione su 29 previsioni: 83.87103448275862068965517241 USD +Variazione media per raccomandazione: 4.787931034482758620689655172 USD +Varianza standard delle variazioni: 104.42 USD +Giorni redditizi: 51.72% +Giorni in perdita: 48.28% +``` + +La maggior parte del tester è identica all'agente, ma ecco le parti nuove o modificate. + +```python +CYCLES_FOR_TEST = 40 # Per il backtest, quanti cicli testiamo + +# Ottieni molte quotazioni +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 +) +``` + +Guardiamo indietro di `CYCLES_FOR_TEST` (specificato come 40 qui) giorni. + +```python +# Crea previsioni e confrontale con la cronologia reale + +total_error = Decimal(0) +changes = [] +``` + +Ci sono due tipi di errori che ci interessano. Il primo, `total_error`, è semplicemente la somma degli errori commessi dal predittore. + +Per capire il secondo, `changes`, dobbiamo ricordare lo scopo dell'agente. Non è prevedere il rapporto WETH/USDC (prezzo di ETH). È emettere raccomandazioni di vendita e acquisto. Se il prezzo è attualmente di 2000 $ e prevede 2010 $ domani, non ci importa se il risultato effettivo è 2020 $ e guadagniamo soldi extra. Ma ci importa se ha previsto 2010 $, e ha comprato ETH sulla base di quella raccomandazione, e il prezzo scende a 1990 $. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +Possiamo esaminare solo i casi in cui la cronologia completa (i valori utilizzati per la previsione e il valore del mondo reale con cui confrontarla) è disponibile. Ciò significa che il caso più recente deve essere quello iniziato `CYCLES_BACK` fa. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +Usa le [slice](https://www.w3schools.com/python/ref_func_slice.asp) per ottenere lo stesso numero di campioni utilizzato dall'agente. Il codice tra qui e il segmento successivo è lo stesso codice per ottenere una previsione che abbiamo nell'agente. + +```python + 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 +``` + +Ottieni il prezzo previsto, il prezzo reale e il prezzo al momento della previsione. Abbiamo bisogno del prezzo al momento della previsione per determinare se la raccomandazione era di comprare o vendere. + +```python + error = abs(predicted_price - real_price) + total_error += error + print (f"Previsione per {prediction_time}: previsto {predicted_price} USD, reale {real_price} USD, errore {error} USD") +``` + +Calcola l'errore e aggiungilo al totale. + +```python + 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) +``` + +Per `changes`, vogliamo l'impatto monetario dell'acquisto o della vendita di un ETH. Quindi, prima dobbiamo determinare la raccomandazione, poi valutare come è cambiato il prezzo effettivo e se la raccomandazione ha prodotto un guadagno (variazione positiva) o una perdita (variazione negativa). + +```python +print (f"Errore medio di previsione su {len(wethusdc_quotes)-CYCLES_BACK} previsioni: {total_error / Decimal(len(wethusdc_quotes)-CYCLES_BACK)} USD") + +length_changes = Decimal(len(changes)) +mean_change = sum(changes, Decimal(0)) / length_changes +print (f"Variazione media per raccomandazione: {mean_change} USD") +var = sum((x - mean_change) ** 2 for x in changes) / length_changes +print (f"Varianza standard delle variazioni: {var.sqrt().quantize(Decimal("0.01"))} USD") +``` + +Riporta i risultati. + +```python +print (f"Giorni redditizi: {len(list(filter(lambda x: x > 0, changes)))/length_changes:.2%}") +print (f"Giorni in perdita: {len(list(filter(lambda x: x < 0, changes)))/length_changes:.2%}") +``` + +Usa [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) per contare il numero di giorni redditizi e il numero di giorni costosi. Il risultato è un oggetto filtro, che dobbiamo convertire in un elenco per ottenerne la lunghezza. + +### Invio di transazioni {#submit-txn} + +Ora dobbiamo effettivamente inviare le transazioni. Tuttavia, non voglio spendere soldi veri a questo punto, prima che il sistema sia collaudato. Invece, creeremo una biforcazione locale della Rete Principale e faremo "trading" su quella rete. + +Ecco i passaggi per creare una biforcazione locale e abilitare il trading. + +1. Installa [Foundry](https://getfoundry.sh/introduction/installation) + +2. Avvia [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` è in ascolto sull'URL predefinito per Foundry, http://localhost:8545, quindi non è necessario specificare l'URL per [il comando `cast`](https://getfoundry.sh/cast/overview) che usiamo per manipolare la blockchain. + +3. Quando si esegue in `anvil`, ci sono dieci conti di prova che hanno ETH: imposta le variabili d'ambiente per il primo + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. Questi sono i contratti che dobbiamo usare. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) è il contratto di Uniswap v3 che usiamo per fare trading. Potremmo fare trading direttamente attraverso il gruppo, ma questo è molto più facile. + + Le due variabili in basso sono i percorsi di Uniswap v3 necessari per scambiare tra WETH e USDC. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. Ognuno dei conti di prova ha 10.000 ETH. Usa il contratto WETH per wrappare 1000 ETH per ottenere 1000 WETH per il trading. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. Usa `SwapRouter` per scambiare 500 WETH con USDC. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + La chiamata `approve` crea un'autorizzazione che consente a `SwapRouter` di spendere alcuni dei nostri token. I contratti non possono monitorare gli eventi, quindi se trasferiamo i token direttamente al contratto `SwapRouter`, questo non saprebbe di essere stato pagato. Invece, permettiamo al contratto `SwapRouter` di spendere un certo importo, e poi `SwapRouter` lo fa. Questo viene fatto tramite una funzione chiamata da `SwapRouter`, quindi sa se ha avuto successo. + +7. Verifica di avere abbastanza di entrambi i token. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +Ora che abbiamo WETH e USDC, possiamo effettivamente eseguire l'agente. + +```sh +git checkout 05-trade +uv run agent.py +``` + +L'output sarà simile a: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +Prezzo attuale: 1843.16 +In 2026-02-06T23:07, prezzo previsto: 1724.41 USD +Saldi del conto prima dello scambio: +Saldo USDC: 927301.578272 +Saldo WETH: 500 +Vendi, mi aspetto che il prezzo scenda di 118.75 USD +Transazione di approvazione inviata: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +Transazione di approvazione minata. +Transazione di vendita inviata: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +Transazione di vendita minata. +Saldi del conto dopo lo scambio: +Saldo USDC: 929143.797116 +Saldo WETH: 499 +``` + +Per usarlo effettivamente, hai bisogno di alcune piccole modifiche. + +- Nella riga 14, cambia `MAINNET_URL` in un punto di accesso reale, come `https://eth.drpc.org` +- Nella riga 28, cambia `PRIVATE_KEY` con la tua chiave privata +- A meno che tu non sia molto ricco e possa comprare o vendere 1 ETH ogni giorno per un agente non provato, potresti voler cambiare la riga 29 per diminuire `WETH_TRADE_AMOUNT` + +#### Spiegazione del codice {#trading-code} + +Ecco il nuovo codice. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +Le stesse variabili che abbiamo usato nel passaggio 4. + +```python +WETH_TRADE_AMOUNT=1 +``` + +L'importo da scambiare. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +Per fare effettivamente trading, abbiamo bisogno della funzione `approve`. Vogliamo anche mostrare i saldi prima e dopo, quindi abbiamo bisogno anche di `balanceOf`. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +Nell'ABI di `SwapRouter` abbiamo solo bisogno di `exactInput`. Esiste una funzione correlata, `exactOutput`, che potremmo usare per comprare esattamente un WETH, ma per semplicità usiamo solo `exactInput` in entrambi i casi. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +Le definizioni Web3 per il [`conto`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) e il contratto `SwapRouter`. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +I parametri della transazione. Abbiamo bisogno di una funzione qui perché [il nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) deve cambiare ogni volta. + +```python +def approve_token(contract: Contract, amount: int): +``` + +Approva un'autorizzazione di token per `SwapRouter`. + +```python + 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) +``` + +Questo è il modo in cui inviamo una transazione in Web3. Per prima cosa usiamo [l'oggetto `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) per costruire la transazione. Poi usiamo [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) per firmare la transazione, usando `PRIVATE_KEY`. Infine, usiamo [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) per inviare la transazione. + +```python + print(f"Transazione di approvazione inviata: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Transazione di approvazione minata.") +``` + +[`w3.eth.wait_for_transaction_receipt`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) attende che la transazione venga minata. Restituisce la ricevuta, se necessario. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +Questi sono i parametri per la vendita di WETH. + +```python +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, + } +``` + +A differenza di `SELL_PARAMS`, i parametri di acquisto possono cambiare. L'importo in entrata è il costo di 1 WETH, come disponibile in `quote`. + +```python +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"Transazione di acquisto inviata: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Transazione di acquisto minata.") + + +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"Transazione di vendita inviata: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Transazione di vendita minata.") +``` + +Le funzioni `buy()` e `sell()` sono quasi identiche. Per prima cosa approviamo un'autorizzazione sufficiente per `SwapRouter`, e poi lo chiamiamo con il percorso e l'importo corretti. + +```python +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} Saldo: {Decimal(token0_balance) / Decimal(10 ** wethusdc_pool.token0.decimals)}") + print(f"{wethusdc_pool.token1.symbol} Saldo: {Decimal(token1_balance) / Decimal(10 ** wethusdc_pool.token1.decimals)}") +``` + +Riporta i saldi dell'utente in entrambe le valute. + +```python +print("Saldi del conto prima dello scambio:") +balances() + +if (expected_price > current_price): + print(f"Compra, mi aspetto che il prezzo salga di {expected_price - current_price} USD") + buy(wethusdc_quotes[-1]) +else: + print(f"Vendi, mi aspetto che il prezzo scenda di {current_price - expected_price} USD") + sell() + +print("Saldi del conto dopo lo scambio:") +balances() +``` + +Questo agente attualmente funziona solo una volta. Tuttavia, puoi modificarlo per farlo funzionare continuamente eseguendolo da [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) o racchiudendo le righe 368-400 in un ciclo e usando [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) per attendere fino al momento del ciclo successivo. + +## Possibili miglioramenti {#improvements} + +Questa non è una versione di produzione completa; è semplicemente un esempio per insegnare le basi. Ecco alcune idee per miglioramenti. + +### Trading più intelligente {#smart-trading} + +Ci sono due fatti importanti che l'agente ignora quando decide cosa fare. + +- _L'entità del cambiamento previsto_. L'agente vende una quantità fissa di `WETH` se si prevede che il prezzo diminuisca, indipendentemente dall'entità del calo. + Probabilmente, sarebbe meglio ignorare le piccole variazioni e vendere in base a quanto ci aspettiamo che il prezzo diminuisca. +- _Il portafoglio attuale_. Se il 10% del tuo portafoglio è in WETH e pensi che il prezzo salirà, probabilmente ha senso acquistarne di più. Ma se il 90% del tuo portafoglio è in WETH, potresti essere sufficientemente esposto e non c'è bisogno di acquistarne di più. Il contrario è vero se ti aspetti che il prezzo scenda. + +### E se volessi mantenere segreta la tua strategia di trading? {#secret} + +I fornitori di IA possono vedere le query che invii ai loro LLM, il che potrebbe esporre il geniale sistema di trading che hai sviluppato con il tuo agente. Un sistema di trading che troppe persone usano è inutile perché troppe persone cercano di comprare quando vuoi comprare tu (e il prezzo sale) e cercano di vendere quando vuoi vendere tu (e il prezzo scende). + +Puoi eseguire un LLM localmente, ad esempio, utilizzando [LM-Studio](https://lmstudio.ai/), per evitare questo problema. + +### Da bot IA ad agente IA {#bot-to-agent} + +Si può sostenere validamente che questo è [un bot IA, non un agente IA](/ai-agents/#ai-agents-vs-ai-bots). Implementa una strategia relativamente semplice che si basa su informazioni predefinite. Possiamo abilitare l'auto-miglioramento, ad esempio, fornendo un elenco di gruppi di Uniswap v3 e i loro ultimi valori e chiedendo quale combinazione ha il miglior valore predittivo. + +### Protezione dallo slippage {#slippage-protection} + +Attualmente non c'è [protezione dallo slippage](https://uniswapv3book.com/milestone_3/slippage-protection.html). Se la quotazione attuale è di 2000 $ e il prezzo previsto è di 2100 $, l'agente comprerà. Tuttavia, se prima che l'agente acquisti il costo sale a 2200 $, non ha più senso comprare. + +Per implementare la protezione dallo slippage, specifica un valore `amountOutMinimum` nelle righe 325 e 334 di [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325). + +## Conclusione {#conclusion} + +Speriamo che ora tu sappia abbastanza per iniziare con gli agenti IA. Questa non è una panoramica completa dell'argomento; ci sono interi libri dedicati a questo, ma questo è sufficiente per iniziare. Buona fortuna! + +[Vedi qui per altri miei lavori](https://cryptodocguy.pro/). diff --git a/public/content/translations/ja/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/ja/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..711b4e44af2 --- /dev/null +++ b/public/content/translations/ja/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,979 @@ +--- +title: "イーサリアムで独自のAI取引エージェントを作成する" +description: "このチュートリアルでは、簡単なAI取引エージェントの作成方法を学びます。 このエージェントは、ブロックチェーンから情報を読み取り、その情報に基づいてLLMに推奨を求め、LLMが推奨する取引を実行し、その後待機して繰り返します。" +author: Ori Pomerantz +tags: [ "AI", "取引", "エージェント", "python" ] +skill: intermediate +published: 2026-02-13 +lang: ja +sidebarDepth: 3 +--- + +このチュートリアルでは、簡単なAI取引エージェントの構築方法を学びます。 このエージェントは次の手順で動作します。 + +1. トークンの現在および過去の価格、および関連する可能性のあるその他の情報を読み取ります +2. この情報と、その関連性を説明する背景情報を使用してクエリを構築します +3. クエリを送信し、予測価格を受け取ります +4. 推奨に基づいて取引します +5. 待機して繰り返します + +このエージェントは、情報を読み取り、それを使用可能な回答をもたらすクエリに変換し、その回答を使用する方法を示します。 これらはすべてAIエージェントに必要な手順です。 このエージェントは、AIで最も一般的に使用される言語であるため、Pythonで実装されています。 + +## なぜこれを行うのか? {#why-do-this} + +自動取引エージェントを使用すると、デベロッパーは取引戦略を選択して実行できます。 [AIエージェント](/ai-agents)は、デベロッパーが使用を考えもしなかった情報やアルゴリズムを使用して、より複雑で動的な取引戦略を可能にします。 + +## ツール {#tools} + +このチュートリアルでは、見積もりと取引に[Python](https://www.python.org/)、[Web3ライブラリ](https://web3py.readthedocs.io/en/stable/)、[Uniswap v3](https://github.com/Uniswap/v3-periphery)を使用します。 + +### なぜPythonなのか? {#python} + +AIで最も広く使用されている言語は[Python](https://www.python.org/)なので、ここではそれを使用します。 Pythonを知らなくても心配いりません。 この言語は非常に分かりやすく、何をするのかを正確に説明します。 + +[Web3ライブラリ](https://web3py.readthedocs.io/en/stable/)は、最も一般的なPythonのイーサリアムAPIです。 非常に使いやすいです。 + +### ブロックチェーンでの取引 {#trading-on-blockchain} + +イーサリアムでトークンを取引できる[多くの分散型取引所(DEX)](/apps/categories/defi/)があります。 しかし、[裁定取引](/developers/docs/smart-contracts/composability/#better-user-experience)により、それらの為替レートは類似する傾向にあります。 + +[Uniswap](https://app.uniswap.org/)は広く使用されているDEXで、見積もり(トークンの相対的な価値を確認するため)と取引の両方に使用できます。 + +### OpenAI {#openai} + +大規模言語モデルには、[OpenAI](https://openai.com/)から始めることにしました。 このチュートリアルのアプリケーションを実行するには、APIアクセスに料金を支払う必要があります。 最低支払額の5ドルで十分すぎるほどです。 + +## 開発、ステップバイステップ {#step-by-step} + +開発を簡素化するため、段階的に進めます。 各ステップはGitHubのブランチです。 + +### はじめに {#getting-started} + +UNIXまたはLinux([WSL](https://learn.microsoft.com/en-us/windows/wsl/install)を含む)で開始するための手順があります + +1. まだお持ちでない場合は、[Python](https://www.python.org/downloads/)をダウンロードしてインストールしてください。 + +2. GitHubリポジトリをクローンします。 + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. [`uv`](https://docs.astral.sh/uv/getting-started/installation/)をインストールします。 お使いのシステムではコマンドが異なる場合があります。 + + ```sh + pipx install uv + ``` + +4. ライブラリをダウンロードします。 + + ```sh + uv sync + ``` + +5. 仮想環境を有効にします。 + + ```sh + source .venv/bin/activate + ``` + +6. PythonとWeb3が正しく動作していることを確認するには、`python3`を実行し、このプログラムを提供します。 `>>>`プロンプトで入力できます。ファイルを作成する必要はありません。 + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### ブロックチェーンからの読み取り {#read-blockchain} + +次のステップは、ブロックチェーンから読み取ることです。 そのためには、`02-read-quote`ブランチに切り替えてから、`uv`を使用してプログラムを実行する必要があります。 + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +タイムスタンプ、価格、資産(現在は常に`WETH/USDC`)を持つ`Quote`オブジェクトのリストを受け取るはずです。 + +以下に一行ごとの説明を示します。 + +```python +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 +``` + +必要なライブラリをインポートします。 これらは使用されるときに以下で説明されます。 + +```python +print = functools.partial(print, flush=True) +``` + +Pythonの`print`を、常に出力を即座にフラッシュするバージョンに置き換えます。 これは、ステータスのアップデートやデバッグ出力を待ちたくない長時間実行されるスクリプトで役立ちます。 + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +メインネットにアクセスするためのURLです。 [Node as a Service](/developers/docs/nodes-and-clients/nodes-as-a-service/)から取得するか、[Chainlist](https://chainlist.org/chain/1)で宣伝されているものを使用できます。 + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +イーサリアムメインネットのブロックは通常12秒ごとに発生するため、これらは一定期間内に発生すると予想されるブロックの数です。 これは正確な数値ではないことに注意してください。 [ブロック提案者](/developers/docs/consensus-mechanisms/pos/block-proposal/)がダウンしている場合、そのブロックはスキップされ、次のブロックまでの時間は24秒になります。 タイムスタンプの正確なブロックを取得したい場合は、[バイナリ検索](https://en.wikipedia.org/wiki/Binary_search)を使用します。 しかし、これは我々の目的には十分近いです。 未来を予測することは、厳密な科学ではありません。 + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +サイクルのサイズ。 サイクルごとに1回見積もりを確認し、次のサイクルの終わりの値を推定しようとします。 + +```python +# 読み取っているプールのアドレス +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +見積もり値は、アドレス[`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract)のUniswap 3 USDC/WETHプールから取得されます。 このアドレスは既にチェックサム形式ですが、コードを再利用可能にするために[`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address)を使用する方が良いです。 + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +これらは、私たちが連絡する必要のある2つのコントラクトの[ABI](https://docs.soliditylang.org/en/latest/abi-spec.html)です。 コードを簡潔に保つため、呼び出す必要のある関数のみを含めます。 + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +[`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers)ライブラリを初期化し、イーサリアムノードに接続します。 + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +これはPythonでデータクラスを作成する方法の一つです。 [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html)データ型は、コントラクトへの接続に使用されます。 `(frozen=True)`に注意してください。 Pythonでは、[ブール値](https://en.wikipedia.org/wiki/Boolean_data_type)は`True`または`False`として定義され、大文字で始まります。 このデータクラスは`frozen`(フリーズ)されており、フィールドを変更できないことを意味します。 + +インデントに注意してください。 [C派生言語](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages)とは対照的に、Pythonはインデントを使用してブロックを示します。 Pythonインタープリタは、次の定義がデータクラスのフィールドと同じインデントで始まっていないため、このデータクラスの一部ではないことを認識します。 + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +[`Decimal`](https://docs.python.org/3/library/decimal.html)型は、小数を正確に扱うために使用されます。 + +```python + def get_price(self, block: int) -> Decimal: +``` + +これはPythonで関数を定義する方法です。 定義はインデントされており、まだ`PoolInfo`の一部であることを示しています。 + +データクラスの一部である関数では、最初のパラメータは常に`self`であり、ここで呼び出されたデータクラスのインスタンスです。 ここにはもう1つのパラメータ、ブロック番号があります。 + +```python + assert block <= w3.eth.block_number, "ブロックは未来にあります" +``` + +もし未来を読むことができれば、取引にAIは必要ありません。 + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +Web3からEVM上の関数を呼び出す構文は、`.functions.().call()`です。 パラメータは、EVM関数のパラメータ(もしあればですが、ここにはありません)、またはブロックチェーンの動作を変更するための[名前付きパラメータ](https://en.wikipedia.org/wiki/Named_parameter)にすることができます。 ここでは、`block_identifier`を使用して、実行したい[ブロック番号](/developers/docs/apis/json-rpc/#default-block)を指定します。 + +結果は[この構造体で、配列形式](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72)です。 最初の値は、2つのトークン間の為替レートの関数です。 + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +オンチェーンでの計算を減らすため、Uniswap v3は実際の為替係数ではなく、その平方根を保存します。 EVMは浮動小数点演算や分数をサポートしていないため、実際の値の代わりに、応答はprice296となります + +```python + # (token0あたりのtoken1) + return 1/(raw_price * self.decimal_factor) +``` + +取得する生の価格は、`token1`ごとに取得できる`token0`の数です。 我々のプールでは、`token0`はUSDC(米ドルと同じ価値を持つステーブルコイン)であり、`token1`は[WETH](https://opensea.io/learn/blockchain/what-is-weth)です。 私たちが本当に欲しい値は、WETHあたりのドル数であり、その逆数ではありません。 + +小数係数は、2つのトークンの[小数係数](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals)間の比率です。 + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +このデータクラスは、特定の時点での特定の資産の価格である見積もりを表します。 この時点では、単一のプールを使用しているため資産も単一であり、`asset`フィールドは無関係です。 しかし、後でさらに資産を追加します。 + +```python +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 + ) +``` + +この関数はアドレスを受け取り、そのアドレスにあるトークンコントラクトに関する情報を返します。 新しい[Web3 `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html)を作成するには、アドレスとABIを`w3.eth.contract`に提供します。 + +```python +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) + ) +``` + +この関数は、[特定のプール](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol)について必要なすべてを返します。 `f""`という構文は[フォーマット済み文字列](https://docs.python.org/3/reference/lexical_analysis.html#f-strings)です。 + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +`Quote`オブジェクトを取得します。 `block_number`のデフォルト値は`None`(値なし)です。 + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +ブロック番号が指定されなかった場合、最新のブロック番号である`w3.eth.block_number`を使用します。 これは[`if`ステートメント](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement)の構文です。 + +デフォルトを単に`w3.eth.block_number`に設定する方が良いように見えるかもしれませんが、それは関数が定義された時点のブロック番号になってしまうため、うまく機能しません。 長時間実行されるエージェントでは、これが問題になります。 + +```python + 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 + ) +``` + +[`datetime`ライブラリ](https://docs.python.org/3/library/datetime.html)を使用して、人間や大規模言語モデル(LLM)が読み取れる形式にフォーマットします。 [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize)を使用して、値を小数点以下2桁に丸めます。 + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +Pythonでは、`list[]`を使用して、特定の型のみを含むことができる[リスト](https://docs.python.org/3/library/stdtypes.html#typesseq-list)を定義します。 + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +Pythonでは、[`for`ループ](https://docs.python.org/3/tutorial/controlflow.html#for-statements)は通常、リストを反復処理します。 見積もりを検索するブロック番号のリストは、[`range`](https://docs.python.org/3/library/stdtypes.html#range)から取得します。 + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +各ブロック番号について、`Quote`オブジェクトを取得し、それを`quotes`リストに追加します。 その後、そのリストを返します。 + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +これはスクリプトのメインコードです。 プール情報を読み取り、12の見積もりを取得し、[`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint)でそれらを表示します。 + +### プロンプトの作成 {#prompt} + +次に、この見積もりリストをLLMのプロンプトに変換し、予想される将来価値を取得する必要があります。 + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +出力は、次のようなLLMへのプロンプトになります。 + +``` +以下の見積もりを前提として: +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 + + +2026-02-02T17:56の時点でWETH/USDCの値はいくらになると予想しますか? + +回答は、他のテキストを含まず、小数点以下2桁に丸めた単一の数値として提供してください。 +``` + +ここには、`WETH/USDC`と`WBTC/WETH`の2つの資産の見積もりがあることに注意してください。 別の資産からの見積もりを追加すると、予測精度が向上する可能性があります。 + +#### プロンプトの構成 {#prompt-explanation} + +このプロンプトには、LLMプロンプトで非常に一般的な3つのセクションが含まれています。 + +1. 情報。 LLMはトレーニングから多くの情報を持っていますが、通常は最新の情報を持っていません。 これが、ここで最新の見積もりを取得する必要がある理由です。 プロンプトに情報を追加することは、[検索拡張生成(RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation)と呼ばれます。 + +2. 実際の質問。 これが私たちが知りたいことです。 + +3. 出力フォーマットの指示。 通常、LLMは、どのようにしてその見積もりに至ったかの説明とともに、見積もりを提供します。 これは人間にとっては良いことですが、コンピュータプログラムには最終的な結果だけが必要です。 + +#### コードの説明 {#prompt-code} + +これが新しいコードです。 + +```python +from datetime import datetime, timezone, timedelta +``` + +LLMに見積もりを依頼したい時間を提供する必要があります。 将来の「n分/時間/日」後の時間を取得するには、[`timedelta`クラス](https://docs.python.org/3/library/datetime.html#datetime.timedelta)を使用します。 + +```python +# 読み取っているプールのアドレス +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +読み取る必要のある2つのプールがあります。 + +```python +@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 # (token1 per token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +WETH/USDCプールでは、`token1`(WETH)を1つ購入するために必要な`token0`(USDC)の数を知りたいです。 WETH/WBTCプールでは、`token0`(WBTC、ラップされたビットコイン)を1つ購入するために必要な`token1`(WETH)の数を知りたいです。 プールの比率を逆にする必要があるかどうかを追跡する必要があります。 + +```python +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 + ) +``` + +プールを逆にする必要があるかどうかを知るには、それを`read_pool`への入力として取得します。 また、資産シンボルも正しく設定する必要があります。 + +` if else `という構文は、Pythonにおける[三項条件演算子](https://en.wikipedia.org/wiki/Ternary_conditional_operator)に相当し、C派生言語では ` ? : `となります。 + +```python +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 +``` + +この関数は、`Quote`オブジェクトのリストをフォーマットする文字列を構築します。それらがすべて同じ資産に適用されることを前提としています。 + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +Pythonでは、[複数行の文字列リテラル](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp)は`"""` ....と書かれます。 `"""`。 + +```python +Given these quotes: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +ここでは、[MapReduce](https://en.wikipedia.org/wiki/MapReduce)パターンを使用して、各見積もりリストに対して`format_quotes`で文字列を生成し、それらをプロンプトで使用する単一の文字列に縮小します。 + +```python +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. + """ +``` + +プロンプトの残りの部分は期待通りです。 + +```python +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 +) +``` + +2つのプールを確認し、両方から見積もりを取得します。 + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +見積もりを依頼したい将来の時点を決定し、プロンプトを作成します。 + +### LLMとのインターフェース {#interface-llm} + +次に、実際のLLMにプロンプトを出し、予想される将来価値を受け取ります。 このプログラムはOpenAIを使用して作成したため、異なるプロバイダを使用する場合は調整する必要があります。 + +1. [OpenAIアカウント](https://auth.openai.com/create-account)を取得する + +2. [アカウントに資金を供給する](https://platform.openai.com/settings/organization/billing/overview)—執筆時点での最低額は5ドルです + +3. [APIキーを作成する](https://platform.openai.com/settings/organization/api-keys) + +4. コマンドラインで、プログラムが使用できるようにAPIキーをエクスポートします + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. エージェントをチェックアウトして実行する + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +これが新しいコードです。 + +```python +from openai import OpenAI + +open_ai = OpenAI() # クライアントはOPENAI_API_KEY環境変数を読み取ります +``` + +OpenAI APIをインポートしてインスタンス化します。 + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +OpenAI API (`open_ai.chat.completions.create`)を呼び出して応答を作成します。 + +```python +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") +``` + +価格を出力し、買いまたは売りの推奨を提供します。 + +#### 予測のテスト {#testing-the-predictions} + +予測を生成できるようになったので、履歴データを使用して、有用な予測を生成できるかどうかを評価することもできます。 + +```sh +uv run test-predictor.py +``` + +期待される結果は次のようになります。 + +``` +2026-01-05T19:50の予測: 予測値 3138.93 USD、実績値 3218.92 USD、誤差 79.99 USD +2026-01-06T19:56の予測: 予測値 3243.39 USD、実績値 3221.08 USD、誤差 22.31 USD +2026-01-07T20:02の予測: 予測値 3223.24 USD、実績値 3146.89 USD、誤差 76.35 USD +2026-01-08T20:11の予測: 予測値 3150.47 USD、実績値 3092.04 USD、誤差 58.43 USD +. +. +. +2026-01-31T22:33の予測: 予測値 2637.73 USD、実績値 2417.77 USD、誤差 219.96 USD +2026-02-01T22:41の予測: 予測値 2381.70 USD、実績値 2318.84 USD、誤差 62.86 USD +2026-02-02T22:49の予測: 予測値 2234.91 USD、実績値 2349.28 USD、誤差 114.37 USD +29回の予測における平均予測誤差: 83.87103448275862068965517241 USD +推奨あたりの平均変化: 4.787931034482758620689655172 USD +変化の標準分散: 104.42 USD +利益が出た日数: 51.72% +損失が出た日数: 48.28% +``` + +テスターのほとんどはエージェントと同じですが、ここでは新規または変更された部分を示します。 + +```python +CYCLES_FOR_TEST = 40 # バックテストでは、テストするサイクル数 + +# 大量の見積もりを取得 +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 +) +``` + +`CYCLES_FOR_TEST`(ここでは40として指定)日分遡ります。 + +```python +# 予測を作成し、実際の履歴と照合する + +total_error = Decimal(0) +changes = [] +``` + +私たちが関心を持つ誤差には2つのタイプがあります。 1つ目の`total_error`は、予測子が犯した誤差の単純な合計です。 + +2つ目の`changes`を理解するためには、エージェントの目的を思い出す必要があります。 それはWETH/USDC比率(ETH価格)を予測することではありません。 それは売りと買いの推奨を出すことです。 現在の価格が2000ドルで、明日2010ドルと予測した場合、実際の結果が2020ドルで追加の利益を得ても気にしません。 しかし、2010ドルと予測し、その推奨に基づいてETHを購入したのに、価格が1990ドルに下落した場合は気にします。 + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +完全な履歴(予測に使用された値と、比較対象の実世界の価値)が利用可能なケースのみを見ることができます。 これは、最新のケースが`CYCLES_BACK`前に始まったものでなければならないことを意味します。 + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +[スライス](https://www.w3schools.com/python/ref_func_slice.asp)を使用して、エージェントが使用するサンプル数と同じ数を取得します。 ここから次のセグメントまでのコードは、エージェントにある予測取得コードと同じです。 + +```python + 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 +``` + +予測価格、実価格、予測時点の価格を取得します。 推奨が買いか売りかを判断するために、予測時点の価格が必要です。 + +```python + 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") +``` + +誤差を計算し、合計に加えます。 + +```python + recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell' + price_increase = real_price - prediction_time_price + changes.append(price_increase if recomended_action == 'buy' else -price_increase) +``` + +`changes`については、1 ETHの売買による金銭的影響を知りたいです。 したがって、まず推奨を決定し、次に実際の価格がどのように変化したか、そして推奨が利益をもたらしたか(正の変化)、または損失をもたらしたか(負の変化)を評価する必要があります。 + +```python +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") +``` + +結果を報告します。 + +```python +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%}") +``` + +[`filter`](https://www.w3schools.com/python/ref_func_filter.asp)を使用して、利益が出た日数と損失が出た日数を数えます。 結果はフィルターオブジェクトであり、長さを取得するにはリストに変換する必要があります。 + +### トランザクションの送信 {#submit-txn} + +次に、実際にトランザクションを送信する必要があります。 しかし、システムが証明される前のこの時点では、実際のお金を使いたくありません。 代わりに、メインネットのローカルフォークを作成し、そのネットワークで「取引」します。 + +ローカルフォークを作成し、取引を有効にする手順は次のとおりです。 + +1. [Foundry](https://getfoundry.sh/introduction/installation)をインストールする + +2. [`anvil`](https://getfoundry.sh/anvil/overview)を起動する + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil`はFoundryのデフォルトURLであるhttp://localhost:8545でリッスンしているため、ブロックチェーンを操作するために使用する[`cast`コマンド](https://getfoundry.sh/cast/overview)のURLを指定する必要はありません。 + +3. `anvil`で実行すると、ETHを持つ10個のテストアカウントがあります—最初の1つの環境変数を設定します + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. これらは使用する必要のあるコントラクトです。 [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol)は、実際に取引に使用するUniswap v3コントラクトです。 プールを通じて直接取引することもできますが、こちらの方がはるかに簡単です。 + + 下の2つの変数は、WETHとUSDCの間でスワップするために必要なUniswap v3のパスです。 + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. 各テストアカウントには10,000 ETHがあります。 WETHコントラクトを使用して1000 ETHをラップし、取引のために1000 WETHを取得します。 + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. `SwapRouter`を使用して500 WETHをUSDCに取引します。 + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + `approve`呼び出しは、`SwapRouter`が私たちのトークンの一部を使うことを許可するアローワンスを作成します。 コントラクトはイベントを監視できないため、トークンを`SwapRouter`コントラクトに直接転送しても、支払われたことを認識しません。 代わりに、`SwapRouter`コントラクトが一定額を使用することを許可し、その後`SwapRouter`がそれを行います。 これは`SwapRouter`によって呼び出される関数を介して行われるため、成功したかどうかを知ることができます。 + +7. 両方のトークンが十分にあることを確認します。 + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +WETHとUSDCが手に入ったので、実際にエージェントを実行できます。 + +```sh +git checkout 05-trade +uv run agent.py +``` + +出力は次のようになります。 + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +現在価格: 1843.16 +2026-02-06T23:07時点での予測価格: 1724.41 USD +取引前の勘定残高: +USDC残高: 927301.578272 +WETH残高: 500 +売り、価格が118.75 USD下落すると予想します +承認トランザクション送信済み: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +承認トランザクションがマイニングされました。 +売りトランザクション送信済み: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +売りトランザクションがマイニングされました。 +取引後の勘定残高: +USDC残高: 929143.797116 +WETH残高: 499 +``` + +実際に使用するには、いくつかの小さな変更が必要です。 + +- 14行目で、`MAINNET_URL`を`https://eth.drpc.org`などの実際のアクセスポイントに変更します +- 28行目で、`PRIVATE_KEY`を自分の秘密鍵に変更します +- 非常に裕福で、未証明のエージェントのために毎日1 ETHを売買できる場合を除き、29行目を変更して`WETH_TRADE_AMOUNT`を減らすことをお勧めします + +#### コードの説明 {#trading-code} + +これが新しいコードです。 + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +ステップ4で使用したのと同じ変数です。 + +```python +WETH_TRADE_AMOUNT=1 +``` + +取引する量。 + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +実際に取引するには、`approve`関数が必要です。 また、前後の残高を表示したいため、`balanceOf`も必要です。 + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +`SwapRouter` ABIでは、`exactInput`だけが必要です。 関連する関数として`exactOutput`があり、これを使用して正確に1 WETHを購入できますが、簡単にするために両方のケースで`exactInput`のみを使用します。 + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +[`account`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html)と`SwapRouter`コントラクトのWeb3定義です。 + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +トランザクションパラメータ。 [ノンス](https://en.wikipedia.org/wiki/Cryptographic_nonce)は毎回変更する必要があるため、ここでは関数が必要です。 + +```python +def approve_token(contract: Contract, amount: int): +``` + +`SwapRouter`のトークンアローワンスを承認します。 + +```python + txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params()) + signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY) + tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction) +``` + +これがWeb3でトランザクションを送信する方法です。 まず、[`Contract`オブジェクト](https://web3py.readthedocs.io/en/stable/web3.contract.html)を使用してトランザクションを構築します。 次に、[`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction)を使用して、`PRIVATE_KEY`でトランザクションに署名します。 最後に、[`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction)を使用してトランザクションを送信します。 + +```python + 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`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt)は、トランザクションがマイニングされるまで待機します。 必要に応じてレシートを返します。 + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +これらはWETHを売るときのパラメータです。 + +```python +def make_buy_params(quote: Quote) -> dict: + return { + "path": USDC_TO_WETH, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals, + "amountOutMinimum": 0, + } +``` + +`SELL_PARAMS`とは対照的に、購入パラメータは変更される可能性があります。 入力額は、`quote`で利用可能な1 WETHのコストです。 + +```python +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.") +``` + +`buy()`関数と`sell()`関数はほぼ同じです。 まず`SwapRouter`に十分なアローワンスを承認し、次に正しいパスと金額でそれを呼び出します。 + +```python +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)}") +``` + +両方の通貨でのユーザー残高を報告します。 + +```python +print("取引前の勘定残高:") +balances() + +if (expected_price > current_price): + print(f"買い、価格が{expected_price - current_price} USD上昇すると予想します") + buy(wethusdc_quotes[-1]) +else: + print(f"売り、価格が{current_price - expected_price} USD下落すると予想します") + sell() + +print("取引後の勘定残高:") +balances() +``` + +このエージェントは現在一度しか動作しません。 ただし、[`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html)から実行するか、368〜400行をループでラップし、[`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep)を使用して次のサイクルまで待機することで、継続的に動作するように変更できます。 + +## 改善の可能性 {#improvements} + +これは完全な製品版ではありません。基本を教えるための単なる例です。 改善のためのいくつかのアイデアを以下に示します。 + +### よりスマートな取引 {#smart-trading} + +エージェントが何をするかを決定する際に無視している2つの重要な事実があります。 + +- _予想される変化の大きさ_。 エージェントは、下落の大きさに関係なく、価格が下落すると予想される場合に固定量の`WETH`を売却します。 + おそらく、軽微な変化は無視し、価格がどれだけ下落すると予想されるかに基づいて売却する方が良いでしょう。 +- _現在のポートフォリオ_。 ポートフォリオの10%がWETHで、価格が上がると考えている場合、さらに購入するのは理にかなっています。 しかし、ポートフォリオの90%がWETHである場合、十分にエクスポージャーがあり、さらに購入する必要はありません。 価格が下がると予想される場合は、その逆が当てはまります。 + +### 取引戦略を秘密にしたい場合はどうしますか? {#secret} + +AIベンダーは、あなたが彼らのLLMに送信するクエリを見ることができ、これによりあなたがエージェントで開発した天才的な取引システムが公開される可能性があります。 あまりにも多くの人が使用する取引システムは、あなたが買いたいときに多くの人が買おうとし(価格が上がる)、売りたいときに多くの人が売ろうとする(価格が下がる)ため、価値がありません。 + +この問題を回避するために、例えば[LM-Studio](https://lmstudio.ai/)を使用して、LLMをローカルで実行できます。 + +### AIボットからAIエージェントへ {#bot-to-agent} + +これが[AIエージェントではなくAIボットである](/ai-agents/#ai-agents-vs-ai-bots)という正当な主張ができます。 事前定義された情報に依存する、比較的単純な戦略を実装しています。 例えば、Uniswap v3プールのリストとその最新の値を提供し、どの組み合わせが最も予測価値が高いかを尋ねることで、自己改善を可能にすることができます。 + +### スリッページ保護 {#slippage-protection} + +現在、[スリッページ保護](https://uniswapv3book.com/milestone_3/slippage-protection.html)はありません。 現在の見積もりが2000ドルで、予想価格が2100ドルの場合、エージェントは購入します。 しかし、エージェントが購入する前にコストが2200ドルに上昇した場合、もはや購入する意味はありません。 + +スリッページ保護を実装するには、[`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325)の325行目と334行目で`amountOutMinimum`値を指定します。 + +## 結論 {#conclusion} + +うまくいけば、これでAIエージェントを始めるのに十分な知識が得られたはずです。 これはこの主題の包括的な概要ではありません。それには全書が捧げられていますが、これは始めるのに十分です。 健闘を祈ります! + +[私の他の作品はこちらでご覧いただけます](https://cryptodocguy.pro/). diff --git a/public/content/translations/ko/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/ko/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..bf4d7dcb6c7 --- /dev/null +++ b/public/content/translations/ko/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "이더리움에서 자신만의 AI 트레이딩 에이전트 만들기" +description: "이 튜토리얼에서는 간단한 AI 트레이딩 에이전트를 만드는 방법을 배웁니다. 이 에이전트는 블록체인에서 정보를 읽고, 해당 정보를 기반으로 LLM에 추천을 요청하고, LLM이 추천하는 교환을 수행한 후, 대기하고 반복합니다." +author: Ori Pomerantz +tags: [ "AI", "트레이딩", "에이전트", "python" ] +skill: intermediate +published: 2026-02-13 +lang: ko +sidebarDepth: 3 +--- + +이 튜토리얼에서는 간단한 AI 트레이딩 에이전트를 구축하는 방법을 배웁니다. 이 에이전트는 다음 단계를 사용하여 작동합니다. + +1. 토큰의 현재 및 과거 가격과 기타 잠재적으로 관련 있는 정보를 읽습니다. +2. 이 정보와 함께 그 관련성을 설명하는 배경 정보로 쿼리를 작성합니다. +3. 쿼리를 제출하고 예상 가격을 받습니다. +4. 추천에 따라 교환합니다. +5. 대기하고 반복합니다. + +이 에이전트는 정보를 읽고, 사용 가능한 답변을 생성하는 쿼리로 변환하고, 해당 답변을 사용하는 방법을 보여줍니다. 이 모든 단계는 AI 에이전트에 필요합니다. 이 에이전트는 Python으로 구현되었습니다. Python이 AI에서 가장 일반적으로 사용되는 언어이기 때문입니다. + +## 왜 이렇게 해야 할까요? {#why-do-this} + +자동화된 트레이딩 에이전트는 개발자가 트레이딩 전략을 선택하고 실행할 수 있도록 합니다. [AI 에이전트](/ai-agents)는 개발자가 사용을 고려조차 하지 않았던 정보와 알고리즘을 잠재적으로 사용하여 더 복잡하고 동적인 트레이딩 전략을 가능하게 합니다. + +## 도구 {#tools} + +이 튜토리얼은 시세 조회 및 트레이딩을 위해 [Python](https://www.python.org/), [Web3 라이브러리](https://web3py.readthedocs.io/en/stable/), [Uniswap v3](https://github.com/Uniswap/v3-periphery)를 사용합니다. + +### 왜 Python인가요? {#python} + +AI에 가장 널리 사용되는 언어는 [Python](https://www.python.org/)이므로 여기서도 사용합니다. Python을 모르더라도 걱정하지 마세요. 언어가 매우 명확하며, 무엇을 하는지 정확히 설명해 드리겠습니다. + +[Web3 라이브러리](https://web3py.readthedocs.io/en/stable/)는 가장 일반적인 Python 이더리움 API입니다. 사용하기가 매우 쉽습니다. + +### 블록체인에서 트레이딩하기 {#trading-on-blockchain} + +이더리움에서 토큰을 교환할 수 있는 [많은 분산형 거래소(DEX)](/apps/categories/defi/)가 있습니다. 하지만 [차익거래](/developers/docs/smart-contracts/composability/#better-user-experience) 때문에 환율은 비슷한 경향이 있습니다. + +[Uniswap](https://app.uniswap.org/)은 널리 사용되는 DEX이며, 시세 조회(토큰 상대 가치 확인)와 교환 모두에 사용할 수 있습니다. + +### OpenAI {#openai} + +대규모 언어 모델의 경우, [OpenAI](https://openai.com/)로 시작하기로 했습니다. 이 튜토리얼의 애플리케이션을 실행하려면 API 액세스 비용을 지불해야 합니다. 최소 결제 금액인 5달러는 충분하고도 남습니다. + +## 단계별 개발 {#step-by-step} + +개발을 단순화하기 위해 단계별로 진행합니다. 각 단계는 GitHub의 브랜치입니다. + +### 시작하기 {#getting-started} + +UNIX 또는 Linux([WSL](https://learn.microsoft.com/en-us/windows/wsl/install) 포함)에서 시작하는 단계가 있습니다. + +1. 아직 설치하지 않았다면 [Python](https://www.python.org/downloads/)을 다운로드하여 설치하세요. + +2. GitHub 리포지토리를 복제합니다. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. [`uv`](https://docs.astral.sh/uv/getting-started/installation/)를 설치합니다. 시스템에 따라 명령어가 다를 수 있습니다. + + ```sh + pipx install uv + ``` + +4. 라이브러리를 다운로드합니다. + + ```sh + uv sync + ``` + +5. 가상 환경을 활성화합니다. + + ```sh + source .venv/bin/activate + ``` + +6. Python과 Web3가 올바르게 작동하는지 확인하려면 `python3`을 실행하고 이 프로그램을 제공하세요. `>>>` 프롬프트에 입력할 수 있으며 파일을 만들 필요는 없습니다. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### 블록체인에서 읽기 {#read-blockchain} + +다음 단계는 블록체인에서 읽는 것입니다. 그렇게 하려면 `02-read-quote` 브랜치로 변경한 다음 `uv`를 사용하여 프로그램을 실행해야 합니다. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +타임스탬프, 가격, 자산(현재는 항상 `WETH/USDC`)을 포함하는 `Quote` 객체 목록을 받게 됩니다. + +다음은 한 줄씩 설명한 것입니다. + +```python +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 +``` + +필요한 라이브러리를 가져옵니다. 사용 시 아래에 설명되어 있습니다. + +```python +print = functools.partial(print, flush=True) +``` + +Python의 `print`를 항상 출력을 즉시 플러시하는 버전으로 바꿉니다. 상태 업데이트나 디버깅 출력을 기다릴 필요가 없으므로 장기 실행 스크립트에서 유용합니다. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +메인넷에 접속하기 위한 URL입니다. [서비스형 노드](/developers/docs/nodes-and-clients/nodes-as-a-service/)에서 얻거나 [Chainlist](https://chainlist.org/chain/1)에 광고된 것 중 하나를 사용할 수 있습니다. + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +이더리움 메인넷 블록은 일반적으로 12초마다 생성되므로, 이는 특정 기간에 생성될 것으로 예상되는 블록 수입니다. 이것은 정확한 수치가 아닙니다. [블록 제안자](/developers/docs/consensus-mechanisms/pos/block-proposal/)가 다운되면 해당 블록은 건너뛰어지고 다음 블록까지의 시간은 24초가 됩니다. 타임스탬프에 대한 정확한 블록을 얻으려면 [이진 검색](https://en.wikipedia.org/wiki/Binary_search)을 사용합니다. 하지만, 우리의 목적에는 이 정도로도 충분합니다. 미래를 예측하는 것은 정확한 과학이 아닙니다. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +주기의 크기입니다. 주기당 한 번씩 시세를 검토하고 다음 주기 말의 가치를 추정합니다. + +```python +# 읽고 있는 풀의 주소 +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +시세 값은 [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract) 주소의 Uniswap 3 USDC/WETH 풀에서 가져옵니다. 이 주소는 이미 체크섬 형식이지만, 코드 재사용성을 높이려면 [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address)를 사용하는 것이 좋습니다. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +이는 우리가 연결해야 하는 두 계약의 [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html)입니다. 코드를 간결하게 유지하기 위해 호출해야 하는 함수만 포함합니다. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +[`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) 라이브러리를 시작하고 이더리움 노드에 연결합니다. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +이것은 Python에서 데이터 클래스를 만드는 한 가지 방법입니다. [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) 데이터 유형은 계약에 연결하는 데 사용됩니다. `(frozen=True)`에 주목하세요. Python에서 [불리언](https://en.wikipedia.org/wiki/Boolean_data_type)은 대문자로 시작하는 `True` 또는 `False`로 정의됩니다. 이 데이터 클래스는 `frozen`이며, 필드를 수정할 수 없음을 의미합니다. + +들여쓰기에 주목하세요. [C 파생 언어](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages)와 달리 Python은 들여쓰기를 사용하여 블록을 나타냅니다. Python 인터프리터는 다음 정의가 데이터 클래스 필드와 동일한 들여쓰기에서 시작하지 않기 때문에 이 데이터 클래스의 일부가 아님을 압니다. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +[`Decimal`](https://docs.python.org/3/library/decimal.html) 유형은 십진 분수를 정확하게 처리하는 데 사용됩니다. + +```python + def get_price(self, block: int) -> Decimal: +``` + +이것이 Python에서 함수를 정의하는 방법입니다. 이 정의는 여전히 `PoolInfo`의 일부임을 보여주기 위해 들여쓰기됩니다. + +데이터 클래스의 일부인 함수에서 첫 번째 매개변수는 항상 여기에서 호출된 데이터 클래스 인스턴스인 `self`입니다. 여기에는 블록 번호라는 또 다른 매개변수가 있습니다. + +```python + assert block <= w3.eth.block_number, "블록이 미래에 있습니다" +``` + +미래를 읽을 수 있다면 트레이딩에 AI가 필요하지 않을 것입니다. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +Web3에서 EVM의 함수를 호출하는 구문은 다음과 같습니다. `.functions."().call()`. 매개변수는 EVM 함수의 매개변수(있는 경우, 여기서는 없음)이거나 블록체인 동작을 수정하기 위한 [명명된 매개변수](https://en.wikipedia.org/wiki/Named_parameter)일 수 있습니다. 여기서는 `block_identifier`를 사용하여 실행하려는 [블록 번호](/developers/docs/apis/json-rpc/#default-block)를 지정합니다. + +결과는 [배열 형식의 이 구조체](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72)입니다. 첫 번째 값은 두 토큰 간의 환율 함수입니다. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +온체인 계산을 줄이기 위해 Uniswap v3는 실제 환율 요소가 아닌 제곱근을 저장합니다. EVM은 부동 소수점 연산이나 분수를 지원하지 않으므로 실제 값 대신 응답은 price296입니다. + +```python + # (토큰 0당 토큰 1) + return 1/(raw_price * self.decimal_factor) +``` + +우리가 얻는 원시 가격은 각 `token1`에 대해 얻는 `token0`의 수입니다. 우리 풀에서 `token0`은 USDC(미국 달러와 동일한 가치를 지닌 스테이블 코인)이고 `token1`은 [WETH](https://opensea.io/learn/blockchain/what-is-weth)입니다. 우리가 정말로 원하는 값은 WETH당 달러 수이지 그 역수가 아닙니다. + +소수점 계수는 두 토큰의 [소수점 계수](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) 간의 비율입니다. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +이 데이터 클래스는 시세, 즉 특정 시점의 특정 자산 가격을 나타냅니다. 이 시점에서는 단일 풀을 사용하므로 단일 자산만 있으므로 `asset` 필드는 관련이 없습니다. 하지만 나중에 더 많은 자산을 추가할 것입니다. + +```python +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 + ) +``` + +이 함수는 주소를 받아 해당 주소에 있는 토큰 계약에 대한 정보를 반환합니다. 새로운 [Web3 `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html)를 만들려면 `w3.eth.contract`에 주소와 ABI를 제공합니다. + +```python +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) + ) +``` + +이 함수는 [특정 풀](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol)에 대해 필요한 모든 것을 반환합니다. `f""` 구문은 [포맷된 문자열](https://docs.python.org/3/reference/lexical_analysis.html#f-strings)입니다. + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +`Quote` 객체를 가져옵니다. `block_number`의 기본값은 `None`(값 없음)입니다. + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +블록 번호가 지정되지 않은 경우 최신 블록 번호인 `w3.eth.block_number`를 사용합니다. 이것은 [`if` 문](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement)의 구문입니다. + +기본값을 `w3.eth.block_number`로 설정하는 것이 더 나아 보일 수 있지만, 함수가 정의된 시점의 블록 번호가 되기 때문에 잘 작동하지 않습니다. 장기 실행 에이전트에서는 이것이 문제가 될 수 있습니다. + +```python + 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 + ) +``` + +[`datetime` 라이브러리](https://docs.python.org/3/library/datetime.html)를 사용하여 사람과 대규모 언어 모델(LLM)이 읽을 수 있는 형식으로 포맷합니다. [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize)를 사용하여 값을 소수점 이하 두 자리로 반올림합니다. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +Python에서는 `list[]`를 사용하여 특정 유형만 포함할 수 있는 [목록](https://docs.python.org/3/library/stdtypes.html#typesseq-list)을 정의합니다. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +Python에서 [`for` 루프](https://docs.python.org/3/tutorial/controlflow.html#for-statements)는 일반적으로 목록을 반복합니다. 시세를 찾을 블록 번호 목록은 [`range`](https://docs.python.org/3/library/stdtypes.html#range)에서 가져옵니다. + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +각 블록 번호에 대해 `Quote` 객체를 가져와 `quotes` 목록에 추가합니다. 그런 다음 해당 목록을 반환합니다. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +이것은 스크립트의 메인 코드입니다. 풀 정보를 읽고, 12개의 시세를 가져와 [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint)로 출력합니다. + +### 프롬프트 만들기 {#prompt} + +다음으로, 이 시세 목록을 LLM용 프롬프트로 변환하고 예상 미래 가치를 얻어야 합니다. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +이제 출력은 다음과 유사한 LLM에 대한 프롬프트가 됩니다. + +``` +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. +``` + +여기에 `WETH/USDC`와 `WBTC/WETH`라는 두 자산에 대한 시세가 있다는 점에 주목하세요. 다른 자산의 시세를 추가하면 예측 정확도가 향상될 수 있습니다. + +#### 프롬프트는 어떻게 생겼나요? {#prompt-explanation} + +이 프롬프트에는 LLM 프롬프트에서 흔히 볼 수 있는 세 가지 섹션이 포함되어 있습니다. + +1. 정보입니다. LLM은 훈련을 통해 많은 정보를 가지고 있지만, 보통 최신 정보는 가지고 있지 않습니다. 이것이 바로 여기서 최신 시세를 검색해야 하는 이유입니다. 프롬프트에 정보를 추가하는 것을 [검색 증강 생성(RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation)이라고 합니다. + +2. 실제 질문입니다. 이것이 우리가 알고 싶은 것입니다. + +3. 출력 형식 지정 지침입니다. 일반적으로 LLM은 추정치와 그에 도달한 방법에 대한 설명을 제공합니다. 이는 사람에게는 더 좋지만, 컴퓨터 프로그램에는 결론만 필요합니다. + +#### 코드 설명 {#prompt-code} + +다음은 새로운 코드입니다. + +```python +from datetime import datetime, timezone, timedelta +``` + +추정치를 원하는 시간을 LLM에 제공해야 합니다. 미래의 "n분/시간/일" 후의 시간을 얻으려면 [`timedelta` 클래스](https://docs.python.org/3/library/datetime.html#datetime.timedelta)를 사용합니다. + +```python +# 읽고 있는 풀의 주소 +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +읽어야 할 풀이 두 개 있습니다. + +```python +@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 # (token1 per token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +WETH/USDC 풀에서 우리는 `token1`(WETH) 하나를 사기 위해 `token0`(USD 코인)이 몇 개 필요한지 알고 싶습니다. WETH/WBTC 풀에서 우리는 `token0`(WBTC, 래핑된 비트코인) 하나를 사기 위해 `token1`(WETH)이 몇 개 필요한지 알고 싶습니다. 풀의 비율을 역전시켜야 하는지 추적해야 합니다. + +```python +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 + ) +``` + +풀을 역전시켜야 하는지 알기 위해 `read_pool`에 입력으로 전달해야 합니다. 또한, 자산 기호를 올바르게 설정해야 합니다. + +` if else ` 구문은 Python의 [삼항 조건 연산자](https://en.wikipedia.org/wiki/Ternary_conditional_operator)에 해당하며, C 파생 언어에서는 ` ? : `가 됩니다. + +```python +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 +``` + +이 함수는 `Quote` 객체 목록의 형식을 지정하는 문자열을 만듭니다. 이때 모든 객체가 동일한 자산에 적용된다고 가정합니다. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +Python에서 [여러 줄 문자열 리터럴](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp)은 `"""` ....로 작성됩니다. `"""`. + +```python +Given these quotes: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +여기서는 [맵리듀스](https://en.wikipedia.org/wiki/MapReduce) 패턴을 사용하여 각 시세 목록에 대해 `format_quotes`로 문자열을 생성한 다음, 이를 단일 문자열로 축소하여 프롬프트에 사용합니다. + +```python +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. + """ +``` + +프롬프트의 나머지 부분은 예상대로입니다. + +```python +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 +) +``` + +두 풀을 검토하고 양쪽에서 시세를 얻습니다. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +추정치를 원하는 미래 시점을 결정하고 프롬프트를 만듭니다. + +### LLM과 인터페이스하기 {#interface-llm} + +다음으로, 실제 LLM에 프롬프트를 보내고 예상 미래 가치를 받습니다. 이 프로그램은 OpenAI를 사용하여 작성되었으므로 다른 제공업체를 사용하려면 수정해야 합니다. + +1. [OpenAI 계정](https://auth.openai.com/create-account) 만들기 + +2. [계정 자금 조달](https://platform.openai.com/settings/organization/billing/overview) — 이 글을 쓰는 시점의 최소 금액은 5달러입니다. + +3. [API 키 만들기](https://platform.openai.com/settings/organization/api-keys) + +4. 명령줄에서 프로그램이 사용할 수 있도록 API 키를 내보냅니다. + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. 에이전트 체크아웃 및 실행 + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +다음은 새로운 코드입니다. + +```python +from openai import OpenAI + +open_ai = OpenAI() # The client reads the OPENAI_API_KEY environment variable +``` + +OpenAI API를 가져와 인스턴스화합니다. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +OpenAI API(`open_ai.chat.completions.create`)를 호출하여 응답을 생성합니다. + +```python +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") +``` + +가격을 출력하고 매수 또는 매도 추천을 제공합니다. + +#### 예측 테스트하기 {#testing-the-predictions} + +이제 예측을 생성할 수 있으므로 과거 데이터를 사용하여 유용한 예측을 생성하는지 평가할 수도 있습니다. + +```sh +uv run test-predictor.py +``` + +예상 결과는 다음과 유사합니다. + +``` +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% +``` + +대부분의 테스터는 에이전트와 동일하지만, 새롭거나 수정된 부분은 다음과 같습니다. + +```python +CYCLES_FOR_TEST = 40 # For the backtest, how many cycles we test over + +# Get lots of quotes +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 +) +``` + +우리는 `CYCLES_FOR_TEST`(여기서는 40으로 지정)일 전을 살펴봅니다. + +```python +# Create predictions and check them against real history + +total_error = Decimal(0) +changes = [] +``` + +우리가 관심 있는 오류에는 두 가지 유형이 있습니다. 첫 번째, `total_error`는 예측기가 만든 오류의 합계입니다. + +두 번째, `changes`를 이해하려면 에이전트의 목적을 기억해야 합니다. WETH/USDC 비율(ETH 가격)을 예측하는 것이 아닙니다. 매도 및 매수 추천을 발행하는 것입니다. 현재 가격이 2000달러이고 내일 2010달러를 예측한다면, 실제 결과가 2020달러가 되어 추가 수익을 얻는 것은 상관없습니다. 하지만 2010달러를 예측하고 그 추천에 따라 ETH를 매수했는데 가격이 1990달러로 떨어지면 문제가 됩니다. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +전체 기록(예측에 사용된 값과 비교할 실제 값)이 사용 가능한 경우만 볼 수 있습니다. 이는 가장 최신 사례가 `CYCLES_BACK` 전에 시작된 것이어야 함을 의미합니다. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +[슬라이스](https://www.w3schools.com/python/ref_func_slice.asp)를 사용하여 에이전트가 사용하는 것과 동일한 수의 샘플을 얻습니다. 여기서부터 다음 세그먼트까지의 코드는 에이전트에 있는 예측을 얻는 코드와 동일합니다. + +```python + 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 +``` + +예측 가격, 실제 가격, 예측 시점의 가격을 가져옵니다. 추천이 매수인지 매도인지 결정하려면 예측 시점의 가격이 필요합니다. + +```python + 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") +``` + +오류를 계산하고 총계에 더합니다. + +```python + recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell' + price_increase = real_price - prediction_time_price + changes.append(price_increase if recomended_action == 'buy' else -price_increase) +``` + +`changes`의 경우, 1ETH를 매수하거나 매도할 때의 금전적 영향을 원합니다. 따라서 먼저 추천을 결정한 다음, 실제 가격이 어떻게 변했는지, 그리고 추천이 수익을 냈는지(양의 변화) 또는 손실을 입혔는지(음의 변화) 평가해야 합니다. + +```python +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") +``` + +결과를 보고합니다. + +```python +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%}") +``` + +[`filter`](https://www.w3schools.com/python/ref_func_filter.asp)를 사용하여 수익성 있는 날의 수와 손실이 발생한 날의 수를 셉니다. 결과는 필터 객체이며, 길이를 얻으려면 목록으로 변환해야 합니다. + +### 트랜잭션 제출하기 {#submit-txn} + +이제 실제로 트랜잭션을 제출해야 합니다. 하지만 시스템이 입증되기 전인 이 시점에서는 실제 돈을 쓰고 싶지 않습니다. 대신, 메인넷의 로컬 포크를 만들고 해당 네트워크에서 "교환"할 것입니다. + +다음은 로컬 포크를 만들고 트레이딩을 활성화하는 단계입니다. + +1. [Foundry](https://getfoundry.sh/introduction/installation) 설치 + +2. [`anvil`](https://getfoundry.sh/anvil/overview) 시작 + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil`은 Foundry의 기본 URL인 http://localhost:8545에서 수신 대기하므로 블록체인을 조작하는 데 사용하는 [`cast` 명령](https://getfoundry.sh/cast/overview)에 대한 URL을 지정할 필요가 없습니다. + +3. `anvil`에서 실행할 때 ETH가 있는 10개의 테스트 계정이 있습니다. 첫 번째 계정에 대한 환경 변수를 설정합니다. + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. 사용해야 하는 계약은 다음과 같습니다. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol)는 실제로 교환하는 데 사용하는 Uniswap v3 계약입니다. 풀을 통해 직접 교환할 수도 있지만, 이 방법이 훨씬 쉽습니다. + + 아래 두 변수는 WETH와 USDC 간에 교환하는 데 필요한 Uniswap v3 경로입니다. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. 각 테스트 계정에는 10,000 ETH가 있습니다. WETH 계약을 사용하여 1000 ETH를 래핑하여 트레이딩에 사용할 1000 WETH를 얻습니다. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. `SwapRouter`를 사용하여 500 WETH를 USD 코인으로 교환합니다. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + `approve` 호출은 `SwapRouter`가 우리 토큰의 일부를 사용할 수 있도록 허용하는 허용량을 생성합니다. 계약은 이벤트를 모니터링할 수 없으므로 토큰을 `SwapRouter` 계약으로 직접 전송하면 지급되었는지 알 수 없습니다. 대신 `SwapRouter` 계약이 특정 금액을 사용하도록 허용한 다음 `SwapRouter`가 이를 수행합니다. 이는 `SwapRouter`가 호출하는 함수를 통해 수행되므로 성공 여부를 알 수 있습니다. + +7. 두 토큰이 모두 충분한지 확인합니다. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +이제 WETH와 USD 코인이 있으므로 실제로 에이전트를 실행할 수 있습니다. + +```sh +git checkout 05-trade +uv run agent.py +``` + +출력은 다음과 유사합니다. + +``` +(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 +``` + +실제로 사용하려면 몇 가지 사소한 변경이 필요합니다. + +- 14행에서 `MAINNET_URL`을 `https://eth.drpc.org`와 같은 실제 액세스 포인트로 변경합니다. +- 28행에서 `PRIVATE_KEY`를 자신의 개인 키로 변경합니다. +- 입증되지 않은 에이전트를 위해 매일 1 ETH를 사거나 팔 수 있을 만큼 부유하지 않다면 29행을 변경하여 `WETH_TRADE_AMOUNT`를 줄이는 것이 좋습니다. + +#### 코드 설명 {#trading-code} + +다음은 새로운 코드입니다. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +4단계에서 사용한 것과 동일한 변수입니다. + +```python +WETH_TRADE_AMOUNT=1 +``` + +교환할 금액입니다. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +실제로 교환하려면 `approve` 함수가 필요합니다. 또한 잔액을 전후로 보여주고 싶으므로 `balanceOf`도 필요합니다. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +`SwapRouter` ABI에서는 `exactInput`만 필요합니다. 관련 함수인 `exactOutput`을 사용하여 정확히 1 WETH를 구매할 수 있지만, 단순화를 위해 두 경우 모두 `exactInput`만 사용합니다. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +[`계정`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html)과 `SwapRouter` 계약에 대한 Web3 정의입니다. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +트랜잭션 매개변수입니다. [논스](https://en.wikipedia.org/wiki/Cryptographic_nonce)는 매번 변경되어야 하므로 여기에 함수가 필요합니다. + +```python +def approve_token(contract: Contract, amount: int): +``` + +`SwapRouter`에 대한 토큰 허용량을 승인합니다. + +```python + txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params()) + signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY) + tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction) +``` + +이것이 Web3에서 트랜잭션을 보내는 방법입니다. 먼저 [`Contract` 객체](https://web3py.readthedocs.io/en/stable/web3.contract.html)를 사용하여 트랜잭션을 빌드합니다. 그런 다음 [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction)을 사용하여 `PRIVATE_KEY`로 트랜잭션에 서명합니다. 마지막으로 [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction)을 사용하여 트랜잭션을 보냅니다. + +```python + 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`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt)는 트랜잭션이 채굴될 때까지 기다립니다. 필요한 경우 영수증을 반환합니다. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +WETH를 판매할 때의 매개변수입니다. + +```python +def make_buy_params(quote: Quote) -> dict: + return { + "path": USDC_TO_WETH, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals, + "amountOutMinimum": 0, + } +``` + +`SELL_PARAMS`와 달리 구매 매개변수는 변경될 수 있습니다. 입력 금액은 `quote`에서 사용할 수 있는 1 WETH의 비용입니다. + +```python +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.") +``` + +`buy()` 및 `sell()` 함수는 거의 동일합니다. 먼저 `SwapRouter`에 대한 충분한 허용량을 승인한 다음 올바른 경로와 금액으로 호출합니다. + +```python +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)}") +``` + +두 통화의 사용자 잔액을 보고합니다. + +```python +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() +``` + +이 에이전트는 현재 한 번만 작동합니다. 하지만 [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html)에서 실행하거나 368-400행을 루프로 감싸고 [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep)를 사용하여 다음 주기가 될 때까지 기다리도록 변경하여 계속 작동하게 할 수 있습니다. + +## 개선 가능성 {#improvements} + +이것은 완전한 프로덕션 버전이 아니며, 단지 기본 사항을 가르치기 위한 예시일 뿐입니다. 다음은 개선을 위한 몇 가지 아이디어입니다. + +### 더 스마트한 트레이딩 {#smart-trading} + +에이전트가 무엇을 할지 결정할 때 무시하는 두 가지 중요한 사실이 있습니다. + +- _예상되는 변화의 크기_. 에이전트는 가격이 하락할 것으로 예상되면 하락의 크기에 관계없이 고정된 양의 `WETH`를 판매합니다. + 사소한 변화는 무시하고 가격이 얼마나 하락할 것으로 예상되는지에 따라 판매하는 것이 더 나을 것입니다. +- _현재 포트폴리오_. 포트폴리오의 10%가 WETH이고 가격이 오를 것이라고 생각한다면 더 많이 사는 것이 합리적일 수 있습니다. 그러나 포트폴리오의 90%가 WETH인 경우, 이미 충분히 노출되어 있으므로 더 이상 구매할 필요가 없습니다. 가격이 하락할 것으로 예상하면 그 반대입니다. + +### 트레이딩 전략을 비밀로 유지하고 싶다면 어떻게 해야 할까요? {#secret} + +AI 공급업체는 LLM에 보내는 쿼리를 볼 수 있으며, 이는 에이전트로 개발한 천재적인 트레이딩 시스템을 노출시킬 수 있습니다. 너무 많은 사람이 사용하는 트레이딩 시스템은 가치가 없습니다. 왜냐하면 사고 싶을 때 너무 많은 사람이 사려고 해서 가격이 오르고, 팔고 싶을 때 너무 많은 사람이 팔려고 해서 가격이 내려가기 때문입니다. + +이 문제를 피하기 위해 [LM-Studio](https://lmstudio.ai/) 등을 사용하여 로컬에서 LLM을 실행할 수 있습니다. + +### AI 봇에서 AI 에이전트로 {#bot-to-agent} + +이것이 [AI 에이전트가 아닌 AI 봇](/ai-agents/#ai-agents-vs-ai-bots)이라는 좋은 주장을 할 수 있습니다. 미리 정의된 정보에 의존하는 비교적 간단한 전략을 구현합니다. 예를 들어, Uniswap v3 풀 목록과 최신 값을 제공하고 어떤 조합이 가장 좋은 예측 가치를 갖는지 물어봄으로써 자기 개선을 가능하게 할 수 있습니다. + +### 슬리피지 보호 {#slippage-protection} + +현재 [슬리피지 보호](https://uniswapv3book.com/milestone_3/slippage-protection.html) 기능이 없습니다. 현재 시세가 2000달러이고 예상 가격이 2100달러라면 에이전트는 매수할 것입니다. 그러나 에이전트가 매수하기 전에 비용이 2200달러로 오르면 더 이상 매수할 의미가 없습니다. + +슬리피지 보호를 구현하려면 [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325)의 325행과 334행에 `amountOutMinimum` 값을 지정합니다. + +## 결론 {#conclusion} + +이제 AI 에이전트를 시작하는 데 충분한 정보를 얻으셨기를 바랍니다. 이것은 이 주제에 대한 포괄적인 개요가 아니며, 전체 책이 그에 할애되어 있지만, 시작하기에는 충분합니다. 행운을 빕니다! + +[여기서 제 작업에 대한 자세한 내용을 확인하세요](https://cryptodocguy.pro/). diff --git a/public/content/translations/mr/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/mr/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..e0795d7688d --- /dev/null +++ b/public/content/translations/mr/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "Ethereum वर तुमचा स्वतःचा AI ट्रेडिंग एजंट बनवा" +description: "या ट्यूटोरियलमध्ये तुम्ही एक साधा AI ट्रेडिंग एजंट कसा बनवायचा हे शिकाल. हा एजंट ब्लॉकचेनवरून माहिती वाचतो, त्या माहितीवर आधारित शिफारशीसाठी LLM ला विचारतो, LLM ने शिफारस केलेला ट्रेड करतो आणि नंतर थांबतो व पुनरावृत्ती करतो." +author: Ori Pomerantz +tags: [ "AI", "ट्रेडिंग", "एजंट", "python" ] +skill: intermediate +published: 2026-02-13 +lang: mr +sidebarDepth: 3 +--- + +या ट्यूटोरियलमध्ये तुम्ही एक साधा AI ट्रेडिंग एजंट कसा बनवायचा हे शिकाल. हा एजंट या पायऱ्या वापरून काम करतो: + +1. एका टोकनच्या वर्तमान आणि भूतकाळातील किमती, तसेच इतर संभाव्य संबंधित माहिती वाचा +2. ही माहिती संबंधित कशी असू शकते हे स्पष्ट करण्यासाठी पार्श्वभूमी माहितीसह या माहितीसह एक क्वेरी तयार करा +3. क्वेरी सबमिट करा आणि अंदाजित किंमत परत मिळवा +4. शिफारशीवर आधारित ट्रेड करा +5. थांबा आणि पुनरावृत्ती करा + +हा एजंट माहिती कशी वाचावी, त्याचे रूपांतर एका क्वेरीमध्ये कसे करावे जे वापरण्यायोग्य उत्तर देते आणि ते उत्तर कसे वापरावे हे दाखवतो. AI एजंटसाठी या सर्व आवश्यक पायऱ्या आहेत. हा एजंट Python मध्ये लागू केला आहे कारण AI मध्ये वापरली जाणारी ही सर्वात सामान्य भाषा आहे. + +## हे का करायचे? {#why-do-this} + +स्वयंचलित ट्रेडिंग एजंट्स डेव्हलपरना ट्रेडिंग स्ट्रॅटेजी निवडण्याची आणि कार्यान्वित करण्याची परवानगी देतात. [AI एजंट](/ai-agents) अधिक जटिल आणि डायनॅमिक ट्रेडिंग स्ट्रॅटेजीसाठी परवानगी देतात, संभाव्यतः अशी माहिती आणि अल्गोरिदम वापरून ज्याचा वापर करण्याचा डेव्हलपरने विचारही केला नसेल. + +## टूल्स {#tools} + +हे ट्यूटोरियल कोट्स आणि ट्रेडिंगसाठी [Python](https://www.python.org/), [Web3 लायब्ररी](https://web3py.readthedocs.io/en/stable/), आणि [Uniswap v3](https://github.com/Uniswap/v3-periphery) वापरते. + +### Python का? {#python} + +AI साठी सर्वात जास्त वापरली जाणारी भाषा [Python](https://www.python.org/) आहे, म्हणून आम्ही येथे ती वापरतो. तुम्हाला Python येत नसेल तरी काळजी करू नका. भाषा खूप स्पष्ट आहे, आणि मी ते नक्की काय करते हे स्पष्ट करेन. + +[Web3 लायब्ररी](https://web3py.readthedocs.io/en/stable/) ही सर्वात सामान्य Python Ethereum API आहे. हे वापरण्यास खूप सोपे आहे. + +### ब्लॉकचेनवर ट्रेडिंग {#trading-on-blockchain} + +असे [अनेक डिस्ट्रिब्युटेड एक्सचेंजेस (DEX)](/apps/categories/defi/) आहेत जे तुम्हाला Ethereum वर टोकन ट्रेड करण्याची परवानगी देतात. तथापि, [आर्बिट्राज](/developers/docs/smart-contracts/composability/#better-user-experience) मुळे त्यांचे एक्सचेंज दर समान असतात. + +[Uniswap](https://app.uniswap.org/) हा एक मोठ्या प्रमाणावर वापरला जाणारा DEX आहे जो आपण कोट्स (टोकनचे सापेक्ष मूल्य पाहण्यासाठी) आणि ट्रेड दोन्हीसाठी वापरू शकतो. + +### OpenAI {#openai} + +लार्ज लँग्वेज मॉडेलसाठी, मी [OpenAI](https://openai.com/) सह सुरुवात करणे निवडले. या ट्यूटोरियलमधील ॲप्लिकेशन चालवण्यासाठी तुम्हाला API ऍक्सेससाठी पैसे द्यावे लागतील. $5 चे किमान पेमेंट पुरेसे आहे. + +## विकास, टप्प्याटप्प्याने {#step-by-step} + +विकास सुलभ करण्यासाठी, आम्ही टप्प्याटप्प्याने पुढे जातो. प्रत्येक पायरी GitHub मधील एक शाखा आहे. + +### सुरुवात करणे {#getting-started} + +UNIX किंवा Linux अंतर्गत ( [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) सह) सुरुवात करण्यासाठी पायऱ्या आहेत + +1. तुमच्याकडे आधीच नसल्यास, [Python](https://www.python.org/downloads/) डाउनलोड आणि इंस्टॉल करा. + +2. GitHub रिपॉझिटरी क्लोन करा. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. [`uv`](https://docs.astral.sh/uv/getting-started/installation/) इंस्टॉल करा. तुमच्या सिस्टमवरील कमांड वेगळी असू शकते. + + ```sh + pipx install uv + ``` + +4. लायब्ररी डाउनलोड करा. + + ```sh + uv sync + ``` + +5. व्हर्च्युअल एन्व्हायर्नमेंट सक्रिय करा. + + ```sh + source .venv/bin/activate + ``` + +6. Python आणि Web3 योग्यरित्या काम करत आहेत की नाही हे तपासण्यासाठी, `python3` चालवा आणि त्याला हा प्रोग्राम द्या. तुम्ही ते `>>>` प्रॉम्प्टवर टाकू शकता; फाईल तयार करण्याची गरज नाही. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### ब्लॉकचेनवरून वाचन {#read-blockchain} + +पुढील पायरी म्हणजे ब्लॉकचेनवरून वाचणे. ते करण्यासाठी, तुम्हाला `02-read-quote` शाखेत बदल करणे आणि नंतर प्रोग्राम चालवण्यासाठी `uv` वापरणे आवश्यक आहे. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +तुम्हाला `Quote` ऑब्जेक्टची एक यादी मिळेल, प्रत्येकामध्ये टाइमस्टॅम्प, किंमत आणि मालमत्ता (सध्या नेहमी `WETH/USDC`) असेल. + +येथे ओळीनुसार स्पष्टीकरण दिले आहे. + +```python +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 +``` + +आपल्याला आवश्यक असलेल्या लायब्ररी आयात करा. जेव्हा ते वापरले जातात तेव्हा ते खाली स्पष्ट केले आहेत. + +```python +print = functools.partial(print, flush=True) +``` + +Python च्या `print` ला अशा आवृत्तीने बदलते जी नेहमी आउटपुट त्वरित फ्लश करते. हे दीर्घकाळ चालणाऱ्या स्क्रिप्टमध्ये उपयुक्त आहे कारण आम्हाला स्टेटस अपडेट किंवा डीबगिंग आउटपुटसाठी प्रतीक्षा करायची नाही. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +मेननेटवर जाण्यासाठी एक URL. तुम्ही [नोड ॲज अ सर्व्हिस](/developers/docs/nodes-and-clients/nodes-as-a-service/) मधून एक मिळवू शकता किंवा [चेनलिस्ट](https://chainlist.org/chain/1) मध्ये जाहिरात केलेल्यांपैकी एक वापरू शकता. + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +एक Ethereum मेननेट ब्लॉक साधारणपणे दर बारा सेकंदांनी होतो, त्यामुळे वेळेच्या कालावधीत अपेक्षित असलेल्या ब्लॉकची ही संख्या आहे. लक्षात घ्या की हा अचूक आकडा नाही. जेव्हा [ब्लॉक प्रपोजर](/developers/docs/consensus-mechanisms/pos/block-proposal/) डाउन असतो, तेव्हा तो ब्लॉक वगळला जातो आणि पुढील ब्लॉकसाठी वेळ 24 सेकंद असतो. जर आम्हाला टाइमस्टॅम्पसाठी अचूक ब्लॉक हवा असेल तर आम्ही [बायनरी सर्च](https://en.wikipedia.org/wiki/Binary_search) वापरू. तथापि, हे आमच्या उद्देशांसाठी पुरेसे आहे. भविष्याचा अंदाज लावणे हे अचूक विज्ञान नाही. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +सायकलचा आकार. आम्ही प्रत्येक सायकलमध्ये एकदा कोट्सचे पुनरावलोकन करतो आणि पुढील सायकलच्या शेवटी मूल्याचा अंदाज लावण्याचा प्रयत्न करतो. + +```python +# आपण वाचत असलेल्या पूलचा ॲड्रेस +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +कोटची मूल्ये [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract) या ॲड्रेसवरील Uniswap 3 USDC/WETH पूलमधून घेतली जातात. हा ॲड्रेस आधीच चेकसम फॉर्ममध्ये आहे, परंतु कोड पुन्हा वापरण्यायोग्य बनवण्यासाठी [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) वापरणे चांगले आहे. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +आपल्याला संपर्क साधण्याची आवश्यकता असलेल्या दोन कॉन्ट्रॅक्ट्ससाठी हे [ABIs](https://docs.soliditylang.org/en/latest/abi-spec.html) आहेत. कोड संक्षिप्त ठेवण्यासाठी, आम्ही फक्त त्या फंक्शन्सचा समावेश करतो ज्यांना आम्हाला कॉल करण्याची आवश्यकता आहे. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +[`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) लायब्ररी सुरू करा आणि Ethereum नोडशी कनेक्ट करा. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +Python मध्ये डेटा क्लास तयार करण्याचा हा एक मार्ग आहे. [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) डेटा प्रकार कॉन्ट्रॅक्टशी कनेक्ट करण्यासाठी वापरला जातो. ` (frozen=True)` लक्षात घ्या. Python मध्ये [बुलियन](https://en.wikipedia.org/wiki/Boolean_data_type) `True` किंवा `False` म्हणून कॅपिटलाइज्ड स्वरूपात परिभाषित केले जातात. हा डेटा क्लास `frozen` आहे, म्हणजे फील्ड्समध्ये बदल करता येणार नाही. + +इंडेंटेशन लक्षात घ्या. [सी-व्युत्पन्न भाषां](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages)च्या तुलनेत, Python ब्लॉक दर्शवण्यासाठी इंडेंटेशन वापरते. Python इंटरप्रिटरला माहित आहे की खालील व्याख्या या डेटा क्लासचा भाग नाही कारण ती डेटा क्लास फील्ड्सप्रमाणे समान इंडेंटेशनवर सुरू होत नाही. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +[`Decimal`](https://docs.python.org/3/library/decimal.html) प्रकार दशांश अपूर्णांक अचूकपणे हाताळण्यासाठी वापरला जातो. + +```python + def get_price(self, block: int) -> Decimal: +``` + +Python मध्ये फंक्शन परिभाषित करण्याचा हा मार्ग आहे. व्याख्या `PoolInfo` चा भाग आहे हे दाखवण्यासाठी इंडेंट केलेली आहे. + +डेटा क्लासचा भाग असलेल्या फंक्शनमध्ये पहिले पॅरामीटर नेहमी `self` असते, जे येथे कॉल केलेले डेटा क्लासचे उदाहरण आहे. येथे आणखी एक पॅरामीटर आहे, ब्लॉक नंबर. + +```python + assert block <= w3.eth.block_number, "ब्लॉक भविष्यात आहे" +``` + +जर आपण भविष्य वाचू शकलो असतो, तर आपल्याला ट्रेडिंगसाठी AI ची गरज भासली नसती. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +Web3 वरून EVM वर फंक्शन कॉल करण्याची सिंटॅक्स अशी आहे: `.functions."().call()`. पॅरामीटर्स EVM फंक्शनचे पॅरामीटर्स (असल्यास; येथे नाहीत) किंवा ब्लॉकचेन वर्तन सुधारण्यासाठी [नेम्ड पॅरामीटर्स](https://en.wikipedia.org/wiki/Named_parameter) असू शकतात. येथे आम्ही एक, `block_identifier` वापरतो, [ब्लॉक नंबर](/developers/docs/apis/json-rpc/#default-block) निर्दिष्ट करण्यासाठी ज्यामध्ये आम्हाला चालवायचे आहे. + +परिणाम [हा स्ट्रक्ट, ॲरे फॉर्ममध्ये](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72) आहे. पहिले मूल्य दोन टोकन्समधील एक्सचेंज रेटचे फंक्शन आहे. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +ऑनचेन गणना कमी करण्यासाठी, Uniswap v3 वास्तविक एक्सचेंज फॅक्टर साठवत नाही तर त्याचे वर्गमूळ साठवते. कारण EVM फ्लोटिंग पॉइंट मॅथ किंवा फ्रॅक्शन्सला सपोर्ट करत नाही, वास्तविक मूल्याऐवजी, प्रतिसाद price296 असतो. + +```python + # (token1 प्रति token0) + return 1/(raw_price * self.decimal_factor) +``` + +आम्हाला मिळणारी रॉ किंमत म्हणजे प्रत्येक `token1` साठी आम्हाला मिळणाऱ्या `token0` ची संख्या. आमच्या पूलमध्ये `token0` हे USDC (यूएस डॉलरसारखेच मूल्य असलेले स्टेबलकॉइन) आहे आणि `token1` हे [WETH](https://opensea.io/learn/blockchain/what-is-weth) आहे. आम्हाला खरोखर हवे असलेले मूल्य म्हणजे प्रति WETH डॉलरची संख्या, व्यस्त नाही. + +डेसिमल फॅक्टर हे दोन टोकन्ससाठी [डेसिमल फॅक्टर्स](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) मधील गुणोत्तर आहे. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +हा डेटा क्लास एक कोट दर्शवतो: एका विशिष्ट वेळी एका विशिष्ट मालमत्तेची किंमत. या टप्प्यावर, `asset` फील्ड अप्रासंगिक आहे कारण आम्ही एकच पूल वापरतो आणि त्यामुळे एकच मालमत्ता आहे. तथापि, आम्ही नंतर अधिक मालमत्ता जोडू. + +```python +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 + ) +``` + +हे फंक्शन एक ॲड्रेस घेते आणि त्या ॲड्रेसवरील टोकन कॉन्ट्रॅक्टबद्दल माहिती देते. नवीन [Web3 `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) तयार करण्यासाठी, आम्ही `w3.eth.contract` ला ॲड्रेस आणि ABI देतो. + +```python +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) + ) +``` + +हे फंक्शन आम्हाला [एका विशिष्ट पूल](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol)बद्दल आवश्यक असलेली प्रत्येक गोष्ट परत करते. `f""` सिंटॅक्स हे [फॉर्मेटेड स्ट्रिंग](https://docs.python.org/3/reference/lexical_analysis.html#f-strings) आहे. + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +`Quote` ऑब्जेक्ट मिळवा. `block_number` साठी डीफॉल्ट मूल्य `None` आहे (मूल्य नाही). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +जर ब्लॉक नंबर निर्दिष्ट केला नसेल, तर `w3.eth.block_number` वापरा, जो नवीनतम ब्लॉक नंबर आहे. हे [एका `if` स्टेटमेंट](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement) साठी सिंटॅक्स आहे. + +डीफॉल्ट `w3.eth.block_number` वर सेट करणे चांगले झाले असते असे वाटू शकते, परंतु ते चांगले काम करत नाही कारण फंक्शन परिभाषित केल्याच्या वेळेचा तो ब्लॉक नंबर असेल. दीर्घकाळ चालणाऱ्या एजंटमध्ये, ही एक समस्या असेल. + +```python + 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 + ) +``` + +मानव आणि लार्ज लँग्वेज मॉडेल्स (LLMs) साठी वाचनीय स्वरूपात फॉरमॅट करण्यासाठी [`datetime` लायब्ररी](https://docs.python.org/3/library/datetime.html) वापरा. मूल्य दोन दशांश स्थानांपर्यंत पूर्णांक करण्यासाठी [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) वापरा. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +Python मध्ये तुम्ही एक [सूची](https://docs.python.org/3/library/stdtypes.html#typesseq-list) परिभाषित करता ज्यात `list[]` वापरून फक्त एक विशिष्ट प्रकार असू शकतो. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +Python मध्ये [`for` लूप](https://docs.python.org/3/tutorial/controlflow.html#for-statements) सामान्यतः सूचीवर पुनरावृत्ती करतो. कोट्स शोधण्यासाठी ब्लॉक नंबर्सची सूची [`range`](https://docs.python.org/3/library/stdtypes.html#range) मधून येते. + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +प्रत्येक ब्लॉक नंबरसाठी, `Quote` ऑब्जेक्ट मिळवा आणि तो `quotes` सूचीमध्ये जोडा. नंतर ती सूची परत करा. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +हा स्क्रिप्टचा मुख्य कोड आहे. पूल माहिती वाचा, बारा कोट्स मिळवा आणि त्यांना [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) करा. + +### प्रॉम्प्ट तयार करणे {#prompt} + +पुढे, आम्हाला या कोट्सच्या सूचीचे LLM साठी प्रॉम्प्टमध्ये रूपांतर करणे आणि अपेक्षित भविष्यकालीन मूल्य मिळवणे आवश्यक आहे. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +आउटपुट आता LLM साठी एक प्रॉम्प्ट असेल, यासारखे: + +``` +हे कोट्स दिले आहेत: +मालमत्ता: WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +मालमत्ता: WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +तुम्ही 2026-02-02T17:56 वाजता WETH/USDC साठी काय मूल्य अपेक्षित कराल? + +तुमचे उत्तर दोन दशांश स्थानांपर्यंत पूर्णांक केलेला एकच क्रमांक म्हणून द्या, +इतर कोणत्याही मजकुराशिवाय. +``` + +लक्षात घ्या की येथे दोन मालमत्तांसाठी कोट्स आहेत, `WETH/USDC` आणि `WBTC/WETH`. दुसऱ्या मालमत्तेतून कोट्स जोडल्याने अंदाजाची अचूकता सुधारू शकते. + +#### प्रॉम्प्ट कसा दिसतो {#prompt-explanation} + +या प्रॉम्प्टमध्ये तीन विभाग आहेत, जे LLM प्रॉम्प्टमध्ये खूप सामान्य आहेत. + +1. माहिती. LLM कडे त्यांच्या प्रशिक्षणातून खूप माहिती असते, परंतु त्यांच्याकडे सहसा नवीनतम माहिती नसते. हेच कारण आहे की आम्हाला येथे नवीनतम कोट्स पुनर्प्राप्त करण्याची आवश्यकता आहे. प्रॉम्प्टमध्ये माहिती जोडण्याला [रिट्रिव्हल ऑगमेंटेड जनरेशन (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation) म्हणतात. + +2. वास्तविक प्रश्न. हेच आपल्याला जाणून घ्यायचे आहे. + +3. आउटपुट फॉरमॅटिंग सूचना. सामान्यतः, एक LLM आम्हाला एक अंदाज देईल, तो कसा आला याच्या स्पष्टीकरणासह. हे मानवांसाठी चांगले आहे, परंतु संगणक प्रोग्रामला फक्त अंतिम उत्तराची गरज असते. + +#### कोड स्पष्टीकरण {#prompt-code} + +येथे नवीन कोड आहे. + +```python +from datetime import datetime, timezone, timedelta +``` + +आम्हाला LLM ला तो वेळ प्रदान करण्याची आवश्यकता आहे ज्यासाठी आम्हाला अंदाज हवा आहे. भविष्यात "n मिनिटे/तास/दिवस" वेळ मिळवण्यासाठी, आम्ही [`timedelta` क्लास](https://docs.python.org/3/library/datetime.html#datetime.timedelta) वापरतो. + +```python +# आपण वाचत असलेल्या पूल्सचे ॲड्रेस +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +आमच्याकडे दोन पूल्स आहेत जे आम्हाला वाचण्याची गरज आहे. + +```python +@dataclass(frozen=True) +class PoolInfo: + . + . + . + reverse: bool = False + + def get_price(self, block: int) -> Decimal: + assert block <= w3.eth.block_number, "ब्लॉक भविष्यात आहे" + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (token1 प्रति token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +WETH/USDC पूलमध्ये, आम्हाला एक `token1` (WETH) विकत घेण्यासाठी किती `token0` (USDC) लागतील हे जाणून घ्यायचे आहे. WETH/WBTC पूलमध्ये, आम्हाला एक `token0` (WBTC, जे रॅप केलेले Bitcoin आहे) विकत घेण्यासाठी किती `token1` (WETH) लागतील हे जाणून घ्यायचे आहे. आम्हाला पूलचे गुणोत्तर उलट करण्याची गरज आहे का याचा मागोवा घेणे आवश्यक आहे. + +```python +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 + ) +``` + +एखादा पूल उलट करण्याची गरज आहे की नाही हे जाणून घेण्यासाठी, आम्ही ते `read_pool` साठी इनपुट म्हणून घेतो. तसेच, मालमत्तेचे चिन्ह योग्यरित्या सेट करणे आवश्यक आहे. + +` if else ` ही सिंटॅक्स [टर्नरी कंडिशनल ऑपरेटर](https://en.wikipedia.org/wiki/Ternary_conditional_operator) ची Python समतुल्य आहे, जी C-व्युत्पन्न भाषेत ` ? : ` असेल. + +```python +def format_quotes(quotes: list[Quote]) -> str: + result = f"मालमत्ता: {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 +``` + +हे फंक्शन `Quote` ऑब्जेक्ट्सच्या सूचीला फॉरमॅट करणारी स्ट्रिंग तयार करते, असे गृहीत धरून की ते सर्व एकाच मालमत्तेवर लागू होतात. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +Python मध्ये [मल्टी-लाइन स्ट्रिंग लिटरल्स](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) `"""` .... असे लिहिले जातात. `"""`. + +```python +हे कोट्स दिले आहेत: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +येथे, आम्ही `format_quotes` सह प्रत्येक कोट सूचीसाठी स्ट्रिंग तयार करण्यासाठी [MapReduce](https://en.wikipedia.org/wiki/MapReduce) पॅटर्न वापरतो, नंतर त्यांना प्रॉम्प्टमध्ये वापरण्यासाठी एकाच स्ट्रिंगमध्ये कमी करतो. + +```python +{expected_time} वाजता {asset} साठी तुम्ही काय मूल्य अपेक्षित कराल? + +तुमचे उत्तर दोन दशांश स्थानांपर्यंत पूर्णांक केलेला एकच क्रमांक म्हणून द्या, +इतर कोणत्याही मजकुराशिवाय. + """ +``` + +प्रॉम्प्टचा उर्वरित भाग अपेक्षित आहे. + +```python +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 +) +``` + +दोन पूल्सचे पुनरावलोकन करा आणि दोन्हींकडून कोट्स मिळवा. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +भविष्यातील वेळेचा बिंदू निश्चित करा ज्यासाठी आम्हाला अंदाज हवा आहे आणि प्रॉम्प्ट तयार करा. + +### LLM सह इंटरफेसिंग {#interface-llm} + +पुढे, आम्ही प्रत्यक्ष LLM ला प्रॉम्प्ट करतो आणि अपेक्षित भविष्यकालीन मूल्य प्राप्त करतो. मी हा प्रोग्राम OpenAI वापरून लिहिला आहे, त्यामुळे तुम्हाला वेगळा प्रदाता वापरायचा असेल, तर तुम्हाला त्यात बदल करावे लागतील. + +1. [OpenAI खाते](https://auth.openai.com/create-account) मिळवा + +2. [खात्यात पैसे भरा](https://platform.openai.com/settings/organization/billing/overview)—लिहिण्याच्या वेळी किमान रक्कम $5 आहे + +3. [API की तयार करा](https://platform.openai.com/settings/organization/api-keys) + +4. कमांड लाइनमध्ये, API की एक्सपोर्ट करा जेणेकरून तुमचा प्रोग्राम ती वापरू शकेल + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. एजंट चेकआउट करा आणि चालवा + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +येथे नवीन कोड आहे. + +```python +from openai import OpenAI + +open_ai = OpenAI() # क्लायंट OPENAI_API_KEY पर्यावरण व्हेरिएबल वाचतो +``` + +OpenAI API इम्पोर्ट आणि इन्स्टॅन्शिएट करा. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +प्रतिसाद तयार करण्यासाठी OpenAI API (`open_ai.chat.completions.create`) कॉल करा. + +```python +expected_price = Decimal(response.choices[0].message.content.strip()) +current_price = wethusdc_quotes[-1].price + +print ("सध्याची किंमत:", wethusdc_quotes[-1].price) +print(f"{future_time} मध्ये, अपेक्षित किंमत: {expected_price} USD") + +if (expected_price > current_price): + print(f"खरेदी करा, मला किंमत {expected_price - current_price} USD ने वाढण्याची अपेक्षा आहे") +else: + print(f"विक्री करा, मला किंमत {current_price - expected_price} USD ने कमी होण्याची अपेक्षा आहे") +``` + +किंमत आउटपुट करा आणि खरेदी किंवा विक्रीची शिफारस द्या. + +#### अंदाजांची चाचणी {#testing-the-predictions} + +आता आपण अंदाज तयार करू शकतो, तेव्हा आपण उपयुक्त अंदाज तयार करतो की नाही हे तपासण्यासाठी ऐतिहासिक डेटा देखील वापरू शकतो. + +```sh +uv run test-predictor.py +``` + +अपेक्षित परिणाम यासारखा आहे: + +``` +2026-01-05T19:50 साठी अंदाज: अंदाजित 3138.93 USD, वास्तविक 3218.92 USD, त्रुटी 79.99 USD +2026-01-06T19:56 साठी अंदाज: अंदाजित 3243.39 USD, वास्तविक 3221.08 USD, त्रुटी 22.31 USD +2026-01-07T20:02 साठी अंदाज: अंदाजित 3223.24 USD, वास्तविक 3146.89 USD, त्रुटी 76.35 USD +2026-01-08T20:11 साठी अंदाज: अंदाजित 3150.47 USD, वास्तविक 3092.04 USD, त्रुटी 58.43 USD +. +. +. +2026-01-31T22:33 साठी अंदाज: अंदाजित 2637.73 USD, वास्तविक 2417.77 USD, त्रुटी 219.96 USD +2026-02-01T22:41 साठी अंदाज: अंदाजित 2381.70 USD, वास्तविक 2318.84 USD, त्रुटी 62.86 USD +2026-02-02T22:49 साठी अंदाज: अंदाजित 2234.91 USD, वास्तविक 2349.28 USD, त्रुटी 114.37 USD +29 अंदाजांवर सरासरी अंदाज त्रुटी: 83.87103448275862068965517241 USD +प्रति शिफारस सरासरी बदल: 4.787931034482758620689655172 USD +बदलांचे मानक विचलन: 104.42 USD +फायदेशीर दिवस: 51.72% +तोट्याचे दिवस: 48.28% +``` + +बहुतेक टेस्टर एजंटसारखाच आहे, परंतु येथे काही भाग नवीन किंवा सुधारित आहेत. + +```python +CYCLES_FOR_TEST = 40 # बॅकटेस्टसाठी, आम्ही किती सायकलची चाचणी घेतो + +# भरपूर कोट्स मिळवा +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 +) +``` + +आम्ही `CYCLES_FOR_TEST` (येथे 40 म्हणून निर्दिष्ट) दिवस मागे पाहतो. + +```python +# अंदाज तयार करा आणि त्यांना वास्तविक इतिहासासह तपासा + +total_error = Decimal(0) +changes = [] +``` + +आपल्याला दोन प्रकारच्या त्रुटींमध्ये रस आहे. पहिली, `total_error`, ही फक्त प्रेडिक्टरने केलेल्या त्रुटींची बेरीज आहे. + +दुसरी, `changes`, समजून घेण्यासाठी आपल्याला एजंटचा उद्देश लक्षात ठेवणे आवश्यक आहे. WETH/USDC गुणोत्तराचा (ETH किंमत) अंदाज लावणे हे त्याचे काम नाही. विक्री आणि खरेदीच्या शिफारसी देणे हे त्याचे काम आहे. जर सध्याची किंमत $2000 असेल आणि उद्या $2010 चा अंदाज असेल, तर वास्तविक परिणाम $2020 असल्यास आणि आपण अतिरिक्त पैसे कमावल्यास आपल्याला काही फरक पडत नाही. परंतु जर त्याने $2010 चा अंदाज लावला असेल, आणि त्या शिफारशीवर आधारित ETH विकत घेतले असेल, आणि किंमत $1990 पर्यंत घसरली तर आपल्याला नक्कीच फरक पडतो. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +आपण फक्त अशा प्रकरणांकडे पाहू शकतो जिथे संपूर्ण इतिहास (अंदाजासाठी वापरलेली मूल्ये आणि त्याची तुलना करण्यासाठी वास्तविक-जगातील मूल्य) उपलब्ध आहे. याचा अर्थ नवीन केस ती असली पाहिजे जी `CYCLES_BACK` पूर्वी सुरू झाली. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +एजंट वापरत असलेल्या सॅम्पल्सची संख्या मिळवण्यासाठी [स्लाइस](https://www.w3schools.com/python/ref_func_slice.asp) वापरा. येथून पुढील सेगमेंटपर्यंतचा कोड हा एजंटमध्ये असलेल्या गेट-अ-प्रेडिक्शन कोडसारखाच आहे. + +```python + 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 +``` + +अपेक्षित किंमत, वास्तविक किंमत आणि अंदाजाच्या वेळेची किंमत मिळवा. खरेदी किंवा विक्रीची शिफारस होती की नाही हे ठरवण्यासाठी आम्हाला अंदाजाच्या वेळेची किंमत आवश्यक आहे. + +```python + 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") +``` + +त्रुटी काढा आणि ती एकूण त्रुटीमध्ये जोडा. + +```python + recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell' + price_increase = real_price - prediction_time_price + changes.append(price_increase if recomended_action == 'buy' else -price_increase) +``` + +`changes` साठी, आम्हाला एक ETH विकत घेतल्यास किंवा विकल्यास होणारा आर्थिक परिणाम हवा आहे. त्यामुळे, प्रथम आपल्याला शिफारस निश्चित करणे आवश्यक आहे, नंतर वास्तविक किंमत कशी बदलली आणि शिफारशीमुळे पैसे मिळाले (सकारात्मक बदल) की पैसे गेले (नकारात्मक बदल) हे तपासा. + +```python +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") +``` + +परिणाम कळवा. + +```python +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%}") +``` + +फायदेशीर दिवसांची संख्या आणि तोट्याच्या दिवसांची संख्या मोजण्यासाठी [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) वापरा. परिणाम एक फिल्टर ऑब्जेक्ट आहे, ज्याला लांबी मिळवण्यासाठी आपल्याला सूचीमध्ये रूपांतरित करणे आवश्यक आहे. + +### व्यवहार सबमिट करणे {#submit-txn} + +आता आपल्याला प्रत्यक्षात व्यवहार सबमिट करणे आवश्यक आहे. तथापि, प्रणाली सिद्ध होण्यापूर्वी या टप्प्यावर मला खरे पैसे खर्च करायचे नाहीत. त्याऐवजी, आम्ही मेननेटचा एक स्थानिक फोर्क तयार करू आणि त्या नेटवर्कवर "ट्रेड" करू. + +स्थानिक फोर्क तयार करण्यासाठी आणि ट्रेडिंग सक्षम करण्यासाठी येथे पायऱ्या आहेत. + +1. [Foundry](https://getfoundry.sh/introduction/installation) इंस्टॉल करा + +2. [`anvil`](https://getfoundry.sh/anvil/overview) सुरू करा + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` Foundry साठी डीफॉल्ट URL, http://localhost:8545 वर ऐकत आहे, त्यामुळे आपल्याला ब्लॉकचेन हाताळण्यासाठी वापरल्या जाणाऱ्या [`cast` कमांड](https://getfoundry.sh/cast/overview)साठी URL निर्दिष्ट करण्याची आवश्यकता नाही. + +3. `anvil` मध्ये चालवताना, दहा चाचणी खाती आहेत ज्यात ETH आहे—पहिल्यासाठी पर्यावरण व्हेरिएबल्स सेट करा + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. हे कॉन्ट्रॅक्ट्स आहेत जे आपल्याला वापरायचे आहेत. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) हा Uniswap v3 कॉन्ट्रॅक्ट आहे जो आपण प्रत्यक्षात ट्रेड करण्यासाठी वापरतो. आपण थेट पूलमार्फत ट्रेड करू शकलो असतो, पण हे खूप सोपे आहे. + + दोन तळाचे व्हेरिएबल्स हे WETH आणि USDC दरम्यान स्वॅप करण्यासाठी आवश्यक असलेले Uniswap v3 पाथ आहेत. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. प्रत्येक चाचणी खात्यात 10,000 ETH आहेत. ट्रेडिंगसाठी 1000 WETH मिळवण्यासाठी WETH कॉन्ट्रॅक्ट वापरून 1000 ETH रॅप करा. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. USDC साठी 500 WETH ट्रेड करण्यासाठी `SwapRouter` वापरा. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + `approve` कॉल एक भत्ता तयार करतो जो `SwapRouter` ला आमचे काही टोकन खर्च करण्याची परवानगी देतो. कॉन्ट्रॅक्ट्स इव्हेंटचे निरीक्षण करू शकत नाहीत, म्हणून जर आपण टोकन थेट `SwapRouter` कॉन्ट्रॅक्टमध्ये हस्तांतरित केले, तर त्याला पैसे दिले गेले आहेत हे कळणार नाही. त्याऐवजी, आम्ही `SwapRouter` कॉन्ट्रॅक्टला एक विशिष्ट रक्कम खर्च करण्याची परवानगी देतो, आणि मग `SwapRouter` ते करतो. हे `SwapRouter` द्वारे कॉल केलेल्या फंक्शनद्वारे केले जाते, त्यामुळे ते यशस्वी झाले की नाही हे त्याला कळते. + +7. तुमच्याकडे दोन्ही टोकन पुरेशी आहेत याची पडताळणी करा. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +आता आमच्याकडे WETH आणि USDC आहेत, आम्ही प्रत्यक्षात एजंट चालवू शकतो. + +```sh +git checkout 05-trade +uv run agent.py +``` + +आउटपुट यासारखे दिसेल: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +सध्याची किंमत: 1843.16 +2026-02-06T23:07 मध्ये, अपेक्षित किंमत: 1724.41 USD +ट्रेडपूर्वी खात्यातील शिल्लक: +USDC शिल्लक: 927301.578272 +WETH शिल्लक: 500 +विक्री करा, मला किंमत 118.75 USD ने कमी होण्याची अपेक्षा आहे +ॲप्रूव्ह व्यवहार पाठवला: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +ॲप्रूव्ह व्यवहार माइन झाला. +विक्री व्यवहार पाठवला: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +विक्री व्यवहार माइन झाला. +ट्रेडनंतर खात्यातील शिल्लक: +USDC शिल्लक: 929143.797116 +WETH शिल्लक: 499 +``` + +प्रत्यक्षात ते वापरण्यासाठी, तुम्हाला काही किरकोळ बदल करणे आवश्यक आहे. + +- ओळ 14 मध्ये, `MAINNET_URL` वास्तविक ऍक्सेस पॉईंटमध्ये बदला, जसे की `https://eth.drpc.org` +- ओळ 28 मध्ये, `PRIVATE_KEY` तुमच्या स्वतःच्या प्रायव्हेट कीमध्ये बदला +- जोपर्यंत तुम्ही खूप श्रीमंत नसाल आणि एका अप्रमाणित एजंटसाठी दररोज 1 ETH खरेदी किंवा विक्री करू शकत नसाल, तोपर्यंत तुम्ही `WETH_TRADE_AMOUNT` कमी करण्यासाठी 29 बदलू शकता. + +#### कोड स्पष्टीकरण {#trading-code} + +येथे नवीन कोड आहे. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +तेच व्हेरिएबल्स जे आपण पायरी 4 मध्ये वापरले. + +```python +WETH_TRADE_AMOUNT=1 +``` + +ट्रेड करण्याची रक्कम. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +प्रत्यक्षात ट्रेड करण्यासाठी, आम्हाला `approve` फंक्शनची आवश्यकता आहे. आम्हाला शिल्लक आधी आणि नंतर दाखवायची आहे, त्यामुळे आम्हाला `balanceOf` ची देखील आवश्यकता आहे. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +`SwapRouter` ABI मध्ये आपल्याला फक्त `exactInput` ची गरज आहे. `exactOutput` नावाचे एक संबंधित फंक्शन आहे, जे आपण नक्की एक WETH खरेदी करण्यासाठी वापरू शकतो, परंतु सोपेपणासाठी आपण दोन्ही बाबतीत `exactInput` वापरतो. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +[`account`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) आणि `SwapRouter` कॉन्ट्रॅक्टसाठी Web3 व्याख्या. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +व्यवहाराचे पॅरामीटर्स. आम्हाला येथे एका फंक्शनची आवश्यकता आहे कारण [नॉन्स](https://en.wikipedia.org/wiki/Cryptographic_nonce) प्रत्येक वेळी बदलला पाहिजे. + +```python +def approve_token(contract: Contract, amount: int): +``` + +`SwapRouter` साठी टोकन भत्ता मंजूर करा. + +```python + txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params()) + signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY) + tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction) +``` + +Web3 मध्ये व्यवहार पाठवण्याची ही पद्धत आहे. प्रथम आपण व्यवहार तयार करण्यासाठी [`Contract` ऑब्जेक्ट](https://web3py.readthedocs.io/en/stable/web3.contract.html) वापरतो. नंतर आपण `PRIVATE_KEY` वापरून व्यवहार साइन करण्यासाठी [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) वापरतो. शेवटी, आपण व्यवहार पाठवण्यासाठी [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) वापरतो. + +```python + 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`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) व्यवहार माइन होईपर्यंत थांबते. गरज भासल्यास ते पावती परत करते. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +WETH विकताना हे पॅरामीटर्स आहेत. + +```python +def make_buy_params(quote: Quote) -> dict: + return { + "path": USDC_TO_WETH, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals, + "amountOutMinimum": 0, + } +``` + +`SELL_PARAMS` च्या विपरीत, खरेदी पॅरामीटर्स बदलू शकतात. इनपुट रक्कम 1 WETH ची किंमत आहे, जी `quote` मध्ये उपलब्ध आहे. + +```python +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.") +``` + +`buy()` आणि `sell()` फंक्शन्स जवळजवळ सारखीच आहेत. प्रथम आम्ही `SwapRouter` साठी पुरेसा भत्ता मंजूर करतो, आणि नंतर आम्ही त्याला योग्य पथ आणि रकमेसह कॉल करतो. + +```python +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)}") +``` + +दोन्ही चलनांमधील वापरकर्ता शिल्लक कळवा. + +```python +print("ट्रेडपूर्वी खात्यातील शिल्लक:") +balances() + +if (expected_price > current_price): + print(f"खरेदी करा, मला किंमत {expected_price - current_price} USD ने वाढण्याची अपेक्षा आहे") + buy(wethusdc_quotes[-1]) +else: + print(f"विक्री करा, मला किंमत {current_price - expected_price} USD ने कमी होण्याची अपेक्षा आहे") + sell() + +print("ट्रेडनंतर खात्यातील शिल्लक:") +balances() +``` + +हा एजंट सध्या फक्त एकदाच काम करतो. तथापि, तुम्ही ते [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) वरून चालवून किंवा ओळी 368-400 लूपमध्ये गुंडाळून आणि पुढील सायकलची वेळ होईपर्यंत थांबण्यासाठी [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) वापरून ते सतत काम करण्यासाठी बदलू शकता. + +## संभाव्य सुधारणा {#improvements} + +ही पूर्ण उत्पादन आवृत्ती नाही; हे फक्त मूलभूत गोष्टी शिकवण्यासाठी एक उदाहरण आहे. सुधारणेसाठी येथे काही कल्पना आहेत. + +### स्मार्ट ट्रेडिंग {#smart-trading} + +दोन महत्त्वाची तथ्ये आहेत ज्याकडे एजंट काय करायचे हे ठरवताना दुर्लक्ष करतो. + +- _अपेक्षित बदलाचे प्रमाण_. किंमत कमी होण्याची अपेक्षा असल्यास एजंट `WETH` ची निश्चित रक्कम विकतो, घसरणीच्या प्रमाणाचा विचार न करता. + असे म्हणता येईल की किरकोळ बदलांकडे दुर्लक्ष करणे आणि किंमत किती कमी होण्याची अपेक्षा आहे यावर आधारित विक्री करणे चांगले होईल. +- _सध्याचा पोर्टफोलिओ_. जर तुमच्या पोर्टफोलिओचा 10% भाग WETH मध्ये असेल आणि तुम्हाला वाटत असेल की किंमत वाढेल, तर अधिक खरेदी करणे कदाचित योग्य आहे. परंतु जर तुमच्या पोर्टफोलिओचा 90% भाग WETH मध्ये असेल, तर तुम्ही पुरेसे एक्सपोज्ड असाल आणि अधिक खरेदी करण्याची गरज नाही. जर तुम्हाला किंमत कमी होण्याची अपेक्षा असेल तर उलट सत्य आहे. + +### जर तुम्हाला तुमची ट्रेडिंग स्ट्रॅटेजी गुप्त ठेवायची असेल तर काय? {#secret} + +AI विक्रेते तुम्ही त्यांच्या LLMs ला पाठवलेल्या क्वेरीज पाहू शकतात, ज्यामुळे तुम्ही तुमच्या एजंटसह विकसित केलेली उत्तम ट्रेडिंग प्रणाली उघड होऊ शकते. एक ट्रेडिंग प्रणाली जी खूप लोक वापरतात ती निरुपयोगी आहे कारण खूप लोक खरेदी करण्याचा प्रयत्न करतात जेव्हा तुम्हाला खरेदी करायची असते (आणि किंमत वाढते) आणि विक्री करण्याचा प्रयत्न करतात जेव्हा तुम्हाला विक्री करायची असते (आणि किंमत कमी होते). + +ही समस्या टाळण्यासाठी तुम्ही स्थानिक पातळीवर LLM चालवू शकता, उदाहरणार्थ, [LM-Studio](https://lmstudio.ai/) वापरून. + +### AI बॉटपासून AI एजंटपर्यंत {#bot-to-agent} + +तुम्ही असा चांगला युक्तिवाद करू शकता की हा [एक AI बॉट आहे, AI एजंट नाही](/ai-agents/#ai-agents-vs-ai-bots). हे एक तुलनेने सोपे धोरण लागू करते जे पूर्वनिर्धारित माहितीवर अवलंबून असते. आम्ही स्वयं-सुधारणा सक्षम करू शकतो, उदाहरणार्थ, Uniswap v3 पूल्सची सूची आणि त्यांची नवीनतम मूल्ये देऊन आणि विचारून की कोणत्या संयोजनाचे सर्वोत्तम भविष्यसूचक मूल्य आहे. + +### स्लिपेज संरक्षण {#slippage-protection} + +सध्या कोणतेही [स्लिपेज संरक्षण](https://uniswapv3book.com/milestone_3/slippage-protection.html) नाही. जर सध्याचा कोट $2000 असेल, आणि अपेक्षित किंमत $2100 असेल, तर एजंट खरेदी करेल. तथापि, एजंट खरेदी करण्यापूर्वी किंमत $2200 पर्यंत वाढल्यास, आता खरेदी करण्यात काही अर्थ नाही. + +स्लिपेज संरक्षण लागू करण्यासाठी, [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325) च्या ओळी 325 आणि 334 मध्ये `amountOutMinimum` मूल्य निर्दिष्ट करा. + +## निष्कर्ष {#conclusion} + +आशा आहे की, आता तुम्हाला AI एजंट्ससह सुरुवात करण्यासाठी पुरेसे माहित आहे. हे विषयाचे सर्वसमावेशक विहंगावलोकन नाही; त्यासाठी संपूर्ण पुस्तके आहेत, परंतु हे तुम्हाला सुरुवात करण्यासाठी पुरेसे आहे. शुभेच्छा! + +[माझ्या कामाबद्दल अधिक माहितीसाठी येथे पहा](https://cryptodocguy.pro/). diff --git a/public/content/translations/pl/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/pl/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..6ee62502036 --- /dev/null +++ b/public/content/translations/pl/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "Stwórz własnego agenta handlowego AI na Ethereum" +description: "W tym samouczku dowiesz się, jak stworzyć prostego agenta handlowego AI. Agent ten odczytuje informacje z blockchaina, prosi LLM o rekomendację na podstawie tych informacji, wykonuje transakcję zalecaną przez LLM, a następnie czeka i powtarza proces." +author: Ori Pomerantz +tags: [ "AI", "handel", "agent", "python" ] +skill: intermediate +published: 2026-02-13 +lang: pl +sidebarDepth: 3 +--- + +W tym samouczku dowiesz się, jak zbudować prostego agenta handlowego AI. Agent ten działa w oparciu o następujące kroki: + +1. Odczytanie bieżących i przeszłych cen tokena, a także innych potencjalnie istotnych informacji +2. Zbudowanie zapytania z tymi informacjami, wraz z informacjami ogólnymi, aby wyjaśnić, w jaki sposób mogą być one istotne +3. Przesłanie zapytania i otrzymanie prognozowanej ceny +4. Handel w oparciu o rekomendację +5. Oczekiwanie i powtórzenie + +Ten agent pokazuje, jak odczytywać informacje, przekształcać je w zapytanie, które daje użyteczną odpowiedź i jak z tej odpowiedzi korzystać. Wszystkie te kroki są wymagane w przypadku agenta AI. Agent ten jest zaimplementowany w Pythonie, ponieważ jest to najpopularniejszy język używany w AI. + +## Po co to robić? {#why-do-this} + +Zautomatyzowani agenci handlowi pozwalają deweloperom wybierać i realizować strategię handlową. [Agenci AI](/ai-agents) pozwalają na bardziej złożone i dynamiczne strategie handlowe, potencjalnie wykorzystując informacje i algorytmy, których użycia deweloper nawet nie brał pod uwagę. + +## Narzędzia {#tools} + +W tym samouczku wykorzystano [Python](https://www.python.org/), [bibliotekę Web3](https://web3py.readthedocs.io/en/stable/) oraz [Uniswap v3](https://github.com/Uniswap/v3-periphery) do notowań i handlu. + +### Dlaczego Python? {#python} + +Najczęściej używanym językiem w dziedzinie AI jest [Python](https://www.python.org/), więc używamy go tutaj. Nie martw się, jeśli nie znasz Pythona. Język jest bardzo przejrzysty, a ja dokładnie wyjaśnię, co robi. + +[Biblioteka Web3](https://web3py.readthedocs.io/en/stable/) jest najpopularniejszym API Pythona dla Ethereum. Jest dość łatwa w użyciu. + +### Handel na blockchainie {#trading-on-blockchain} + +Istnieje [wiele zdecentralizowanych giełd (DEX)](/apps/categories/defi/), które pozwalają na handel tokenami na Ethereum. Jednakże, ze względu na [arbitraż](/developers/docs/smart-contracts/composability/#better-user-experience), mają one zazwyczaj podobne kursy wymiany. + +[Uniswap](https://app.uniswap.org/) to powszechnie używana giełda DEX, której możemy używać zarówno do notowań (w celu sprawdzenia względnych wartości tokenów), jak i transakcji. + +### OpenAI {#openai} + +Jeśli chodzi o duży model językowy, zdecydowałem się zacząć od [OpenAI](https://openai.com/). Aby uruchomić aplikację z tego samouczka, trzeba będzie zapłacić za dostęp do API. Minimalna opłata w wysokości 5 USD jest więcej niż wystarczająca. + +## Programowanie, krok po kroku {#step-by-step} + +Aby uprościć programowanie, postępujemy etapami. Każdy krok to gałąź w GitHub. + +### Pierwsze kroki {#getting-started} + +Oto kroki, aby rozpocząć pracę w systemie UNIX lub Linux (w tym [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) + +1. Jeśli jeszcze go nie masz, pobierz i zainstaluj [Pythona](https://www.python.org/downloads/). + +2. Sklonuj repozytorium GitHub. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. Zainstaluj [`uv`](https://docs.astral.sh/uv/getting-started/installation/). Polecenie w twoim systemie może być inne. + + ```sh + pipx install uv + ``` + +4. Pobierz biblioteki. + + ```sh + uv sync + ``` + +5. Aktywuj środowisko wirtualne. + + ```sh + source .venv/bin/activate + ``` + +6. Aby sprawdzić, czy Python i Web3 działają poprawnie, uruchom `python3` i podaj mu ten program. Można go wpisać w wierszu poleceń `>>>`; nie ma potrzeby tworzenia pliku. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### Odczyt z blockchaina {#read-blockchain} + +Następnym krokiem jest odczyt z blockchaina. Aby to zrobić, należy przełączyć się na gałąź `02-read-quote`, a następnie użyć `uv` do uruchomienia programu. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +Powinna zostać wyświetlona lista obiektów `Quote`, z których każdy ma znacznik czasu, cenę i aktywo (obecnie zawsze `WETH/USDC`). + +Oto wyjaśnienie linijka po linijce. + +```python +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 +``` + +Zaimportuj biblioteki, których potrzebujemy. Są one wyjaśnione poniżej, gdy są używane. + +```python +print = functools.partial(print, flush=True) +``` + +Zastępuje funkcję `print` Pythona wersją, która zawsze natychmiast opróżnia bufor wyjścia. Jest to przydatne w długo działającym skrypcie, ponieważ nie chcemy czekać na aktualizacje statusu lub dane wyjściowe debugowania. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +Adres URL do sieci głównej. Możesz uzyskać go z [węzła jako usługa](/developers/docs/nodes-and-clients/nodes-as-a-service/) lub użyć jednego z tych reklamowanych w [Chainlist](https://chainlist.org/chain/1). + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +Blok w sieci głównej Ethereum pojawia się zazwyczaj co dwanaście sekund, więc są to liczby bloków, których spodziewamy się w danym okresie. Należy pamiętać, że nie jest to dokładna liczba. Gdy [proposer bloku](/developers/docs/consensus-mechanisms/pos/block-proposal/) nie działa, ten blok jest pomijany, a czas do następnego bloku wynosi 24 sekundy. Gdybyśmy chcieli uzyskać dokładny blok dla znacznika czasu, użylibyśmy [wyszukiwania binarnego](https://en.wikipedia.org/wiki/Binary_search). Jednak na nasze potrzeby jest to wystarczająco dokładne. Przewidywanie przyszłości nie jest nauką ścisłą. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +Rozmiar cyklu. Przeglądamy notowania raz na cykl i próbujemy oszacować wartość na koniec następnego cyklu. + +```python +# Adres puli, z której odczytujemy +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +Wartości notowań są pobierane z puli Uniswap 3 USDC/WETH pod adresem [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract). Ten adres jest już w formie sumy kontrolnej, ale lepiej jest użyć [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address), aby kod był wielokrotnego użytku. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +Są to [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html) dla dwóch kontraktów, z którymi musimy się skontaktować. Aby kod był zwięzły, dołączamy tylko te funkcje, które musimy wywołać. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +Zainicjuj bibliotekę [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) i połącz się z węzłem Ethereum. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +Jest to jeden ze sposobów tworzenia klasy danych w Pythonie. Typ danych [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) jest używany do łączenia się z kontraktem. Zwróć uwagę na `(frozen=True)`. W Pythonie wartości [logiczne](https://en.wikipedia.org/wiki/Boolean_data_type) są zdefiniowane jako `True` lub `False`, pisane wielką literą. Ta klasa danych jest `frozen`, co oznacza, że jej pól nie można modyfikować. + +Zwróć uwagę na wcięcie. W przeciwieństwie do [języków wywodzących się z C](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages), Python używa wcięć do oznaczania bloków. Interpreter Pythona wie, że poniższa definicja nie jest częścią tej klasy danych, ponieważ nie zaczyna się na tym samym poziomie wcięcia, co pola klasy danych. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +Typ [`Decimal`](https://docs.python.org/3/library/decimal.html) służy do dokładnej obsługi ułamków dziesiętnych. + +```python + def get_price(self, block: int) -> Decimal: +``` + +W ten sposób definiuje się funkcję w Pythonie. Definicja jest wcięta, aby pokazać, że nadal jest częścią `PoolInfo`. + +W funkcji, która jest częścią klasy danych, pierwszym parametrem jest zawsze `self`, czyli instancja klasy danych, która została tutaj wywołana. Jest tu jeszcze jeden parametr, numer bloku. + +```python + assert block <= w3.eth.block_number, "Block is in the future" +``` + +Gdybyśmy potrafili czytać przyszłość, nie potrzebowalibyśmy AI do handlu. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +Składnia wywoływania funkcji na EVM z Web3 jest następująca: `.functions."().call()`. Parametry mogą być parametrami funkcji EVM (jeśli istnieją; tutaj ich nie ma) lub [parametrami nazwanymi](https://en.wikipedia.org/wiki/Named_parameter) do modyfikowania zachowania blockchaina. Tutaj używamy jednego, `block_identifier`, aby określić [numer bloku](/developers/docs/apis/json-rpc/#default-block), w którym chcemy uruchomić. + +Wynikiem jest [ta struktura, w formie tablicy](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). Pierwsza wartość jest funkcją kursu wymiany między dwoma tokenami. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +Aby zmniejszyć liczbę obliczeń on-chain, Uniswap v3 nie przechowuje rzeczywistego współczynnika wymiany, a raczej jego pierwiastek kwadratowy. Ponieważ EVM nie obsługuje matematyki zmiennoprzecinkowej ani ułamków, zamiast rzeczywistej wartości, odpowiedzią jest price296 + +```python + # (token1 na token0) + return 1/(raw_price * self.decimal_factor) +``` + +Cena surowa, którą otrzymujemy, to liczba `token0`, którą otrzymujemy za każdy `token1`. W naszej puli `token0` to USDC (stablecoin o tej samej wartości co dolar amerykański), a `token1` to [WETH](https://opensea.io/learn/blockchain/what-is-weth). Wartością, której tak naprawdę potrzebujemy, jest liczba dolarów za WETH, a nie odwrotność. + +Współczynnik dziesiętny to stosunek między [współczynnikami dziesiętnymi](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) dla dwóch tokenów. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +Ta klasa danych reprezentuje notowanie: cenę określonego aktywa w danym momencie. W tym momencie pole `asset` jest nieistotne, ponieważ używamy jednej puli, a zatem mamy jedno aktywo. Jednak później dodamy więcej aktywów. + +```python +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 + ) +``` + +Funkcja ta pobiera adres i zwraca informacje o kontrakcie tokena pod tym adresem. Aby utworzyć nowy [Web3 `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html), podajemy adres i ABI do `w3.eth.contract`. + +```python +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) + ) +``` + +Ta funkcja zwraca wszystko, czego potrzebujemy na temat [określonej puli](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol). Składnia `f""` to [sformatowany ciąg znaków](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +Pobierz obiekt `Quote`. Domyślna wartość dla `block_number` to `None` (brak wartości). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +Jeśli numer bloku nie został określony, użyj `w3.eth.block_number`, który jest najnowszym numerem bloku. Jest to składnia dla [instrukcji `if`](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement). + +Może się wydawać, że lepiej byłoby po prostu ustawić wartość domyślną na `w3.eth.block_number`, ale to nie działa dobrze, ponieważ byłby to numer bloku w momencie definiowania funkcji. W przypadku długo działającego agenta byłby to problem. + +```python + 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 + ) +``` + +Użyj [biblioteki `datetime`](https://docs.python.org/3/library/datetime.html), aby sformatować go do formatu czytelnego dla ludzi i dużych modeli językowych (LLM). Użyj [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize), aby zaokrąglić wartość do dwóch miejsc po przecinku. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +W Pythonie definiuje się [listę](https://docs.python.org/3/library/stdtypes.html#typesseq-list), która może zawierać tylko określony typ za pomocą `list[]`. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +W Pythonie pętla [`for`](https://docs.python.org/3/tutorial/controlflow.html#for-statements) zwykle iteruje po liście. Lista numerów bloków do znalezienia notowań pochodzi z [`range`](https://docs.python.org/3/library/stdtypes.html#range). + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +Dla każdego numeru bloku pobierz obiekt `Quote` i dołącz go do listy `quotes`. Następnie zwróć tę listę. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +To jest główny kod skryptu. Odczytaj informacje o puli, uzyskaj dwanaście notowań i wydrukuj je za pomocą [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint). + +### Tworzenie monitu {#prompt} + +Następnie musimy przekonwertować tę listę notowań na monit dla LLM i uzyskać oczekiwaną przyszłą wartość. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +Dane wyjściowe będą teraz monitem dla LLM, podobnym do: + +``` +Biorąc pod uwagę te notowania: +Aktywa: WETH/USDC + 2026-01-20T16:34 3016,21 + . + . + . + 2026-02-01T17:49 2299,10 + +Aktywa: WBTC/WETH + 2026-01-20T16:34 29,84 + . + . + . + 2026-02-01T17:50 33,46 + + +Jakiej wartości WETH/USDC spodziewałbyś się o godzinie 2026-02-02T17:56? + +Podaj odpowiedź jako pojedynczą liczbę zaokrągloną do dwóch miejsc po przecinku, +bez żadnego innego tekstu. +``` + +Zauważ, że są tu notowania dla dwóch aktywów, `WETH/USDC` i `WBTC/WETH`. Dodanie notowań z innego aktywa może poprawić dokładność prognozy. + +#### Jak wygląda monit {#prompt-explanation} + +Ten monit zawiera trzy sekcje, które są dość powszechne w monitach LLM. + +1. Informacje. Modele LLM mają wiele informacji ze swojego szkolenia, ale zazwyczaj nie mają najnowszych. Z tego powodu musimy pobrać tutaj najnowsze notowania. Dodawanie informacji do monitu nazywa się [generowaniem rozszerzonym o wyszukiwanie (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +2. Właściwe pytanie. To jest to, co chcemy wiedzieć. + +3. Instrukcje formatowania danych wyjściowych. Zwykle LLM poda nam szacunkową wartość wraz z wyjaśnieniem, w jaki sposób do niej doszedł. Jest to lepsze dla ludzi, ale program komputerowy potrzebuje tylko wyniku końcowego. + +#### Wyjaśnienie kodu {#prompt-code} + +Oto nowy kod. + +```python +from datetime import datetime, timezone, timedelta +``` + +Musimy podać LLM czas, dla którego chcemy uzyskać oszacowanie. Aby uzyskać czas „n minut/godzin/dni” w przyszłości, używamy [klasy `timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta). + +```python +# Adresy pul, z których odczytujemy +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +Mamy dwie pule, które musimy odczytać. + +```python +@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 # (token1 per token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +W puli WETH/USDC chcemy wiedzieć, ile `token0` (USDC) potrzebujemy, aby kupić jeden `token1` (WETH). W puli WETH/WBTC chcemy wiedzieć, ile `token1` (WETH) potrzebujemy, aby kupić jeden `token0` (WBTC, czyli opakowany Bitcoin). Musimy śledzić, czy współczynnik puli wymaga odwrócenia. + +```python +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 + ) +``` + +Aby dowiedzieć się, czy pula wymaga odwrócenia, musimy uzyskać te dane wejściowe do `read_pool`. Ponadto symbol aktywa musi być poprawnie skonfigurowany. + +Składnia ` if else ` jest odpowiednikiem w Pythonie [trójargumentowego operatora warunkowego](https://en.wikipedia.org/wiki/Ternary_conditional_operator), który w języku pochodnym C byłby ` ? : `. + +```python +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 +``` + +Funkcja ta tworzy ciąg znaków, który formatuje listę obiektów `Quote`, zakładając, że wszystkie dotyczą tego samego aktywa. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +W Pythonie [wieloliniowe literały ciągów znaków](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) są zapisywane jako `"""` .... `"""`. + +```python +Biorąc pod uwagę te notowania: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +W tym miejscu używamy wzorca [MapReduce](https://en.wikipedia.org/wiki/MapReduce) do wygenerowania ciągu znaków dla każdej listy notowań za pomocą `format_quotes`, a następnie redukujemy je do pojedynczego ciągu znaków do użycia w monicie. + +```python +Jakiej wartości dla {asset} spodziewałbyś się o godzinie {expected_time}? + +Podaj swoją odpowiedź jako pojedynczą liczbę zaokrągloną do dwóch miejsc po przecinku, +bez żadnego innego tekstu. + """ +``` + +Reszta monitu jest zgodna z oczekiwaniami. + +```python +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 +) +``` + +Przejrzyj dwie pule i uzyskaj notowania z obu. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +Określ przyszły punkt czasowy, dla którego chcemy uzyskać oszacowanie i utwórz monit. + +### Interfejs z LLM {#interface-llm} + +Następnie monitujemy rzeczywisty LLM i otrzymujemy oczekiwaną przyszłą wartość. Napisałem ten program przy użyciu OpenAI, więc jeśli chcesz użyć innego dostawcy, będziesz musiał go dostosować. + +1. Załóż [konto OpenAI](https://auth.openai.com/create-account) + +2. [Zasil konto](https://platform.openai.com/settings/organization/billing/overview) — minimalna kwota w momencie pisania tego tekstu to 5 USD + +3. [Utwórz klucz API](https://platform.openai.com/settings/organization/api-keys) + +4. W wierszu poleceń wyeksportuj klucz API, aby program mógł z niego korzystać + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. Zamelduj i uruchom agenta + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +Oto nowy kod. + +```python +from openai import OpenAI + +open_ai = OpenAI() # Klient odczytuje zmienną środowiskową OPENAI_API_KEY +``` + +Zaimportuj i utwórz instancję API OpenAI. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +Wywołaj API OpenAI (`open_ai.chat.completions.create`), aby utworzyć odpowiedź. + +```python +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") +``` + +Wyświetl cenę i podaj rekomendację kupna lub sprzedaży. + +#### Testowanie prognoz {#testing-the-predictions} + +Teraz, gdy możemy generować prognozy, możemy również wykorzystać dane historyczne, aby ocenić, czy tworzymy użyteczne prognozy. + +```sh +uv run test-predictor.py +``` + +Oczekiwany wynik jest podobny do: + +``` +Prognoza na 2026-01-05T19:50: prognozowano 3138,93 USD, realnie 3218,92 USD, błąd 79,99 USD +Prognoza na 2026-01-06T19:56: prognozowano 3243,39 USD, realnie 3221,08 USD, błąd 22,31 USD +Prognoza na 2026-01-07T20:02: prognozowano 3223,24 USD, realnie 3146,89 USD, błąd 76,35 USD +Prognoza na 2026-01-08T20:11: prognozowano 3150,47 USD, realnie 3092,04 USD, błąd 58,43 USD +. +. +. +Prognoza na 2026-01-31T22:33: prognozowano 2637,73 USD, realnie 2417,77 USD, błąd 219,96 USD +Prognoza na 2026-02-01T22:41: prognozowano 2381,70 USD, realnie 2318,84 USD, błąd 62,86 USD +Prognoza na 2026-02-02T22:49: prognozowano 2234,91 USD, realnie 2349,28 USD, błąd 114,37 USD +Średni błąd prognozy dla 29 prognoz: 83.87103448275862068965517241 USD +Średnia zmiana na rekomendację: 4,787931034482758620689655172 USD +Standardowa wariancja zmian: 104,42 USD +Dni zyskowne: 51,72% +Dni stratne: 48,28% +``` + +Większość testera jest identyczna z agentem, ale oto części, które są nowe lub zmodyfikowane. + +```python +CYCLES_FOR_TEST = 40 # W przypadku testu historycznego, ile cykli testujemy + +# Uzyskaj wiele notowań +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 +) +``` + +Patrzymy wstecz na `CYCLES_FOR_TEST` (określone tutaj jako 40) dni. + +```python +# Tworzenie prognoz i sprawdzanie ich pod kątem rzeczywistej historii + +total_error = Decimal(0) +changes = [] +``` + +Interesują nas dwa rodzaje błędów. Pierwszy, `total_error`, to po prostu suma błędów popełnionych przez predyktora. + +Aby zrozumieć drugi, `changes`, musimy pamiętać o celu agenta. Nie chodzi o przewidywanie współczynnika WETH/USDC (ceny ETH). Chodzi o wydawanie zaleceń sprzedaży i kupna. Jeśli cena wynosi obecnie 2000 USD, a jutro przewiduje się 2010 USD, nie ma znaczenia, czy faktyczny wynik wyniesie 2020 USD i zarobimy dodatkowe pieniądze. Ale _ma_ znaczenie, jeśli przewidywano 2010 USD i kupiono ETH na podstawie tej rekomendacji, a cena spadnie do 1990 USD. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +Możemy przyjrzeć się tylko przypadkom, w których dostępna jest pełna historia (wartości użyte do prognozy i wartość rzeczywista do porównania). Oznacza to, że najnowszy przypadek musi być tym, który rozpoczął się `CYCLES_BACK` temu. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +Użyj [fragmentów](https://www.w3schools.com/python/ref_func_slice.asp), aby uzyskać taką samą liczbę próbek, jakiej używa agent. Kod między tym a następnym segmentem to ten sam kod do uzyskiwania prognoz, który mamy w agencie. + +```python + 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 +``` + +Uzyskaj przewidywaną cenę, cenę rzeczywistą i cenę w momencie prognozy. Potrzebujemy ceny w momencie prognozy, aby określić, czy rekomendacja dotyczyła kupna czy sprzedaży. + +```python + 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") +``` + +Ustal błąd i dodaj go do sumy. + +```python + 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) +``` + +W przypadku `changes` chcemy uzyskać wpływ pieniężny kupna lub sprzedaży jednego ETH. Najpierw musimy więc określić rekomendację, a następnie ocenić, jak zmieniła się rzeczywista cena i czy rekomendacja przyniosła zysk (zmiana dodatnia) czy stratę (zmiana ujemna). + +```python +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") +``` + +Zgłoś wyniki. + +```python +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%}") +``` + +Użyj [`filter`](https://www.w3schools.com/python/ref_func_filter.asp), aby policzyć liczbę dni zyskownych i liczbę dni kosztownych. Wynikiem jest obiekt filtru, który musimy przekonwertować na listę, aby uzyskać długość. + +### Wysyłanie transakcji {#submit-txn} + +Teraz musimy faktycznie przesłać transakcje. Nie chcę jednak wydawać prawdziwych pieniędzy na tym etapie, zanim system nie zostanie sprawdzony. Zamiast tego utworzymy lokalny fork sieci głównej i będziemy „handlować” w tej sieci. + +Oto kroki, aby utworzyć lokalny fork i włączyć handel. + +1. Zainstaluj [Foundry](https://getfoundry.sh/introduction/installation) + +2. Uruchom [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` nasłuchuje na domyślnym adresie URL dla Foundry, http://localhost:8545, więc nie musimy określać adresu URL dla [polecenia `cast`](https://getfoundry.sh/cast/overview), którego używamy do manipulowania blockchainem. + +3. Podczas pracy w `anvil` dostępnych jest dziesięć kont testowych z ETH — ustaw zmienne środowiskowe dla pierwszego z nich + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. Są to kontrakty, których musimy użyć. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) to kontrakt Uniswap v3, którego używamy do faktycznego handlu. Moglibyśmy handlować bezpośrednio przez pulę, ale jest to znacznie łatwiejsze. + + Dwie ostatnie zmienne to ścieżki Uniswap v3 wymagane do wymiany między WETH i USDC. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. Każde z kont testowych ma 10 000 ETH. Użyj kontraktu WETH, aby opakować 1000 ETH w celu uzyskania 1000 WETH do handlu. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. Użyj `SwapRouter` do handlu 500 WETH na USDC. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + Wywołanie `approve` tworzy limit wydatków, który pozwala `SwapRouter` na wydanie niektórych z naszych tokenów. Kontrakty nie mogą monitorować zdarzeń, więc jeśli przekażemy tokeny bezpośrednio do kontraktu `SwapRouter`, nie będzie on wiedział, że został opłacony. Zamiast tego pozwalamy kontraktowi `SwapRouter` wydać określoną kwotę, a następnie `SwapRouter` to robi. Odbywa się to za pomocą funkcji wywoływanej przez `SwapRouter`, dzięki czemu wie on, czy się powiodła. + +7. Sprawdź, czy masz wystarczającą ilość obu tokenów. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +Teraz, gdy mamy WETH i USDC, możemy faktycznie uruchomić agenta. + +```sh +git checkout 05-trade +uv run agent.py +``` + +Wynik będzie wyglądał podobnie do: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +Aktualna cena: 1843.16 +O 2026-02-06T23:07 spodziewana cena: 1724.41 USD +Sada kont przed transakcją: +Saldo USDC: 927301.578272 +Saldo WETH: 500 +Sprzedaj, spodziewam się, że cena spadnie o 118,75 USD +Wysłano transakcję zatwierdzenia: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +Transakcja zatwierdzenia wykopana. +Wysłano transakcję sprzedaży: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +Transakcja sprzedaży wykopana. +Salda kont po transakcji: +Saldo USDC: 929143,797116 +Saldo WETH: 499 +``` + +Aby faktycznie z niego skorzystać, potrzeba kilku drobnych zmian. + +- W wierszu 14 zmień `MAINNET_URL` na prawdziwy punkt dostępu, taki jak `https://eth.drpc.org` +- W wierszu 28 zmień `PRIVATE_KEY` na swój własny klucz prywatny +- Chyba że jesteś bardzo bogaty i możesz kupować lub sprzedawać 1 ETH każdego dnia dla niesprawdzonego agenta, możesz chcieć zmienić 29, aby zmniejszyć `WETH_TRADE_AMOUNT` + +#### Wyjaśnienie kodu {#trading-code} + +Oto nowy kod. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +Te same zmienne, których użyliśmy w kroku 4. + +```python +WETH_TRADE_AMOUNT=1 +``` + +Kwota do handlu. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +Aby faktycznie handlować, potrzebujemy funkcji `approve`. Chcemy również pokazać salda przed i po, więc potrzebujemy również `balanceOf`. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +W ABI `SwapRouter` potrzebujemy tylko `exactInput`. Istnieje powiązana funkcja, `exactOutput`, której moglibyśmy użyć do zakupu dokładnie jednego WETH, ale dla uproszczenia używamy `exactInput` w obu przypadkach. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +Definicje Web3 dla [`konta`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) i kontraktu `SwapRouter`. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +Parametry transakcji. Potrzebujemy tutaj funkcji, ponieważ [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) musi się zmieniać za każdym razem. + +```python +def approve_token(contract: Contract, amount: int): +``` + +Zatwierdź limit wydatków tokena dla `SwapRouter`. + +```python + 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) +``` + +W ten sposób wysyłamy transakcję w Web3. Najpierw używamy [obiektu `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) do zbudowania transakcji. Następnie używamy [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) do podpisania transakcji za pomocą `PRIVATE_KEY`. Na koniec używamy [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) do wysłania transakcji. + +```python + 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`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) czeka na wykopanie transakcji. W razie potrzeby zwraca potwierdzenie. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +Są to parametry przy sprzedaży WETH. + +```python +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, + } +``` + +W przeciwieństwie do `SELL_PARAMS`, parametry kupna mogą się zmieniać. Kwota wejściowa to koszt 1 WETH, dostępny w `quote`. + +```python +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.") +``` + +Funkcje `buy()` i `sell()` są prawie identyczne. Najpierw zatwierdzamy wystarczający limit wydatków dla `SwapRouter`, a następnie wywołujemy go z poprawną ścieżką i kwotą. + +```python +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)}") +``` + +Zgłoś salda użytkowników w obu walutach. + +```python +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() +``` + +Ten agent działa obecnie tylko raz. Można jednak zmienić go tak, aby działał w sposób ciągły, uruchamiając go z [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) lub zawijając wiersze 368-400 w pętlę i używając [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep), aby poczekać, aż nadejdzie czas na następny cykl. + +## Możliwe ulepszenia {#improvements} + +To nie jest pełna wersja produkcyjna; to tylko przykład, aby nauczyć podstaw. Oto kilka pomysłów na ulepszenia. + +### Inteligentniejszy handel {#smart-trading} + +Istnieją dwa ważne fakty, które agent ignoruje przy podejmowaniu decyzji, co robić. + +- _Wielkość przewidywanej zmiany_. Agent sprzedaje stałą kwotę `WETH`, jeśli oczekuje się spadku ceny, niezależnie od wielkości spadku. + Prawdopodobnie lepiej byłoby ignorować drobne zmiany i sprzedawać w oparciu o to, jak bardzo spodziewamy się spadku ceny. +- _Aktualne portfolio_. Jeśli 10% Twojego portfela to WETH i uważasz, że cena wzrośnie, prawdopodobnie warto kupić więcej. Ale jeśli 90% Twojego portfela to WETH, możesz być wystarczająco wyeksponowany i nie ma potrzeby kupować więcej. Odwrotnie jest, jeśli spodziewasz się spadku ceny. + +### Co, jeśli chcesz zachować swoją strategię handlową w tajemnicy? {#secret} + +Dostawcy AI mogą zobaczyć zapytania, które wysyłasz do ich LLM, co może ujawnić genialny system handlowy, który opracowałeś ze swoim agentem. System handlowy, z którego korzysta zbyt wiele osób, jest bezwartościowy, ponieważ zbyt wiele osób próbuje kupować, gdy chcesz kupić (a cena rośnie) i próbuje sprzedawać, gdy chcesz sprzedać (a cena spada). + +Aby uniknąć tego problemu, można uruchomić LLM lokalnie, na przykład za pomocą [LM-Studio](https://lmstudio.ai/). + +### Od bota AI do agenta AI {#bot-to-agent} + +Można argumentować, że jest to [bot AI, a nie agent AI](/ai-agents/#ai-agents-vs-ai-bots). Implementuje on stosunkowo prostą strategię opartą na predefiniowanych informacjach. Możemy włączyć samodoskonalenie, na przykład, dostarczając listę pul Uniswap v3 i ich najnowszych wartości i pytając, która kombinacja ma najlepszą wartość predykcyjną. + +### Ochrona przed poślizgiem {#slippage-protection} + +Obecnie nie ma [ochrony przed poślizgiem](https://uniswapv3book.com/milestone_3/slippage-protection.html). Jeśli obecne notowanie wynosi 2000 USD, a oczekiwana cena 2100 USD, agent dokona zakupu. Jeśli jednak przed zakupem przez agenta koszt wzrośnie do 2200 USD, dalszy zakup nie ma sensu. + +Aby zaimplementować ochronę przed poślizgiem, należy określić wartość `amountOutMinimum` w wierszach 325 i 334 pliku [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325). + +## Wnioski {#conclusion} + +Mam nadzieję, że wiesz już wystarczająco dużo, aby zacząć z agentami AI. Nie jest to kompleksowy przegląd tematu; istnieją całe książki poświęcone temu zagadnieniu, ale to wystarczy, aby zacząć. Powodzenia! + +[Zobacz więcej mojej pracy tutaj](https://cryptodocguy.pro/). diff --git a/public/content/translations/pt-br/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/pt-br/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..9305476d7c3 --- /dev/null +++ b/public/content/translations/pt-br/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,981 @@ +--- +title: "Crie seu próprio agente de negociação de IA no Ethereum" +description: "Neste tutorial, você aprenderá a criar um agente simples de negociação de IA. Este agente lê informações da cadeia de blocos, solicita uma recomendação a um LLM com base nessas informações, executa a negociação que o LLM recomenda e, em seguida, aguarda e repete." +author: Ori Pomerantz + Ori Pomerantz +tags: [ "IA", "negociação", "agente", "python" ] +skill: intermediate +published: 2026-02-13 +lang: pt-br +sidebarDepth: 3 +--- + +Neste tutorial, você aprenderá a criar um agente simples de negociação de IA. Este agente funciona seguindo estas etapas: + +1. Ler os preços atuais e passados de um token, bem como outras informações potencialmente relevantes +2. Criar uma consulta com essas informações, juntamente com informações de fundo para explicar como elas podem ser relevantes +3. Enviar a consulta e receber de volta um preço projetado +4. Negociar com base na recomendação +5. Aguardar e repetir + +Este agente demonstra como ler informações, traduzi-las em uma consulta que produz uma resposta utilizável e usar essa resposta. Todas essas são etapas necessárias para um agente de IA. Este agente é implementado em Python porque é a linguagem mais comum usada em IA. + +## Por que fazer isso? {#why-do-this} + +Agentes de negociação automatizados permitem que os desenvolvedores selecionem e executem uma estratégia de negociação. [agentes de IA](/ai-agents) permitem estratégias de negociação mais complexas e dinâmicas, usando potencialmente informações e algoritmos que o desenvolvedor nem considerou usar. + +## As ferramentas {#tools} + +Este tutorial usa [Python](https://www.python.org/), a [biblioteca Web3](https://web3py.readthedocs.io/en/stable/) e o [Uniswap v3](https://github.com/Uniswap/v3-periphery) para cotações e negociações. + +### Por que Python? {#python} + +A linguagem mais utilizada para IA é o [Python](https://www.python.org/), por isso a usamos aqui. Não se preocupe se você não conhece Python. A linguagem é muito clara e explicarei exatamente o que ela faz. + +A [biblioteca Web3](https://web3py.readthedocs.io/en/stable/) é a API Python Ethereum mais comum. É bem fácil de usar. + +### Negociando na cadeia de blocos {#trading-on-blockchain} + +Existem [muitas corretoras distribuídas (DEX)](/apps/categories/defi/) que permitem negociar tokens no Ethereum. No entanto, elas tendem a ter taxas de câmbio semelhantes devido à [arbitragem](/developers/docs/smart-contracts/composability/#better-user-experience). + +O [Uniswap](https://app.uniswap.org/) é uma DEX amplamente utilizada que podemos usar tanto para cotações (para ver os valores relativos dos tokens) quanto para negociações. + +### OpenAI {#openai} + +Para um modelo de linguagem grande, escolhi começar com o [OpenAI](https://openai.com/). Para executar a aplicação neste tutorial, você precisará pagar pelo acesso à API. O pagamento mínimo de US$ 5 é mais do que suficiente. + +## Desenvolvimento, passo a passo {#step-by-step} + +Para simplificar o desenvolvimento, prosseguimos em etapas. Cada etapa é um branch no GitHub. + +### Primeiros passos {#getting-started} + +Existem etapas para começar no UNIX ou Linux (incluindo o [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) + +1. Se ainda não tiver, baixe e instale o [Python](https://www.python.org/downloads/). + +2. Clone o repositório do GitHub. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. Instale o [`uv`](https://docs.astral.sh/uv/getting-started/installation/). O comando em seu sistema pode ser diferente. + + ```sh + pipx install uv + ``` + +4. Baixe as bibliotecas. + + ```sh + uv sync + ``` + +5. Ative o ambiente virtual. + + ```sh + source .venv/bin/activate + ``` + +6. Para verificar se o Python e o Web3 estão funcionando corretamente, execute o `python3` e forneça a ele este programa. Você pode inseri-lo no prompt `>>>`; não é necessário criar um arquivo. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### Lendo da cadeia de blocos {#read-blockchain} + +O próximo passo é ler da cadeia de blocos. Para fazer isso, você precisa mudar para o branch `02-read-quote` e usar o `uv` para executar o programa. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +Você deve receber uma lista de objetos `Quote`, cada um com um timestamp, um preço e o ativo (atualmente sempre `WETH/USDC`). + +Aqui está uma explicação linha por linha. + +```python +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 +``` + +Importe as bibliotecas que precisamos. Eles são explicados abaixo quando usados. + +```python +print = functools.partial(print, flush=True) +``` + +Substitui o `print` do Python por uma versão que sempre descarrega a saída imediatamente. Isso é útil em um script de longa duração, porque não queremos esperar por atualizações de status ou saídas de depuração. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +Uma URL para chegar à rede principal. Você pode obter uma de um [Nó como serviço](/developers/docs/nodes-and-clients/nodes-as-a-service/) ou usar uma das anunciadas na [Chainlist](https://chainlist.org/chain/1). + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +Um bloco da rede principal do Ethereum geralmente acontece a cada doze segundos, então este é o número de blocos que esperamos que aconteçam em um período de tempo. Observe que este não é um número exato. Quando o [propositor de bloco](/developers/docs/consensus-mechanisms/pos/block-proposal/) está inativo, esse bloco é pulado e o tempo para o próximo bloco é de 24 segundos. Se quiséssemos obter o bloco exato para um timestamp, usaríamos a [busca binária](https://en.wikipedia.org/wiki/Binary_search). No entanto, isso é próximo o suficiente para nossos propósitos. Prever o futuro não é uma ciência exata. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +O tamanho do ciclo. Revisamos as cotações uma vez por ciclo e tentamos estimar o valor no final do próximo ciclo. + +```python +# O endereço do pool que estamos lendo +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +Os valores da cotação são retirados do pool USDC/WETH do Uniswap 3 no endereço [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract). Este endereço já está no formato checksum, mas é melhor usar [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) para tornar o código reutilizável. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +Estas são as [IABs](https://docs.soliditylang.org/en/latest/abi-spec.html) para os dois contratos que precisamos contatar. Para manter o código conciso, incluímos apenas as funções que precisamos chamar. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +Inicie a biblioteca [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) e conecte-se a um nó Ethereum. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +Esta é uma maneira de criar uma classe de dados em Python. O tipo de dados [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) é usado para se conectar ao contrato. Observe o `(frozen=True)`. Em Python, os [booleanos](https://en.wikipedia.org/wiki/Boolean_data_type) são definidos como `True` ou `False`, com letra maiúscula. Esta classe de dados é `frozen` (congelada), o que significa que os campos não podem ser modificados. + +Observe a indentação. Em contraste com as [linguagens derivadas de C](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages), o Python usa indentação para denotar blocos. O interpretador Python sabe que a definição a seguir não faz parte desta classe de dados porque não começa na mesma indentação que os campos da classe de dados. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +O tipo [`Decimal`](https://docs.python.org/3/library/decimal.html) é usado para lidar com precisão com frações decimais. + +```python + def get_price(self, block: int) -> Decimal: +``` + +Esta é a maneira de definir uma função em Python. A definição é indentada para mostrar que ainda faz parte de `PoolInfo`. + +Em uma função que faz parte de uma classe de dados, o primeiro parâmetro é sempre `self`, a instância da classe de dados que a chamou. Aqui há outro parâmetro, o número do bloco. + +```python + assert block <= w3.eth.block_number, "O bloco está no futuro" +``` + +Se pudéssemos ler o futuro, não precisaríamos de IA para negociação. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +A sintaxe para chamar uma função na EVM a partir do Web3 é esta: `.functions.().call()`. Os parâmetros podem ser os parâmetros da função da EVM (se houver; aqui não há) ou [parâmetros nomeados](https://en.wikipedia.org/wiki/Named_parameter) para modificar o comportamento da cadeia de blocos. Aqui usamos um, `block_identifier`, para especificar [o número do bloco](/developers/docs/apis/json-rpc/#default-block) no qual desejamos executar. + +O resultado é [esta struct, em formato de array](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). O primeiro valor é uma função da taxa de câmbio entre os dois tokens. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +Para reduzir os cálculos na cadeia, o Uniswap v3 não armazena o fator de câmbio real, mas sim sua raiz quadrada. Como a EVM não suporta matemática de ponto flutuante ou frações, em vez do valor real, a resposta é price296 + +```python + # (token1 por token0) + return 1/(raw_price * self.decimal_factor) +``` + +O preço bruto que obtemos é o número de `token0` que obtemos para cada `token1`. Em nosso pool, o `token0` é USDC (moeda estável com o mesmo valor de um dólar americano) e o `token1` é [WETH](https://opensea.io/learn/blockchain/what-is-weth). O valor que realmente queremos é o número de dólares por WETH, não o inverso. + +O fator decimal é a proporção entre os [fatores decimais](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) para os dois tokens. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +Essa classe de dados representa uma cotação: o preço de um ativo específico em um determinado momento. Neste ponto, o campo `asset` é irrelevante, porque usamos um único pool e, portanto, temos um único ativo. No entanto, adicionaremos mais ativos posteriormente. + +```python +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 + ) +``` + +Esta função recebe um endereço e retorna informações sobre o contrato do token nesse endereço. Para criar um novo [`Contrato` Web3](https://web3py.readthedocs.io/en/stable/web3.contract.html), fornecemos o endereço e a IAB para `w3.eth.contract`. + +```python +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) + ) +``` + +Esta função retorna tudo o que precisamos sobre [um pool específico](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol). A sintaxe `f""` é uma [string formatada](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +Obtenha um objeto `Quote`. O valor padrão para `block_number` é `None` (sem valor). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +Se um número de bloco não foi especificado, use `w3.eth.block_number`, que é o número do bloco mais recente. Essa é a sintaxe para [uma instrução `if`](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement). + +Pode parecer que seria melhor apenas definir o padrão como `w3.eth.block_number`, mas isso não funciona bem porque seria o número do bloco no momento em que a função é definida. Em um agente de longa duração, isso seria um problema. + +```python + 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 + ) +``` + +Use [a biblioteca `datetime`](https://docs.python.org/3/library/datetime.html) para formatá-lo em um formato legível para humanos e modelos de linguagem grandes (LLMs). Use [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) para arredondar o valor para duas casas decimais. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +Em Python, você define uma [lista](https://docs.python.org/3/library/stdtypes.html#typesseq-list) que só pode conter um tipo específico usando `list[]`. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +Em Python, um [loop `for`](https://docs.python.org/3/tutorial/controlflow.html#for-statements) normalmente itera sobre uma lista. A lista de números de bloco para encontrar cotações vem de [`range`](https://docs.python.org/3/library/stdtypes.html#range). + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +Para cada número de bloco, obtenha um objeto `Quote` e anexe-o à lista `quotes`. Em seguida, retorne essa lista. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +Este é o código principal do script. Leia as informações do pool, obtenha doze cotações e use [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) para exibi-las. + +### Criando um prompt {#prompt} + +Em seguida, precisamos converter essa lista de cotações em um prompt para um LLM e obter um valor futuro esperado. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +A saída agora será um prompt para um LLM, semelhante a: + +``` +Dadas estas cotações: +Ativo: WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +Ativo: WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +Qual você esperaria que fosse o valor para WETH/USDC no momento 2026-02-02T17:56? + +Forneça sua resposta como um único número arredondado para duas casas decimais, +sem nenhum outro texto. +``` + +Observe que há cotações para dois ativos aqui, `WETH/USDC` e `WBTC/WETH`. Adicionar cotações de outro ativo pode melhorar a precisão da previsão. + +#### Como é um prompt {#prompt-explanation} + +Este prompt contém três seções, que são bastante comuns em prompts de LLM. + +1. Informação. Os LLMs têm muitas informações de seu treinamento, mas geralmente não têm as mais recentes. É por isso que precisamos recuperar as cotações mais recentes aqui. Adicionar informações a um prompt é chamado de [geração aumentada por recuperação (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +2. A pergunta real. É isso que queremos saber. + +3. Instruções de formatação de saída. Normalmente, um LLM nos dará uma estimativa com uma explicação de como chegou a ela. Isso é melhor para humanos, mas um programa de computador só precisa do resultado final. + +#### Explicação do código {#prompt-code} + +Aqui está o novo código. + +```python +from datetime import datetime, timezone, timedelta +``` + +Precisamos fornecer ao LLM o tempo para o qual queremos uma estimativa. Para obter um tempo "n minutos/horas/dias" no futuro, usamos [a classe `timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta). + +```python +# Os endereços dos pools que estamos lendo +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +Temos dois pools que precisamos ler. + +```python +@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 # (token1 per token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +No pool WETH/USDC, queremos saber quantos `token0` (USDC) precisamos para comprar um `token1` (WETH). No pool WETH/WBTC, queremos saber quantos `token1` (WETH) precisamos para comprar um `token0` (WBTC, que é Bitcoin embrulhado). Precisamos rastrear se a proporção do pool precisa ser invertida. + +```python +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 + ) +``` + +Para saber se um pool precisa ser revertido, precisamos obter isso como entrada para `read_pool`. Além disso, o símbolo do ativo precisa ser configurado corretamente. + +A sintaxe ` if else ` é o equivalente em Python do [operador condicional ternário](https://en.wikipedia.org/wiki/Ternary_conditional_operator), que em uma linguagem derivada de C seria ` ? : `. + +```python +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 +``` + +Esta função cria uma string que formata uma lista de objetos `Quote`, supondo que todos se apliquem ao mesmo ativo. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +Em Python, os [literais de string de várias linhas](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) são escritos como `"""` .... `"""`. + +```python +Dadas estas cotações: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +Aqui, usamos o padrão [MapReduce](https://en.wikipedia.org/wiki/MapReduce) para gerar uma string para cada lista de cotações com `format_quotes` e, em seguida, reduzi-las a uma única string para uso no prompt. + +```python +Qual você esperaria que fosse o valor para {asset} no momento {expected_time}? + +Forneça sua resposta como um único número arredondado para duas casas decimais, +sem nenhum outro texto. + """ +``` + +O resto do prompt é como esperado. + +```python +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 +) +``` + +Revise os dois pools e obtenha cotações de ambos. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +Determine o ponto de tempo futuro para o qual queremos a estimativa e crie o prompt. + +### Interface com um LLM {#interface-llm} + +Em seguida, solicitamos um LLM real e recebemos um valor futuro esperado. Eu escrevi este programa usando OpenAI, então se você quiser usar um provedor diferente, precisará ajustá-lo. + +1. Obtenha uma [conta OpenAI](https://auth.openai.com/create-account) + +2. [Financie a conta](https://platform.openai.com/settings/organization/billing/overview) — o valor mínimo no momento da escrita é de US$ 5 + +3. [Crie uma chave de API](https://platform.openai.com/settings/organization/api-keys) + +4. Na linha de comando, exporte a chave de API para que seu programa possa usá-la + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. Faça o checkout e execute o agente + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +Aqui está o novo código. + +```python +from openai import OpenAI + +open_ai = OpenAI() # O cliente lê a variável de ambiente OPENAI_API_KEY +``` + +Importe e instancie a API OpenAI. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +Chame a API OpenAI (`open_ai.chat.completions.create`) para criar a resposta. + +```python +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") +``` + +Mostre o preço e forneça uma recomendação de compra ou venda. + +#### Testando as previsões {#testing-the-predictions} + +Agora que podemos gerar previsões, também podemos usar dados históricos para avaliar se produzimos previsões úteis. + +```sh +uv run test-predictor.py +``` + +O resultado esperado é semelhante a: + +``` +Previsão para 2026-01-05T19:50: previsto 3138.93 USD, real 3218.92 USD, erro 79.99 USD +Previsão para 2026-01-06T19:56: previsto 3243.39 USD, real 3221.08 USD, erro 22.31 USD +Previsão para 2026-01-07T20:02: previsto 3223.24 USD, real 3146.89 USD, erro 76.35 USD +Previsão para 2026-01-08T20:11: previsto 3150.47 USD, real 3092.04 USD, erro 58.43 USD +. +. +. +Previsão para 2026-01-31T22:33: previsto 2637.73 USD, real 2417.77 USD, erro 219.96 USD +Previsão para 2026-02-01T22:41: previsto 2381.70 USD, real 2318.84 USD, erro 62.86 USD +Previsão para 2026-02-02T22:49: previsto 2234.91 USD, real 2349.28 USD, erro 114.37 USD +Erro médio de previsão em 29 previsões: 83.87103448275862068965517241 USD +Mudança média por recomendação: 4.787931034482758620689655172 USD +Variância padrão das mudanças: 104.42 USD +Dias lucrativos: 51.72% +Dias com perdas: 48.28% +``` + +A maior parte do testador é idêntica ao agente, mas aqui estão as partes que são novas ou modificadas. + +```python +CYCLES_FOR_TEST = 40 # Para o backtest, quantos ciclos testamos + +# Obtenha muitas cotações +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 +) +``` + +Analisamos `CYCLES_FOR_TEST` (especificado como 40 aqui) dias para trás. + +```python +# Crie previsões e verifique-as em relação ao histórico real + +total_error = Decimal(0) +changes = [] +``` + +Existem dois tipos de erros nos quais estamos interessados. O primeiro, `total_error`, é simplesmente a soma dos erros que o previsor cometeu. + +Para entender o segundo, `changes`, precisamos nos lembrar do propósito do agente. Não é prever a proporção WETH/USDC (preço do ETH). É para emitir recomendações de venda e compra. Se o preço atual for US$ 2.000 e ele prever US$ 2.010 para amanhã, não nos importamos se o resultado real for US$ 2.020 e ganharmos dinheiro extra. Mas nós nos importamos se ele previu US$ 2.010 e comprou ETH com base nessa recomendação, e o preço cair para US$ 1.990. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +Só podemos analisar os casos em que o histórico completo (os valores usados para a previsão e o valor do mundo real para compará-lo) está disponível. Isso significa que o caso mais recente deve ser aquele que começou há `CYCLES_BACK`. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +Use [fatias (slices)](https://www.w3schools.com/python/ref_func_slice.asp) para obter o mesmo número de amostras que o número que o agente usa. O código entre aqui e o próximo segmento é o mesmo código para obter uma previsão que temos no agente. + +```python + 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 +``` + +Obtenha o preço previsto, o preço real e o preço no momento da previsão. Precisamos do preço no momento da previsão para determinar se a recomendação foi de compra ou de venda. + +```python + 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") +``` + +Calcule o erro e adicione-o ao total. + +```python + 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) +``` + +Para `changes`, queremos o impacto monetário da compra ou venda de um ETH. Então, primeiro, precisamos determinar a recomendação, depois avaliar como o preço real mudou e se a recomendação gerou lucro (mudança positiva) ou prejuízo (mudança negativa). + +```python +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") +``` + +Relate os resultados. + +```python +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%}") +``` + +Use o [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) para contar o número de dias lucrativos e o número de dias com prejuízo. O resultado é um objeto de filtro, que precisamos converter em uma lista para obter o comprimento. + +### Envio de transações {#submit-txn} + +Agora precisamos realmente enviar as transações. No entanto, não quero gastar dinheiro de verdade neste momento, antes que o sistema seja comprovado. Em vez disso, criaremos uma bifurcação local da rede principal e "negociaremos" nessa rede. + +Aqui estão as etapas para criar uma bifurcação local e habilitar a negociação. + +1. Instale o [Foundry](https://getfoundry.sh/introduction/installation) + +2. Inicie o [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` está escutando na URL padrão para o Foundry, http://localhost:8545, então não precisamos especificar a URL para o [comando `cast`](https://getfoundry.sh/cast/overview) que usamos para manipular a cadeia de blocos. + +3. Ao executar no `anvil`, há dez contas de teste que têm ETH — defina as variáveis de ambiente para a primeira + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. Estes são os contratos que precisamos usar. O [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) é o contrato Uniswap v3 que usamos para negociar de fato. Poderíamos negociar diretamente através do pool, mas isso é muito mais fácil. + + As duas variáveis inferiores são os caminhos do Uniswap v3 necessários para trocar entre WETH e USDC. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. Cada uma das contas de teste tem 10.000 ETH. Use o contrato WETH para embrulhar 1000 ETH e obter 1000 WETH para negociação. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. Use o `SwapRouter` para negociar 500 WETH por USDC. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + A chamada `approve` cria uma permissão que permite que o `SwapRouter` gaste alguns dos nossos tokens. Contratos não podem monitorar eventos, portanto, se transferirmos tokens diretamente para o contrato `SwapRouter`, ele não saberia que foi pago. Em vez disso, permitimos que o contrato `SwapRouter` gaste uma certa quantia, e então o `SwapRouter` o faz. Isso é feito através de uma função chamada pelo `SwapRouter`, para que ele saiba se foi bem-sucedido. + +7. Verifique se você tem o suficiente de ambos os tokens. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +Agora que temos WETH e USDC, podemos realmente executar o agente. + +```sh +git checkout 05-trade +uv run agent.py +``` + +A saída será semelhante a: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +Preço atual: 1843.16 +Em 2026-02-06T23:07, preço esperado: 1724.41 USD +Saldos da conta antes da negociação: +Saldo de USDC: 927301.578272 +Saldo de WETH: 500 +Vender, espero que o preço caia 118,75 USD +Transação de aprovação enviada: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +Transação de aprovação minerada. +Transação de venda enviada: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +Transação de venda minerada. +Saldos da conta após a negociação: +Saldo de USDC: 929143.797116 +Saldo de WETH: 499 +``` + +Para realmente usá-lo, você precisa de algumas pequenas alterações. + +- Na linha 14, altere `MAINNET_URL` para um ponto de acesso real, como `https://eth.drpc.org` +- Na linha 28, altere `PRIVATE_KEY` para sua própria chave privada +- A menos que você seja muito rico e possa comprar ou vender 1 ETH por dia para um agente não comprovado, você pode querer alterar a linha 29 para diminuir o `WETH_TRADE_AMOUNT` + +#### Explicação do código {#trading-code} + +Aqui está o novo código. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +As mesmas variáveis que usamos na etapa 4. + +```python +WETH_TRADE_AMOUNT=1 +``` + +A quantia a ser negociada. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +Para realmente negociar, precisamos da função `approve`. Também queremos mostrar os saldos antes e depois, então também precisamos de `balanceOf`. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +Na IAB do `SwapRouter`, só precisamos de `exactInput`. Existe uma função relacionada, `exactOutput`, que poderíamos usar para comprar exatamente um WETH, mas para simplificar, usamos apenas `exactInput` em ambos os casos. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +As definições do Web3 para a [`conta`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) e o contrato `SwapRouter`. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +Os parâmetros da transação. Precisamos de uma função aqui porque [o nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) deve mudar a cada vez. + +```python +def approve_token(contract: Contract, amount: int): +``` + +Aprove uma permissão de token para o `SwapRouter`. + +```python + 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) +``` + +É assim que enviamos uma transação no Web3. Primeiro, usamos [o objeto `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) para construir a transação. Em seguida, usamos [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) para assinar a transação, usando `PRIVATE_KEY`. Finalmente, usamos [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) para enviar a transação. + +```python + 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`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) espera até que a transação seja minerada. Ele retorna o recibo, se necessário. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +Estes são os parâmetros ao vender WETH. + +```python +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, + } +``` + +Em contraste com `SELL_PARAMS`, os parâmetros de compra podem mudar. O valor de entrada é o custo de 1 WETH, conforme disponível em `quote`. + +```python +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.") +``` + +As funções `buy()` e `sell()` são quase idênticas. Primeiro, aprovamos uma permissão suficiente para o `SwapRouter` e, em seguida, o chamamos com o caminho e a quantia corretos. + +```python +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)}") +``` + +Relate os saldos do usuário em ambas as moedas. + +```python +print("Saldos da conta antes da negociação:") +balances() + +if (expected_price > current_price): + print(f"Comprar, espero que o preço suba em {expected_price - current_price} USD") + buy(wethusdc_quotes[-1]) +else: + print(f"Vender, espero que o preço caia em {current_price - expected_price} USD") + sell() + +print("Saldos da conta após a negociação:") +balances() +``` + +Este agente atualmente só funciona uma vez. No entanto, você pode alterá-lo para funcionar continuamente, executando-o a partir do [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) ou envolvendo as linhas 368-400 em um loop e usando [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) para esperar até a hora do próximo ciclo. + +## Possíveis melhorias {#improvements} + +Esta não é uma versão de produção completa; é apenas um exemplo para ensinar o básico. Aqui estão algumas ideias de melhorias. + +### Negociação mais inteligente {#smart-trading} + +Há dois fatos importantes que o agente ignora ao decidir o que fazer. + +- _A magnitude da mudança antecipada_. O agente vende uma quantia fixa de `WETH` se o preço for esperado para diminuir, independentemente da magnitude do declínio. + Pode-se argumentar que seria melhor ignorar pequenas alterações e vender com base no quanto esperamos que o preço caia. +- _O portfólio atual_. Se 10% do seu portfólio estiver em WETH e você achar que o preço vai subir, provavelmente faz sentido comprar mais. Mas se 90% do seu portfólio estiver em WETH, você pode estar suficientemente exposto e não há necessidade de comprar mais. O inverso é verdadeiro se você espera que o preço caia. + +### E se você quiser manter sua estratégia de negociação em segredo? {#secret} + +Os fornecedores de IA podem ver as consultas que você envia para seus LLMs, o que poderia expor o sistema de negociação genial que você desenvolveu com seu agente. Um sistema de negociação que muitas pessoas usam não tem valor, porque muitas pessoas tentam comprar quando você quer comprar (e o preço sobe) e tentam vender quando você quer vender (e o preço cai). + +Você pode executar um LLM localmente, por exemplo, usando o [LM-Studio](https://lmstudio.ai/), para evitar este problema. + +### De bot de IA para agente de IA {#bot-to-agent} + +Você pode argumentar que este é [um bot de IA, não um agente de IA](/ai-agents/#ai-agents-vs-ai-bots). Ele implementa uma estratégia relativamente simples que depende de informações predefinidas. Podemos habilitar a auto-melhoria, por exemplo, fornecendo uma lista de pools do Uniswap v3 e seus valores mais recentes e perguntando qual combinação tem o melhor valor preditivo. + +### Proteção contra slippage {#slippage-protection} + +Atualmente não há [proteção contra slippage](https://uniswapv3book.com/milestone_3/slippage-protection.html). Se a cotação atual for de US$ 2.000 e o preço esperado for de US$ 2.100, o agente comprará. No entanto, se antes de o agente comprar o custo subir para US$ 2.200, não faz mais sentido comprar. + +Para implementar a proteção contra slippage, especifique um valor de `amountOutMinimum` nas linhas 325 e 334 de [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325). + +## Conclusão {#conclusion} + +Esperamos que agora você saiba o suficiente para começar a usar agentes de IA. Esta não é uma visão geral abrangente do assunto; existem livros inteiros dedicados a isso, mas isso é suficiente para você começar. Boa sorte! + +[Veja aqui mais do meu trabalho](https://cryptodocguy.pro/). diff --git a/public/content/translations/ru/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/ru/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..f23d86e62f4 --- /dev/null +++ b/public/content/translations/ru/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "Создайте своего торгового агента с ИИ на Ethereum" +description: "В этом руководстве вы узнаете, как создать простого торгового агента с ИИ. Этот агент считывает информацию из блокчейна, запрашивает у LLM рекомендацию на основе этой информации, выполняет сделку, рекомендованную LLM, а затем ожидает и повторяет." +author: Ori Pomerantz +tags: [ "ИИ", "торговля", "агент", "python" ] +skill: intermediate +published: 2026-02-13 +lang: ru +sidebarDepth: 3 +--- + +В этом руководстве вы узнаете, как создать простого торгового агента с ИИ. Этот агент работает следующим образом: + +1. Считывание текущих и прошлых цен токена, а также другой потенциально релевантной информации +2. Создание запроса с этой информацией, а также с фоновой информацией для объяснения ее возможной релевантности +3. Отправка запроса и получение прогнозируемой цены +4. Торговля на основе рекомендации +5. Ожидание и повторение + +Этот агент демонстрирует, как считывать информацию, преобразовывать ее в запрос, который дает применимый ответ, и использовать этот ответ. Все это необходимые шаги для агента с ИИ. Этот агент реализован на языке Python, поскольку это самый распространенный язык, используемый в сфере ИИ. + +## Зачем это делать? {#why-do-this} + +Автоматизированные торговые агенты позволяют разработчикам выбирать и выполнять торговую стратегию. [Агенты с ИИ](/ai-agents) позволяют использовать более сложные и динамичные торговые стратегии, потенциально используя информацию и алгоритмы, которые разработчик даже не рассматривал для использования. + +## Инструменты {#tools} + +В этом руководстве используется [Python](https://www.python.org/), библиотека [Web3](https://web3py.readthedocs.io/en/stable/), и [Uniswap v3](https://github.com/Uniswap/v3-periphery) для получения котировок и торговли. + +### Почему Python? {#python} + +Самый широко используемый язык для ИИ — это [Python](https://www.python.org/), поэтому мы используем его здесь. Не беспокойтесь, если вы не знаете Python. Язык очень понятен, и я объясню, что именно он делает. + +Библиотека [Web3](https://web3py.readthedocs.io/en/stable/) является самым распространенным API для Ethereum на языке Python. Она довольно проста в использовании. + +### Торговля в блокчейне {#trading-on-blockchain} + +Существует [много распределенных бирж (DEX)](/apps/categories/defi/), которые позволяют торговать токенами на Ethereum. Однако у них, как правило, схожие обменные курсы благодаря [арбитражу](/developers/docs/smart-contracts/composability/#better-user-experience). + +[Uniswap](https://app.uniswap.org/) — это широко используемая DEX, которую мы можем использовать как для получения котировок (чтобы увидеть относительную стоимость токенов), так и для совершения сделок. + +### OpenAI {#openai} + +Для работы с большой языковой моделью я решил начать с [OpenAI](https://openai.com/). Для запуска приложения из этого руководства вам понадобится оплатить доступ к API. Минимального платежа в 5 $ более чем достаточно. + +## Пошаговая разработка {#step-by-step} + +Чтобы упростить разработку, мы будем действовать поэтапно. Каждый шаг — это отдельная ветка в GitHub. + +### Начало работы {#getting-started} + +Вот шаги для начала работы в UNIX или Linux (включая [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) + +1. Если у вас еще нет, скачайте и установите [Python](https://www.python.org/downloads/). + +2. Клонируйте репозиторий GitHub. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. Установите [`uv`](https://docs.astral.sh/uv/getting-started/installation/). Команда в вашей системе может отличаться. + + ```sh + pipx install uv + ``` + +4. Скачайте библиотеки. + + ```sh + uv sync + ``` + +5. Активируйте виртуальное окружение. + + ```sh + source .venv/bin/activate + ``` + +6. Чтобы убедиться, что Python и Web3 работают корректно, запустите `python3` и передайте ему эту программу. Вы можете ввести ее в строке `>>>`; нет необходимости создавать файл. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### Чтение из блокчейна {#read-blockchain} + +Следующий шаг — чтение из блокчейна. Для этого вам нужно переключиться на ветку `02-read-quote`, а затем использовать `uv` для запуска программы. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +Вы должны получить список объектов `Quote`, каждый из которых содержит временную метку, цену и актив (в настоящее время это всегда `WETH/USDC`). + +Вот пошаговое объяснение. + +```python +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 +``` + +Импортируйте необходимые нам библиотеки. Они объясняются ниже, по мере их использования. + +```python +print = functools.partial(print, flush=True) +``` + +Заменяет `print` из Python на версию, которая всегда немедленно сбрасывает вывод. Это полезно в долго работающем скрипте, потому что мы не хотим ждать обновлений статуса или вывода для отладки. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +URL для доступа к основной сети. Вы можете получить его у сервиса [«Узел как услуга»](/developers/docs/nodes-and-clients/nodes-as-a-service/) или использовать один из тех, что рекламируются в [Chainlist](https://chainlist.org/chain/1). + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +Блок в основной сети Ethereum обычно создается каждые двенадцать секунд, поэтому это количество блоков, которое мы ожидаем за определенный период времени. Обратите внимание, что это не точное значение. Когда [предлагающий блок](/developers/docs/consensus-mechanisms/pos/block-proposal/) недоступен, этот блок пропускается, и время до следующего блока составляет 24 секунды. Если бы мы хотели получить точный блок по временной метке, мы бы использовали [двоичный поиск](https://en.wikipedia.org/wiki/Binary_search). Однако для наших целей этого достаточно. Предсказание будущего — не точная наука. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +Размер цикла. Мы просматриваем котировки один раз за цикл и пытаемся оценить стоимость в конце следующего цикла. + +```python +# Адрес пула, который мы считываем +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +Значения котировок берутся из пула Uniswap 3 USDC/WETH по адресу [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract). Этот адрес уже в формате с контрольной суммой, но лучше использовать [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address), чтобы сделать код повторно используемым. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +Это [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html) для двух контрактов, с которыми нам нужно взаимодействовать. Для краткости кода мы включаем только те функции, которые нам нужно вызывать. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +Инициализируйте библиотеку [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) и подключитесь к узлу Ethereum. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +Это один из способов создания класса данных в Python. Тип данных [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) используется для подключения к контракту. Обратите внимание на `(frozen=True)`. В Python [логические значения](https://en.wikipedia.org/wiki/Boolean_data_type) определяются как `True` или `False`, с заглавной буквы. Этот класс данных является `frozen`, что означает, что поля не могут быть изменены. + +Обратите внимание на отступы. В отличие от [C-подобных языков](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages), Python использует отступы для обозначения блоков. Интерпретатор Python знает, что следующее определение не является частью этого класса данных, потому что оно не начинается с тем же отступом, что и поля класса данных. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +Тип [`Decimal`](https://docs.python.org/3/library/decimal.html) используется для точной обработки десятичных дробей. + +```python + def get_price(self, block: int) -> Decimal: +``` + +Так определяется функция в Python. Определение имеет отступ, чтобы показать, что оно по-прежнему является частью `PoolInfo`. + +В функции, которая является частью класса данных, первым параметром всегда является `self` — экземпляр класса данных, который ее вызвал. Здесь есть еще один параметр — номер блока. + +```python + assert block <= w3.eth.block_number, "Block is in the future" +``` + +Если бы мы могли читать будущее, нам бы не нужен был ИИ для торговли. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +Синтаксис для вызова функции в EVM из Web3 следующий: `.functions.().call()`. Параметрами могут быть параметры функции EVM (если они есть; здесь их нет) или [именованные параметры](https://en.wikipedia.org/wiki/Named_parameter) для изменения поведения блокчейна. Здесь мы используем один, `block_identifier`, чтобы указать [номер блока](/developers/docs/apis/json-rpc/#default-block), в котором мы хотим выполнить операцию. + +Результатом является [эта структура в виде массива](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). Первое значение — это функция обменного курса между двумя токенами. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +Для сокращения вычислений в сети Uniswap v3 хранит не фактический коэффициент обмена, а его квадратный корень. Поскольку EVM не поддерживает математику с плавающей запятой или дроби, вместо фактического значения ответ будет price296 + +```python + # (token1 per token0) + return 1/(raw_price * self.decimal_factor) +``` + +Необработанная цена, которую мы получаем, — это количество `token0`, которое мы получаем за каждый `token1`. В нашем пуле `token0` — это USDC (стабильная монета с такой же стоимостью, как у доллара США), а `token1` — это [WETH](https://opensea.io/learn/blockchain/what-is-weth). Значение, которое нам действительно нужно, — это количество долларов за WETH, а не обратное значение. + +Десятичный множитель — это соотношение между [десятичными множителями](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) для двух токенов. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +Этот класс данных представляет собой котировку: цену определенного актива в данный момент времени. На данный момент поле `asset` не имеет значения, поскольку мы используем один пул и, следовательно, имеем один актив. Однако позже мы добавим больше активов. + +```python +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 + ) +``` + +Эта функция принимает адрес и возвращает информацию о контракте токена по этому адресу. Чтобы создать новый [`Contract` Web3](https://web3py.readthedocs.io/en/stable/web3.contract.html), мы передаем адрес и ABI в `w3.eth.contract`. + +```python +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) + ) +``` + +Эта функция возвращает все, что нам нужно о [конкретном пуле](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol). Синтаксис `f""` — это [форматированная строка](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +Получить объект `Quote`. Значение по умолчанию для `block_number` — `None` (нет значения). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +Если номер блока не был указан, используется `w3.eth.block_number`, который является последним номером блока. Это синтаксис для [оператора `if`](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement). + +Может показаться, что было бы лучше просто установить значение по умолчанию `w3.eth.block_number`, но это не очень хорошо работает, потому что это был бы номер блока на момент определения функции. В долго работающем агенте это стало бы проблемой. + +```python + 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 + ) +``` + +Используйте [библиотеку `datetime`](https://docs.python.org/3/library/datetime.html), чтобы отформатировать ее в формат, читаемый для людей и больших языковых моделей (LLM). Используйте [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize), чтобы округлить значение до двух десятичных знаков. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +В Python вы определяете [список](https://docs.python.org/3/library/stdtypes.html#typesseq-list), который может содержать только определенный тип, используя `list[]`. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +В Python [цикл `for`](https://docs.python.org/3/tutorial/controlflow.html#for-statements) обычно итерируется по списку. Список номеров блоков для поиска котировок получается из [`range`](https://docs.python.org/3/library/stdtypes.html#range). + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +Для каждого номера блока получите объект `Quote` и добавьте его в список `quotes`. Затем верните этот список. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +Это основной код скрипта. Прочитайте информацию о пуле, получите двенадцать котировок и [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) их. + +### Создание подсказки {#prompt} + +Далее нам нужно преобразовать этот список котировок в подсказку для LLM и получить ожидаемое будущее значение. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +Результатом будет подсказка для LLM, подобная этой: + +``` +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. +``` + +Обратите внимание, что здесь есть котировки для двух активов, `WETH/USDC` и `WBTC/WETH`. Добавление котировок из другого актива может повысить точность прогноза. + +#### Как выглядит подсказка {#prompt-explanation} + +Эта подсказка содержит три раздела, которые довольно часто встречаются в подсказках для LLM. + +1. Информация. LLM обладают большим объемом информации, полученной в ходе обучения, но обычно у них нет самой последней. Именно по этой причине нам нужно получить здесь последние котировки. Добавление информации в подсказку называется [поисково-дополненной генерацией (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +2. Собственно вопрос. Это то, что мы хотим знать. + +3. Инструкции по форматированию вывода. Обычно LLM дает нам оценку с объяснением, как она была получена. Это лучше для людей, но компьютерной программе нужна только суть. + +#### Объяснение кода {#prompt-code} + +Вот новый код. + +```python +from datetime import datetime, timezone, timedelta +``` + +Нам нужно предоставить LLM время, для которого мы хотим получить оценку. Чтобы получить время «n минут/часов/дней» в будущем, мы используем [класс `timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta). + +```python +# Адреса пулов, которые мы считываем +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +У нас есть два пула, которые нам нужно прочитать. + +```python +@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 # (token1 per token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +В пуле WETH/USDC мы хотим знать, сколько `token0` (USDC) нам нужно, чтобы купить один `token1` (WETH). В пуле WETH/WBTC мы хотим знать, сколько `token1` (WETH) нам нужно, чтобы купить один `token0` (WBTC, что является обернутым Bitcoin). Нам нужно отслеживать, нужно ли инвертировать соотношение пула. + +```python +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 + ) +``` + +Чтобы знать, нужно ли инвертировать пул, мы должны получить это как входные данные для `read_pool`. Кроме того, символ актива должен быть настроен правильно. + +Синтаксис ` if else ` является эквивалентом Python [тернарного условного оператора](https://en.wikipedia.org/wiki/Ternary_conditional_operator), который в C-подобном языке был бы ` ? : `. + +```python +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 +``` + +Эта функция строит строку, которая форматирует список объектов `Quote`, предполагая, что все они относятся к одному и тому же активу. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +В Python [многострочные строковые литералы](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) пишутся как `"""` .... `"""`. + +```python +Given these quotes: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +Здесь мы используем шаблон [MapReduce](https://en.wikipedia.org/wiki/MapReduce) для генерации строки для каждого списка котировок с помощью `format_quotes`, а затем объединяем их в одну строку для использования в подсказке. + +```python +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. + """ +``` + +Остальная часть подсказки соответствует ожиданиям. + +```python +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 +) +``` + +Просмотрите два пула и получите котировки из обоих. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +Определите будущий момент времени, для которого мы хотим получить оценку, и создайте подсказку. + +### Взаимодействие с LLM {#interface-llm} + +Далее мы запрашиваем реальную LLM и получаем ожидаемое будущее значение. Я написал эту программу с использованием OpenAI, поэтому, если вы хотите использовать другого провайдера, вам нужно будет ее скорректировать. + +1. Получите [аккаунт OpenAI](https://auth.openai.com/create-account) + +2. [Пополните счет](https://platform.openai.com/settings/organization/billing/overview) — минимальная сумма на момент написания составляет 5 долларов + +3. [Создайте ключ API](https://platform.openai.com/settings/organization/api-keys) + +4. В командной строке экспортируйте ключ API, чтобы ваша программа могла его использовать + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. Получите и запустите агент + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +Вот новый код. + +```python +from openai import OpenAI + +open_ai = OpenAI() # Клиент считывает переменную окружения OPENAI_API_KEY +``` + +Импортируйте и создайте экземпляр API OpenAI. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +Вызовите API OpenAI (`open_ai.chat.completions.create`) для создания ответа. + +```python +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") +``` + +Выведите цену и дайте рекомендацию на покупку или продажу. + +#### Тестирование прогнозов {#testing-the-predictions} + +Теперь, когда мы можем генерировать прогнозы, мы также можем использовать исторические данные для оценки, создаем ли мы полезные прогнозы. + +```sh +uv run test-predictor.py +``` + +Ожидаемый результат подобен этому: + +``` +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% +``` + +Большая часть тестера идентична агенту, но вот новые или измененные части. + +```python +CYCLES_FOR_TEST = 40 # Для бэктеста, сколько циклов мы тестируем + +# Получить много котировок +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 +) +``` + +Мы смотрим на `CYCLES_FOR_TEST` (здесь указано 40) дней назад. + +```python +# Создать прогнозы и проверить их на реальной истории + +total_error = Decimal(0) +changes = [] +``` + +Нас интересуют два типа ошибок. Первая, `total_error`, — это просто сумма ошибок, допущенных предсказателем. + +Чтобы понять вторую, `changes`, нам нужно вспомнить о цели агента. Его цель не предсказать соотношение WETH/USDC (цену ETH). Его цель — выдавать рекомендации о продаже и покупке. Если текущая цена составляет 2000 $, и он предсказывает 2010 $ на завтра, мы не возражаем, если фактический результат будет 2020 $, и мы заработаем дополнительные деньги. Но мы _возражаем_, если он предсказал 2010 $, и купил ETH на основе этой рекомендации, а цена упала до 1990 $. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +Мы можем рассматривать только те случаи, когда доступна полная история (значения, используемые для прогноза, и реальное значение для сравнения). Это означает, что самый новый случай должен был начаться `CYCLES_BACK` назад. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +Используйте [срезы](https://www.w3schools.com/python/ref_func_slice.asp), чтобы получить то же количество образцов, которое использует агент. Код между этим и следующим сегментом — это тот же код для получения прогноза, который есть в агенте. + +```python + 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 +``` + +Получите предсказанную цену, реальную цену и цену на момент прогноза. Нам нужна цена на момент прогноза, чтобы определить, была ли рекомендация купить или продать. + +```python + 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") +``` + +Вычислите ошибку и добавьте ее к общей сумме. + +```python + recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell' + price_increase = real_price - prediction_time_price + changes.append(price_increase if recomended_action == 'buy' else -price_increase) +``` + +Для `changes` мы хотим получить денежное влияние от покупки или продажи одного ETH. Поэтому сначала нам нужно определить рекомендацию, затем оценить, как изменилась фактическая цена, и принесла ли рекомендация деньги (положительное изменение) или стоила денег (отрицательное изменение). + +```python +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") +``` + +Сообщите о результатах. + +```python +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%}") +``` + +Используйте [`filter`](https://www.w3schools.com/python/ref_func_filter.asp), чтобы подсчитать количество прибыльных дней и количество убыточных дней. Результатом является объект-фильтр, который нам нужно преобразовать в список, чтобы получить его длину. + +### Отправка транзакций {#submit-txn} + +Теперь нам нужно фактически отправить транзакции. Однако я не хочу тратить реальные деньги на этом этапе, пока система не доказала свою эффективность. Вместо этого мы создадим локальный форк основной сети и будем «торговать» в этой сети. + +Вот шаги по созданию локального форка и включению торговли. + +1. Установите [Foundry](https://getfoundry.sh/introduction/installation) + +2. Запустите [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` слушает на URL по умолчанию для Foundry, http://localhost:8545, поэтому нам не нужно указывать URL для команды [`cast`](https://getfoundry.sh/cast/overview), которую мы используем для манипулирования блокчейном. + +3. При работе в `anvil` есть десять тестовых аккаунтов с ETH — установите переменные окружения для первого + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. Это контракты, которые нам нужно использовать. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) — это контракт Uniswap v3, который мы используем для фактической торговли. Мы могли бы торговать напрямую через пул, но так гораздо проще. + + Две нижние переменные — это пути Uniswap v3, необходимые для обмена между WETH и USDC. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. На каждом из тестовых аккаунтов есть 10 000 ETH. Используйте контракт WETH, чтобы обернуть 1000 ETH и получить 1000 WETH для торговли. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. Используйте `SwapRouter`, чтобы обменять 500 WETH на USDC. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + Вызов `approve` создает разрешение, которое позволяет `SwapRouter` тратить некоторые из наших токенов. Контракты не могут отслеживать события, поэтому, если мы переведем токены непосредственно на контракт `SwapRouter`, он не узнает, что ему заплатили. Вместо этого мы разрешаем контракту `SwapRouter` потратить определенную сумму, а затем `SwapRouter` делает это. Это делается через функцию, вызываемую `SwapRouter`, поэтому он знает, была ли она успешной. + +7. Убедитесь, что у вас достаточно обоих токенов. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +Теперь, когда у нас есть WETH и USDC, мы можем запустить агент. + +```sh +git checkout 05-trade +uv run agent.py +``` + +Вывод будет выглядеть примерно так: + +``` +(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 +``` + +Чтобы использовать его на самом деле, вам понадобятся несколько незначительных изменений. + +- В строке 14 измените `MAINNET_URL` на реальную точку доступа, например `https://eth.drpc.org` +- В строке 28 измените `PRIVATE_KEY` на ваш собственный приватный ключ +- Если вы не очень богаты и не можете покупать или продавать 1 ETH каждый день для непроверенного агента, вы можете изменить строку 29, чтобы уменьшить `WETH_TRADE_AMOUNT` + +#### Объяснение кода {#trading-code} + +Вот новый код. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +Те же переменные, что мы использовали на шаге 4. + +```python +WETH_TRADE_AMOUNT=1 +``` + +Сумма для торговли. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +Для фактической торговли нам нужна функция `approve`. Мы также хотим показать балансы до и после, поэтому нам также нужна `balanceOf`. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +В ABI `SwapRouter` нам нужен только `exactInput`. Есть связанная функция, `exactOutput`, которую мы могли бы использовать для покупки ровно одного WETH, но для простоты мы просто используем `exactInput` в обоих случаях. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +Определения Web3 для [`account`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) и контракта `SwapRouter`. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +Параметры транзакции. Нам нужна здесь функция, потому что [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) должен меняться каждый раз. + +```python +def approve_token(contract: Contract, amount: int): +``` + +Утвердите разрешение на токены для `SwapRouter`. + +```python + txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params()) + signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY) + tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction) +``` + +Вот как мы отправляем транзакцию в Web3. Сначала мы используем [объект `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) для создания транзакции. Затем мы используем [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) для подписи транзакции, используя `PRIVATE_KEY`. Наконец, мы используем [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) для отправки транзакции. + +```python + 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`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) ожидает, пока транзакция будет включена в блок. При необходимости возвращается квитанция. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +Это параметры при продаже WETH. + +```python +def make_buy_params(quote: Quote) -> dict: + return { + "path": USDC_TO_WETH, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals, + "amountOutMinimum": 0, + } +``` + +В отличие от `SELL_PARAMS`, параметры покупки могут меняться. Входная сумма — это стоимость 1 WETH, доступная в `quote`. + +```python +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.") +``` + +Функции `buy()` и `sell()` почти идентичны. Сначала мы утверждаем достаточное разрешение для `SwapRouter`, а затем вызываем его с правильным путем и суммой. + +```python +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)}") +``` + +Сообщите о балансах пользователя в обеих валютах. + +```python +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() +``` + +Этот агент в настоящее время работает только один раз. Однако вы можете изменить его для непрерывной работы либо запустив его из [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html), либо обернув строки 368-400 в цикл и используя [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep), чтобы дождаться времени для следующего цикла. + +## Возможные улучшения {#improvements} + +Это не полная производственная версия; это лишь пример для обучения основам. Вот несколько идей для улучшений. + +### Более умная торговля {#smart-trading} + +Есть два важных факта, которые агент игнорирует при принятии решения о действиях. + +- _Величина ожидаемого изменения_. Агент продает фиксированное количество `WETH`, если ожидается снижение цены, независимо от величины этого снижения. + Вероятно, было бы лучше игнорировать незначительные изменения и продавать в зависимости от того, на сколько мы ожидаем снижения цены. +- _Текущий портфель_. Если 10% вашего портфеля находятся в WETH, и вы думаете, что цена вырастет, вероятно, имеет смысл купить больше. Но если 90% вашего портфеля находятся в WETH, вы можете быть достаточно подвержены риску, и нет необходимости покупать больше. Обратное верно, если вы ожидаете снижения цены. + +### Что, если вы хотите сохранить свою торговую стратегию в секрете? {#secret} + +Поставщики ИИ могут видеть запросы, которые вы отправляете их LLM, что может раскрыть гениальную торговую систему, разработанную вами с помощью вашего агента. Торговая система, которую использует слишком много людей, бесполезна, потому что слишком много людей пытаются купить, когда вы хотите купить (и цена растет), и пытаются продать, когда вы хотите продать (и цена падает). + +Вы можете запустить LLM локально, например, используя [LM-Studio](https://lmstudio.ai/), чтобы избежать этой проблемы. + +### От бота с ИИ к агенту с ИИ {#bot-to-agent} + +Можно с полным основанием утверждать, что это [бот с ИИ, а не агент с ИИ](/ai-agents/#ai-agents-vs-ai-bots). Он реализует относительно простую стратегию, которая опирается на предопределенную информацию. Мы можем включить самосовершенствование, например, предоставив список пулов Uniswap v3 и их последние значения и спросив, какая комбинация имеет наилучшую прогностическую ценность. + +### Защита от проскальзывания {#slippage-protection} + +В настоящее время нет [защиты от проскальзывания](https://uniswapv3book.com/milestone_3/slippage-protection.html). Если текущая котировка составляет 2000 $, а ожидаемая цена — 2100 $, агент купит. Однако, если до того, как агент купит, стоимость поднимется до 2200 $, покупать больше не имеет смысла. + +Чтобы реализовать защиту от проскальзывания, укажите значение `amountOutMinimum` в строках 325 и 334 файла [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325). + +## Заключение {#conclusion} + +Надеюсь, теперь вы знаете достаточно, чтобы начать работать с агентами ИИ. Это не исчерпывающий обзор предмета; этому посвящены целые книги, но этого достаточно, чтобы вы могли начать. Удачи! + +[Больше моих работ смотрите здесь](https://cryptodocguy.pro/). diff --git a/public/content/translations/sw/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/sw/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..a476e5e116e --- /dev/null +++ b/public/content/translations/sw/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: Tengeneza wakala wako wa biashara wa AI kwenye Ethereum +description: Katika mafunzo haya utajifunza jinsi ya kutengeneza wakala rahisi wa biashara wa AI. Wakala huyu husoma taarifa kutoka kwa mnyororo wa bloku, anauliza LLM pendekezo kulingana na taarifa hiyo, anafanya biashara ambayo LLM inapendekeza, kisha anasubiri na kurudia. +author: Ori Pomerantz +tags: [ "AI", "biashara", "wakala", "python" ] +skill: intermediate +published: 2026-02-13 +lang: sw +sidebarDepth: 3 +--- + +Katika mafunzo haya utajifunza jinsi ya kujenga wakala rahisi wa biashara wa AI. Wakala huyu hufanya kazi kwa kutumia hatua hizi: + +1. Soma bei za sasa na za zamani za tokeni, pamoja na taarifa nyingine zinazoweza kuwa muhimu +2. Unda swali kwa taarifa hii, pamoja na taarifa ya usuli ili kueleza jinsi inavyoweza kuwa muhimu +3. Wasilisha swali na upokee bei iliyokadiriwa +4. Fanya biashara kulingana na pendekezo +5. Subiri na urudie + +Wakala huyu anaonyesha jinsi ya kusoma taarifa, kuitafsiri kuwa swali linalotoa jibu linaloweza kutumika, na kutumia jibu hilo. Hizi zote ni hatua zinazohitajika kwa wakala wa AI. Wakala huyu ametekelezwa katika Python kwa sababu ndiyo lugha ya kawaida inayotumika katika AI. + +## Kwa nini ufanye hivi? {#why-do-this} + +Wakala wa biashara wa kiotomatiki huruhusu wasanidi programu kuchagua na kutekeleza mkakati wa biashara. [Wakala wa AI](/ai-agents) huruhusu mikakati changamano na yenye nguvu zaidi ya biashara, inayoweza kutumia taarifa na kanuni ambazo msanidi programu hata hajafikiria kutumia. + +## Zana {#tools} + +Mafunzo haya yanatumia [Python](https://www.python.org/), [maktaba ya Web3](https://web3py.readthedocs.io/en/stable/), na [Uniswap v3](https://github.com/Uniswap/v3-periphery) kwa nukuu na biashara. + +### Kwa nini Python? {#python} + +Lugha inayotumika sana kwa AI ni [Python](https://www.python.org/), kwa hivyo tunaitumia hapa. Usijali kama hujui Python. Lugha iko wazi sana, na nitaeleza hasa inachofanya. + +[Maktaba ya Web3](https://web3py.readthedocs.io/en/stable/) ndiyo API ya Python ya Ethereum ya kawaida zaidi. Ni rahisi sana kutumia. + +### Kufanya biashara kwenye mnyororo wa bloku {#trading-on-blockchain} + +Kuna [mabadilishano mengi yaliyosambazwa (DEX)](/apps/categories/defi/) ambayo yanakuwezesha kufanya biashara ya tokeni kwenye Ethereum. Hata hivyo, huwa na viwango vya ubadilishaji vinavyofanana kutokana na [usuluhishi](/developers/docs/smart-contracts/composability/#better-user-experience). + +[Uniswap](https://app.uniswap.org/) ni DEX inayotumika sana ambayo tunaweza kutumia kwa nukuu (kuona thamani za tokeni linganishi) na biashara. + +### OpenAI {#openai} + +Kwa muundo mkuu wa lugha, nilichagua kuanza na [OpenAI](https://openai.com/). Ili kuendesha programu katika mafunzo haya utahitaji kulipia ufikiaji wa API. Malipo ya chini ya $5 ni zaidi ya ya kutosha. + +## Uendelezaji, hatua kwa hatua {#step-by-step} + +Ili kurahisisha uendelezaji, tunaendelea kwa hatua. Kila hatua ni tawi katika GitHub. + +### Kuanza {#getting-started} + +Kuna hatua za kuanza chini ya UNIX au Linux (pamoja na [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) + +1. Ikiwa huna tayari, pakua na usakinishe [Python](https://www.python.org/downloads/). + +2. Clone hazina ya GitHub. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. Sakinisha [`uv`](https://docs.astral.sh/uv/getting-started/installation/). Amri kwenye mfumo wako inaweza kuwa tofauti. + + ```sh + pipx install uv + ``` + +4. Pakua maktaba. + + ```sh + uv sync + ``` + +5. Washa mazingira ya mtandaoni. + + ```sh + source .venv/bin/activate + ``` + +6. Ili kuthibitisha Python na Web3 zinafanya kazi ipasavyo, endesha `python3` na uipe programu hii. Unaweza kuiingiza kwenye kidokezo cha `>>>`; hakuna haja ya kuunda faili. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### Kusoma kutoka kwa mnyororo wa bloku {#read-blockchain} + +Hatua inayofuata ni kusoma kutoka kwa mnyororo wa bloku. Ili kufanya hivyo, unahitaji kubadilisha hadi tawi la `02-read-quote` kisha utumie `uv` kuendesha programu. + +```sh +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 mali (kwa sasa daima ni `WETH/USDC`). + +Hapa kuna maelezo ya mstari kwa mstari. + +```python +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. Zinaelezwa hapa chini zinapotumika. + +```python +print = functools.partial(print, flush=True) +``` + +Inabadilisha `print` ya Python na toleo ambalo daima husafisha matokeo mara moja. Hii ni muhimu katika hati inayoendeshwa kwa muda mrefu kwa sababu hatutaki kusubiri masasisho ya hali au matokeo ya utatuzi. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +URL ya kufika kwenye mtandao mkuu. Unaweza kupata moja kutoka [Nodi kama huduma](/developers/docs/nodes-and-clients/nodes-as-a-service/) au kutumia moja ya zile zilizotangazwa katika [Chainlist](https://chainlist.org/chain/1). + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +Bloku ya mtandao mkuu wa Ethereum kwa kawaida hutokea kila sekunde kumi na mbili, kwa hivyo hizi ni idadi ya bloku tunazotarajia kutokea katika kipindi cha muda. Kumbuka kuwa hii si takwimu kamili. Wakati [mpendekezaji wa bloku](/developers/docs/consensus-mechanisms/pos/block-proposal/) hayuko hewani, bloku hiyo hurukwa, na muda wa bloku inayofuata ni sekunde 24. Ikiwa tungetaka kupata bloku kamili kwa muhuri wa muda, tungetumia [utafutaji wa binary](https://en.wikipedia.org/wiki/Binary_search). Hata hivyo, hii ni karibu vya kutosha kwa madhumuni yetu. Kutabiri siku zijazo si sayansi kamili. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +Ukubwa wa mzunguko. Tunapitia nukuu mara moja kwa kila mzunguko na kujaribu kukadiria thamani mwishoni mwa mzunguko unaofuata. + +```python +# Anwani ya pool tunayosoma +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +Thamani za nukuu zinachukuliwa kutoka kwa pool ya Uniswap 3 USDC/WETH katika anwani [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract). Anwani hii tayari iko katika fomu ya checksum, lakini ni bora kutumia [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) kufanya msimbo uweze kutumika tena. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +Hizi ni [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html) za mikataba miwili tunayohitaji kuwasiliana nayo. Ili kuweka msimbo mfupi, tunajumuisha tu kazi tunazohitaji kupiga simu. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +Anzisha maktaba ya [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) na uunganishe kwenye nodi ya Ethereum. + +```python +@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`](https://web3py.readthedocs.io/en/stable/web3.contract.html) inatumika kuunganisha na mkataba. Kumbuka `(frozen=True)`. Katika Python [booleans](https://en.wikipedia.org/wiki/Boolean_data_type) hufafanuliwa kama `True` au `False`, herufi kubwa. Darasa hili la data ni `frozen`, ikimaanisha sehemu zake haziwezi kurekebishwa. + +Kumbuka mpangilio. Tofauti na [lugha zinazotokana na C](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages), Python hutumia mpangilio kuashiria bloku. Mkalimani wa Python anajua kuwa ufafanuzi unaofuata si sehemu ya darasa hili la data kwa sababu hauanzi katika mpangilio sawa na sehemu za darasa la data. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +Aina ya [`Decimal`](https://docs.python.org/3/library/decimal.html) inatumika kwa kushughulikia sehemu za desimali kwa usahihi. + +```python + def get_price(self, block: int) -> Decimal: +``` + +Hii ndiyo njia ya kufafanua kazi katika Python. Ufafanuzi umewekwa ndani ili kuonyesha bado ni sehemu ya `PoolInfo`. + +Katika kazi ambayo ni sehemu ya darasa la data, kigezo cha kwanza daima ni `self`, mfano wa darasa la data uliopiga simu hapa. Hapa kuna kigezo kingine, nambari ya bloku. + +```python + assert block <= w3.eth.block_number, "Bloku iko katika siku zijazo" +``` + +Kama tungeweza kusoma siku zijazo, hatungehitaji AI kwa biashara. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +Sintaksia ya kupiga simu kazi kwenye EVM kutoka Web3 ni hii: `.kazi.``().call()`. Vigezo vinaweza kuwa vigezo vya kazi ya EVM (ikiwa vipo; hapa havipo) au [vigezo vilivyotajwa](https://en.wikipedia.org/wiki/Named_parameter) kwa kurekebisha tabia ya mnyororo wa bloku. Hapa tunatumia moja, `block_identifier`, kubainisha [nambari ya bloku](/developers/docs/apis/json-rpc/#default-block) tunayotaka kuendesha ndani. + +Matokeo ni [muundo huu, katika fomu ya safu](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). Thamani ya kwanza ni kazi ya kiwango cha ubadilishaji kati ya tokeni mbili. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +Ili kupunguza mahesabu ya onchain, Uniswap v3 haihifadhi kipengele halisi cha ubadilishaji bali mzizi wake wa mraba. Kwa sababu EVM haitumii hesabu ya nambari inayoelea au sehemu, badala ya thamani halisi, jibu ni bei296 + +```python + # (tokeni1 kwa kila tokeni0) + return 1/(raw_price * self.decimal_factor) +``` + +Bei ghafi tunayopata ni idadi ya `tokeni0` tunayopata kwa kila `tokeni1`. Katika pool yetu, `tokeni0` ni USDC (sarafu-imara yenye thamani sawa na dola ya Marekani) na `tokeni1` ni [WETH](https://opensea.io/learn/blockchain/what-is-weth). Thamani tunayoitaka kweli ni idadi ya dola kwa kila WETH, si kinyume chake. + +Kipengele cha desimali ni uwiano kati ya [vipengele vya desimali](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) kwa tokeni mbili. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +Darasa hili la data linawakilisha nukuu: bei ya mali maalum kwa wakati fulani. Kwa wakati huu, sehemu ya `asset` haina umuhimu kwa sababu tunatumia pool moja na kwa hivyo tuna mali moja. Hata hivyo, tutaongeza mali zaidi baadaye. + +```python +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 katika anwani hiyo. Ili kuunda [`Contract` mpya ya Web3](https://web3py.readthedocs.io/en/stable/web3.contract.html), tunatoa anwani na ABI kwa `w3.eth.contract`. + +```python +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 [pool maalum](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol). Sintaksia `f""` ni [kamba iliyoumbizwa](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +Pata kitu cha `Quote`. Thamani ya chaguo-msingi kwa `block_number` ni `None` (hakuna thamani). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +Ikiwa nambari ya bloku haikubainishwa, tumia `w3.eth.block_number`, ambayo ni nambari ya bloku ya hivi karibuni. Hii ni sintaksia ya [taarifa ya `if`](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement). + +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 bloku wakati kazi inafafanuliwa. Katika wakala anayeendeshwa kwa muda mrefu, hili lingekuwa tatizo. + +```python + 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`](https://docs.python.org/3/library/datetime.html) kuiweka katika umbizo linaloweza kusomeka na binadamu na miundo mikubwa ya lugha (LLMs). Tumia [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) kuzungusha thamani hadi sehemu mbili za desimali. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +Katika Python unafafanua [orodha](https://docs.python.org/3/library/stdtypes.html#typesseq-list) ambayo inaweza tu kuwa na aina maalum kwa kutumia `list[]`. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +Katika Python [kitanzi cha `for`](https://docs.python.org/3/tutorial/controlflow.html#for-statements) kwa kawaida hupitia orodha. Orodha ya nambari za bloku za kupata nukuu ndani yake hutoka kwenye [`range`](https://docs.python.org/3/library/stdtypes.html#range). + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +Kwa kila nambari ya bloku, pata kitu cha `Quote` na ukiongeze kwenye orodha ya `quotes`. Kisha rudisha orodha hiyo. + +```python +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 ya pool, pata nukuu kumi na mbili, na uzichapishe kwa [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint). + +### Kuunda kidokezo {#prompt} + +Kisha, tunahitaji kubadilisha orodha hii ya nukuu kuwa kidokezo kwa LLM na kupata thamani inayotarajiwa ya siku zijazo. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +Matokeo sasa yatakuwa kidokezo kwa LLM, sawa na: + +``` +Kutokana na nukuu hizi: +Mali: WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +Mali: WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +Ungetarajia thamani ya WETH/USDC iwe nini saa 2026-02-02T17:56? + +Toa jibu lako kama nambari moja iliyozungushwa hadi sehemu mbili za desimali, +bila maandishi mengine yoyote. +``` + +Kumbuka kuwa kuna nukuu za mali mbili hapa, `WETH/USDC` na `WBTC/WETH`. Kuongeza nukuu kutoka kwa mali nyingine kunaweza kuboresha usahihi wa utabiri. + +#### Jinsi kidokezo kinavyoonekana {#prompt-explanation} + +Kidokezo hiki kina sehemu tatu, ambazo ni za kawaida katika vidokezo vya LLM. + +1. Taarifa. LLM zina taarifa nyingi kutoka kwa mafunzo yao, lakini kwa kawaida hazina za hivi karibuni. Hii ndiyo sababu tunahitaji kupata nukuu za hivi karibuni hapa. Kuongeza taarifa kwenye kidokezo kunaitwa [kizazi kilichoongezwa kwa urejeshaji (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +2. Swali halisi. Hiki ndicho tunachotaka kujua. + +3. Maagizo ya uumbizaji wa matokeo. Kwa kawaida, LLM itatupa makadirio na maelezo ya jinsi ilivyoyafikia. Hii ni bora kwa wanadamu, lakini programu ya kompyuta inahitaji tu mstari wa chini. + +#### Maelezo ya msimbo {#prompt-code} + +Huu hapa msimbo mpya. + +```python +from datetime import datetime, timezone, timedelta +``` + +Tunahitaji kuipa LLM muda ambao tunataka makadirio yake. Ili kupata muda "dakika/masaa/siku n" katika siku zijazo, tunatumia [darasa la `timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta). + +```python +# Anwani za pools tunazosoma +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +Tuna pools mbili tunazohitaji kusoma. + +```python +@dataclass(frozen=True) +class PoolInfo: + . + . + . + reverse: bool = False + + def get_price(self, block: int) -> Decimal: + assert block <= w3.eth.block_number, "Bloku iko katika siku zijazo" + 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 pool ya WETH/USDC, tunataka kujua ni `tokeni0` (USDC) ngapi tunahitaji kununua `tokeni1` moja (WETH). Katika pool ya WETH/WBTC, tunataka kujua ni `tokeni1` (WETH) ngapi tunahitaji kununua `tokeni0` moja (WBTC, ambayo ni Bitcoin iliyofungwa). Tunahitaji kufuatilia ikiwa uwiano wa pool unahitaji kubadilishwa. + +```python +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 kama pool inahitaji kubadilishwa, tunapata hiyo kama ingizo kwa `read_pool`. Pia, alama ya mali inahitaji kuwekwa kwa usahihi. + +Sintaksia ` if else ` ni sawa na [kiendeshaji cha masharti cha ternary](https://en.wikipedia.org/wiki/Ternary_conditional_operator) cha Python, ambacho katika lugha inayotokana na C kingekuwa ` ? : `. + +```python +def format_quotes(quotes: list[Quote]) -> str: + result = f"Mali: {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 inaunda kamba inayoweka umbizo la orodha ya vitu vya `Quote`, ikidhani vyote vinatumika kwa mali moja. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +Katika Python [literali za kamba za mistari mingi](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) huandikwa kama `"""` .... `"""`. + +```python +Kutokana na nukuu hizi: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +Hapa, tunatumia mfumo wa [MapReduce](https://en.wikipedia.org/wiki/MapReduce) kutengeneza kamba kwa kila orodha ya nukuu na `format_quotes`, kisha tunazipunguza kuwa kamba moja kwa matumizi katika kidokezo. + +```python +Ungetarajia thamani ya {asset} iwe nini saa {expected_time}? + +Toa jibu lako kama nambari moja iliyozungushwa hadi sehemu mbili za desimali, +bila maandishi mengine yoyote. + """ +``` + +Sehemu iliyobaki ya kidokezo iko kama inavyotarajiwa. + +```python +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 +) +``` + +Pitia pools zote mbili na upate nukuu kutoka kwa zote mbili. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +Bainisha nukta ya wakati wa siku zijazo ambayo tunataka makadirio, na unda kidokezo. + +### Kuunganisha na LLM {#interface-llm} + +Kisha, tunatoa kidokezo kwa LLM halisi na kupokea thamani inayotarajiwa ya siku zijazo. Niliandika programu hii kwa kutumia OpenAI, kwa hivyo ikiwa unataka kutumia mtoa huduma tofauti, utahitaji kuirekebisha. + +1. Pata [akaunti ya OpenAI](https://auth.openai.com/create-account) + +2. [Weka fedha kwenye akaunti](https://platform.openai.com/settings/organization/billing/overview)—kiwango cha chini wakati wa kuandika ni $5 + +3. [Unda ufunguo wa API](https://platform.openai.com/settings/organization/api-keys) + +4. Katika mstari wa amri, safirisha ufunguo wa API ili programu yako iweze kuutumia + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. Kagua na endesha wakala + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +Huu hapa msimbo mpya. + +```python +from openai import OpenAI + +open_ai = OpenAI() # Wateja husoma kigeugeu cha mazingira cha OPENAI_API_KEY +``` + +Ingiza na uanzishe API ya OpenAI. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +Piga simu API ya OpenAI (`open_ai.chat.completions.create`) kuunda jibu. + +```python +expected_price = Decimal(response.choices[0].message.content.strip()) +current_price = wethusdc_quotes[-1].price + +print ("Bei ya sasa:", wethusdc_quotes[-1].price) +print(f"Mnamo {future_time}, bei inayotarajiwa: {expected_price} USD") + +if (expected_price > current_price): + print(f"Nunua, ninatarajia bei itapanda kwa {expected_price - current_price} USD") +else: + print(f"Uza, ninatarajia bei itashuka kwa {current_price - expected_price} USD") +``` + +Toa bei na toa pendekezo la kununua au kuuza. + +#### Kupima utabiri {#testing-the-predictions} + +Sasa kwa kuwa tunaweza kutengeneza utabiri, tunaweza pia kutumia data ya kihistoria kutathmini kama tunazalisha utabiri muhimu. + +```sh +uv run test-predictor.py +``` + +Matokeo yanayotarajiwa ni sawa na: + +``` +Utabiri wa 2026-01-05T19:50: ilitabiriwa 3138.93 USD, halisi 3218.92 USD, kosa 79.99 USD +Utabiri wa 2026-01-06T19:56: ilitabiriwa 3243.39 USD, halisi 3221.08 USD, kosa 22.31 USD +Utabiri wa 2026-01-07T20:02: ilitabiriwa 3223.24 USD, halisi 3146.89 USD, kosa 76.35 USD +Utabiri wa 2026-01-08T20:11: ilitabiriwa 3150.47 USD, halisi 3092.04 USD, kosa 58.43 USD +. +. +. +Utabiri wa 2026-01-31T22:33: ilitabiriwa 2637.73 USD, halisi 2417.77 USD, kosa 219.96 USD +Utabiri wa 2026-02-01T22:41: ilitabiriwa 2381.70 USD, halisi 2318.84 USD, kosa 62.86 USD +Utabiri wa 2026-02-02T22:49: ilitabiriwa 2234.91 USD, halisi 2349.28 USD, kosa 114.37 USD +Kosa la wastani la utabiri juu ya utabiri 29: 83.87103448275862068965517241 USD +Mabadiliko ya wastani kwa kila pendekezo: 4.787931034482758620689655172 USD +Tofauti ya kawaida ya mabadiliko: 104.42 USD +Siku za faida: 51.72% +Siku za hasara: 48.28% +``` + +Sehemu kubwa ya mpimaji ni sawa na wakala, lakini hapa kuna sehemu ambazo ni mpya au zilizobadilishwa. + +```python +CYCLES_FOR_TEST = 40 # Kwa jaribio la nyuma, ni mizunguko mingapi tunayopima + +# 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) zilizopita. + +```python +# Unda utabiri na uangalie dhidi ya historia halisi + +total_error = Decimal(0) +changes = [] +``` + +Kuna aina mbili za makosa tunayovutiwa nayo. Ya kwanza, `total_error`, ni jumla ya makosa ambayo mtabiri alifanya. + +Ili kuelewa ya pili, `changes`, tunahitaji kukumbuka kusudi la wakala. 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 tukanunua ETH kulingana na pendekezo hilo, na bei ikashuka hadi $1990. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +Tunaweza tu kuangalia kesi ambapo historia kamili (thamani zilizotumika kwa utabiri na thamani halisi ya kulinganisha nayo) inapatikana. Hii inamaanisha kesi mpya zaidi lazima iwe ile iliyoanza `CYCLES_BACK` iliyopita. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +Tumia [slices](https://www.w3schools.com/python/ref_func_slice.asp) kupata idadi sawa ya sampuli kama idadi ambayo wakala hutumia. Msimbo kati ya hapa na sehemu inayofuata ni msimbo uleule wa kupata utabiri tulio nao katika wakala. + +```python + 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 kubaini kama pendekezo lilikuwa la kununua au kuuza. + +```python + error = abs(predicted_price - real_price) + total_error += error + print (f"Utabiri wa {prediction_time}: ilitabiriwa {predicted_price} USD, halisi {real_price} USD, kosa {error} USD") +``` + +Tafuta kosa, na uliongeze kwenye jumla. + +```python + 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 kubaini pendekezo, kisha tutathmini jinsi bei halisi ilivyobadilika, na kama pendekezo lilileta pesa (mabadiliko chanya) au liligharimu pesa (mabadiliko hasi). + +```python +print (f"Kosa la wastani la utabiri juu ya utabiri {len(wethusdc_quotes)-CYCLES_BACK}: {total_error / Decimal(len(wethusdc_quotes)-CYCLES_BACK)} USD") + +length_changes = Decimal(len(changes)) +mean_change = sum(changes, Decimal(0)) / length_changes +print (f"Mabadiliko ya wastani kwa kila pendekezo: {mean_change} USD") +var = sum((x - mean_change) ** 2 for x in changes) / length_changes +print (f"Tofauti ya kawaida ya mabadiliko: {var.sqrt().quantize(Decimal("0.01"))} USD") +``` + +Ripoti matokeo. + +```python +print (f"Siku za faida: {len(list(filter(lambda x: x > 0, changes)))/length_changes:.2%}") +print (f"Siku za hasara: {len(list(filter(lambda x: x < 0, changes)))/length_changes:.2%}") +``` + +Tumia [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) kuhesabu idadi ya siku za faida na idadi ya siku za gharama. Matokeo ni kitu cha chujio, ambacho tunahitaji kukibadilisha kuwa orodha ili kupata urefu. + +### Kuwasilisha miamala {#submit-txn} + +Sasa tunahitaji kuwasilisha miamala. Hata hivyo, sitaki kutumia pesa halisi kwa wakati huu, kabla mfumo haujathibitishwa. Badala yake, tutaunda uma wa ndani wa mtandao mkuu, na "kufanya biashara" kwenye mtandao huo. + +Hizi ni hatua za kuunda uma wa ndani na kuwezesha biashara. + +1. Sakinisha [Foundry](https://getfoundry.sh/introduction/installation) + +2. Anzisha [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` inasikiliza kwenye URL ya chaguo-msingi ya Foundry, http://localhost:8545, kwa hivyo hatuhitaji kubainisha URL kwa [amri ya `cast`](https://getfoundry.sh/cast/overview) tunayotumia kudhibiti mnyororo wa bloku. + +3. Wakati wa kuendesha `anvil`, kuna akaunti kumi za majaribio ambazo zina ETH—weka vigeugeu vya mazingira kwa ya kwanza + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. Hii ni mikataba tunayohitaji kutumia. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) ni mkataba wa Uniswap v3 tunaotumia kufanya biashara. Tungeweza kufanya biashara moja kwa moja kupitia pool, lakini hii ni rahisi zaidi. + + Vigeugeu viwili vya chini ni njia za Uniswap v3 zinazohitajika kubadilisha kati ya WETH na USDC. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. Kila moja ya akaunti za majaribio ina ETH 10,000. Tumia mkataba wa WETH kufunga ETH 1000 ili kupata WETH 1000 kwa biashara. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. Tumia `SwapRouter` kubadilisha WETH 500 kwa USDC. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + Simu ya `approve` inaunda ruhusa inayoruhusu `SwapRouter` kutumia baadhi ya tokeni zetu. Mikataba haiwezi kufuatilia matukio, kwa hivyo ikiwa tutahamisha tokeni moja kwa moja kwenye mkataba wa `SwapRouter`, haitajua imelipwa. Badala yake, tunaruhusu mkataba wa `SwapRouter` kutumia kiasi fulani, na kisha `SwapRouter` hufanya hivyo. Hii inafanywa kupitia kazi inayoitwa na `SwapRouter`, kwa hivyo inajua kama imefanikiwa. + +7. Thibitisha una tokeni za kutosha za aina zote mbili. + + ```sh + 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 kweli kuendesha wakala. + +```sh +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 +Bei ya sasa: 1843.16 +Mnamo 2026-02-06T23:07, bei inayotarajiwa: 1724.41 USD +Salio za akaunti kabla ya biashara: +Salio la USDC: 927301.578272 +Salio la WETH: 500 +Uza, ninatarajia bei itashuka kwa 118.75 USD +Muamala wa idhini umetumwa: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +Muamala wa idhini umefanikiwa. +Muamala wa uuzaji umetumwa: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +Muamala wa uuzaji umefanikiwa. +Salio za akaunti baada ya biashara: +Salio la USDC: 929143.797116 +Salio la WETH: 499 +``` + +Ili kuitumia kweli, unahitaji mabadiliko madogo. + +- Katika mstari wa 14, badilisha `MAINNET_URL` kuwa sehemu halisi ya ufikiaji, kama vile `https://eth.drpc.org` +- Katika mstari wa 28, badilisha `PRIVATE_KEY` kuwa ufunguo wako binafsi +- Isipokuwa wewe ni tajiri sana na unaweza kununua au kuuza ETH 1 kila siku kwa wakala ambaye hajathibitishwa, unaweza kutaka kubadilisha 29 ili kupunguza `WETH_TRADE_AMOUNT` + +#### Maelezo ya msimbo {#trading-code} + +Huu hapa msimbo mpya. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +Vigeugeu vilevile tulivyotumia katika hatua ya 4. + +```python +WETH_TRADE_AMOUNT=1 +``` + +Kiasi cha kufanya biashara. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +Ili kufanya biashara kweli, tunahitaji kazi ya `approve`. Pia tunataka kuonyesha salio kabla na baada, kwa hivyo tunahitaji pia `balanceOf`. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +Katika ABI ya `SwapRouter` tunahitaji tu `exactInput`. Kuna kazi inayohusiana, `exactOutput`, tunaweza kuitumia kununua WETH moja hasa, lakini kwa urahisi tunatumia tu `exactInput` katika visa vyote viwili. + +```python +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 [`akaunti`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) na mkataba wa `SwapRouter`. + +```python +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 [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) lazima ibadilike kila wakati. + +```python +def approve_token(contract: Contract, amount: int): +``` + +Idhinisha ruhusa ya tokeni kwa `SwapRouter`. + +```python + 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`](https://web3py.readthedocs.io/en/stable/web3.contract.html) kujenga muamala. Kisha tunatumia [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) kusaini muamala, kwa kutumia `PRIVATE_KEY`. Mwisho, tunatumia [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) kutuma muamala. + +```python + print(f"Muamala wa idhini umetumwa: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Muamala wa idhini umefanikiwa.") +``` + +[`w3.eth.wait_for_transaction_receipt`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) husubiri hadi muamala utakapofanikiwa. Inarudisha risiti ikihitajika. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +Hizi ni vigezo wakati wa kuuza WETH. + +```python +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 ununuzi vinaweza kubadilika. Kiasi cha ingizo ni gharama ya WETH 1, kama inavyopatikana katika `quote`. + +```python +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"Muamala wa ununuzi umetumwa: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Muamala wa ununuzi umefanikiwa.") + + +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"Muamala wa uuzaji umetumwa: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Muamala wa uuzaji umefanikiwa.") +``` + +Kazi za `buy()` na `sell()` karibu zinafanana. Kwanza tunakubali ruhusa ya kutosha kwa `SwapRouter`, na kisha tunaipiga simu kwa njia na kiasi sahihi. + +```python +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} Salio: {Decimal(token0_balance) / Decimal(10 ** wethusdc_pool.token0.decimals)}") + print(f"{wethusdc_pool.token1.symbol} Salio: {Decimal(token1_balance) / Decimal(10 ** wethusdc_pool.token1.decimals)}") +``` + +Ripoti salio la mtumiaji katika sarafu zote mbili. + +```python +print("Salio za akaunti kabla ya biashara:") +balances() + +if (expected_price > current_price): + print(f"Nunua, ninatarajia bei itapanda kwa {expected_price - current_price} USD") + buy(wethusdc_quotes[-1]) +else: + print(f"Uza, ninatarajia bei itashuka kwa {current_price - expected_price} USD") + sell() + +print("Salio za akaunti baada ya biashara:") +balances() +``` + +Wakala huyu kwa sasa anafanya kazi mara moja tu. Hata hivyo, unaweza kuibadilisha ifanye kazi mfululizo ama kwa kuiendesha kutoka [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) au kwa kufunga mistari 368-400 katika kitanzi na kutumia [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) kusubiri hadi iwe wakati wa mzunguko unaofuata. + +## Maboresho yanayowezekana {#improvements} + +Hili si toleo kamili la uzalishaji; ni mfano tu wa kufundisha misingi. Hapa kuna maoni kadhaa ya maboresho. + +### Biashara yenye akili zaidi {#smart-trading} + +Kuna ukweli mbili muhimu ambazo wakala anapuuza wakati wa kuamua nini cha kufanya. + +- _Ukubwa wa mabadiliko yanayotarajiwa_. Wakala anauza kiasi kisichobadilika cha `WETH` ikiwa bei inatarajiwa kushuka, bila kujali ukubwa wa kushuka. + Inaweza kusemwa, ingekuwa bora kupuuza mabadiliko madogo na kuuza kulingana na kiasi gani tunatarajia bei itashuka. +- _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 umejifunua vya kutosha, na hakuna haja ya kununua zaidi. Kinyume chake ni kweli ikiwa unatarajia bei itashuka. + +### Vipi ikiwa unataka kuweka mkakati wako wa biashara kuwa siri? {#secret} + +Wachuuzi wa AI wanaweza kuona maswali unayotuma kwa LLM zao, ambayo inaweza kufichua mfumo wa biashara wa kitaalam uliouendeleza na wakala wako. Mfumo wa biashara ambao watu wengi sana hutumia hauna thamani kwa sababu watu wengi sana hujaribu kununua wakati unapotaka kununua (na bei inapanda) na kujaribu kuuza wakati unapotaka kuuza (na bei inashuka). + +Unaweza kuendesha LLM ndani ya nchi, kwa mfano, kwa kutumia [LM-Studio](https://lmstudio.ai/), ili kuepuka tatizo hili. + +### Kutoka kwa boti ya AI hadi wakala wa AI {#bot-to-agent} + +Unaweza kutoa hoja nzuri kwamba hii ni [boti ya AI, si wakala wa AI](/ai-agents/#ai-agents-vs-ai-bots). Inatekeleza mkakati rahisi ambao unategemea taarifa zilizofafanuliwa awali. Tunaweza kuwezesha uboreshaji wa kibinafsi, kwa mfano, kwa kutoa orodha ya pools za Uniswap v3 na thamani zao za hivi karibuni na kuuliza ni mchanganyiko gani una thamani bora ya utabiri. + +### Ulinzi wa slippage {#slippage-protection} + +Kwa sasa hakuna [ulinzi wa slippage](https://uniswapv3book.com/milestone_3/slippage-protection.html). Ikiwa nukuu ya sasa ni $2000, na bei inayotarajiwa ni $2100, wakala atanunua. Hata hivyo, ikiwa kabla wakala hajanunua gharama itapanda hadi $2200, haina maana kununua tena. + +Ili kutekeleza ulinzi wa slippage, bainisha thamani ya `amountOutMinimum` katika mistari ya 325 na 334 ya [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325). + +## Hitimisho {#conclusion} + +Tunatumai, sasa unajua vya kutosha kuanza na wakala wa AI. Huu si muhtasari kamili wa somo; kuna vitabu vizima vilivyojitolea kwa hilo, lakini hii inatosha kukuanzisha. Kila la kheri! + +[Tazama hapa kwa kazi zangu zaidi](https://cryptodocguy.pro/). diff --git a/public/content/translations/ta/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/ta/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..3f3848c61db --- /dev/null +++ b/public/content/translations/ta/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "எத்தேரியத்தில் உங்கள் சொந்த AI வர்த்தக முகவரை உருவாக்குங்கள்" +description: "இந்த டுடோரியலில் ஒரு எளிய AI வர்த்தக முகவரை எவ்வாறு உருவாக்குவது என்பதை நீங்கள் கற்றுக்கொள்வீர்கள். இந்த முகவர் பிளாக்செயினிலிருந்து தகவல்களைப் படிக்கிறது, அந்தத் தகவலின் அடிப்படையில் ஒரு பரிந்துரைக்காக ஒரு LLM-ஐக் கேட்கிறது, LLM பரிந்துரைக்கும் வர்த்தகத்தைச் செய்கிறது, பின்னர் காத்திருந்து மீண்டும் செய்கிறது." +author: Ori Pomerantz +tags: [ "AI", "வர்த்தகம்", "ஏஜென்ட்", "python" ] +skill: intermediate +published: 2026-02-13 +lang: ta +sidebarDepth: 3 +--- + +இந்த டுடோரியலில் ஒரு எளிய AI வர்த்தக முகவரை எவ்வாறு உருவாக்குவது என்பதை நீங்கள் கற்றுக்கொள்வீர்கள். இந்த முகவர் இந்தப் படிகளைப் பயன்படுத்தி வேலை செய்கிறது: + +1. ஒரு டோக்கனின் தற்போதைய மற்றும் கடந்த கால விலைகளையும், மற்ற சாத்தியமான தொடர்புடைய தகவல்களையும் படிக்கவும் +2. இந்தத் தகவலுடன், அது எவ்வாறு தொடர்புடையதாக இருக்கலாம் என்பதை விளக்குவதற்கான பின்னணித் தகவலுடன் ஒரு வினவலை உருவாக்கவும் +3. வினவலைச் சமர்ப்பித்து, கணிக்கப்பட்ட விலையைத் திரும்பப் பெறவும் +4. பரிந்துரையின் அடிப்படையில் வர்த்தகம் செய்யவும் +5. காத்திருந்து மீண்டும் செய்யவும் + +இந்த முகவர் தகவலை எவ்வாறு படிப்பது, பயன்படுத்தக்கூடிய பதிலைத் தரும் வினவலாக மாற்றுவது, மற்றும் அந்தப் பதிலை எவ்வாறு பயன்படுத்துவது என்பதை நிரூபிக்கிறது. இவை அனைத்தும் ஒரு AI முகவருக்குத் தேவையான படிகள். இந்த முகவர் Python-இல் செயல்படுத்தப்படுகிறது, ஏனெனில் இது AI-இல் பயன்படுத்தப்படும் மிகவும் பொதுவான மொழியாகும். + +## இதை ஏன் செய்ய வேண்டும்? {#why-do-this} + +தானியங்கு வர்த்தக முகவர்கள், உருவாக்குநர்கள் (டெவலப்பர்கள்) ஒரு வர்த்தக உத்தியைத் தேர்ந்தெடுத்து செயல்படுத்த அனுமதிக்கின்றன. [AI முகவர்கள்](/ai-agents) மேலும் சிக்கலான மற்றும் மாறும் வர்த்தக உத்திகளை அனுமதிக்கின்றன, உருவாக்குநர்கள் (டெவலப்பர்கள்) பயன்படுத்தக் கூட கருதாத தகவல் மற்றும் அல்காரிதம்களை இது சாத்தியமாகப் பயன்படுத்துகிறது. + +## கருவிகள் {#tools} + +இந்த டுடோரியல் மேற்கோள்கள் மற்றும் வர்த்தகத்திற்காக [Python](https://www.python.org/), [Web3 நூலகம்](https://web3py.readthedocs.io/en/stable/) மற்றும் [Uniswap v3](https://github.com/Uniswap/v3-periphery) ஆகியவற்றைப் பயன்படுத்துகிறது. + +### ஏன் Python? {#python} + +AI-க்கு மிகவும் பரவலாகப் பயன்படுத்தப்படும் மொழி [Python](https://www.python.org/) என்பதால், நாங்கள் அதை இங்கே பயன்படுத்துகிறோம். உங்களுக்கு Python தெரியாவிட்டால் கவலைப்பட வேண்டாம். மொழி மிகவும் தெளிவாக உள்ளது, அது என்ன செய்கிறது என்பதை நான் சரியாக விளக்குகிறேன். + +[Web3 நூலகம்](https://web3py.readthedocs.io/en/stable/) என்பது மிகவும் பொதுவான Python எத்தேரியம் பயன்பாட்டு நிரலாக்க இடைமுகம் (API) ஆகும். இதைப் பயன்படுத்துவது மிகவும் எளிதானது. + +### பிளாக்செயினில் வர்த்தகம் செய்தல் {#trading-on-blockchain} + +எத்தேரியத்தில் டோக்கன்களை வர்த்தகம் செய்ய உங்களை அனுமதிக்கும் [பல பரவலாக்கப்பட்ட பரிமாற்றங்கள் (DEX)](/apps/categories/defi/) உள்ளன. இருப்பினும், [ஆர்பிட்ரேஜ்](/developers/docs/smart-contracts/composability/#better-user-experience) காரணமாக அவை ஒரே மாதிரியான மாற்று விகிதங்களைக் கொண்டிருக்கின்றன. + +[Uniswap](https://app.uniswap.org/) என்பது பரவலாகப் பயன்படுத்தப்படும் ஒரு DEX ஆகும், அதை நாம் மேற்கோள்கள் (டோக்கன் சார்பு மதிப்புகளைப் பார்க்க) மற்றும் வர்த்தகங்கள் ஆகிய இரண்டிற்கும் பயன்படுத்தலாம். + +### OpenAI {#openai} + +ஒரு பெரிய மொழி மாதிரிக்கு, நான் [OpenAI](https://openai.com/)-உடன் தொடங்கத் தேர்ந்தெடுத்தேன். இந்த டுடோரியலில் உள்ள பயன்பாட்டை இயக்க நீங்கள் பயன்பாட்டு நிரலாக்க இடைமுக (API) அணுகலுக்கு பணம் செலுத்த வேண்டும். குறைந்தபட்ச கட்டணமான $5 போதுமானதை விட அதிகமாக உள்ளது. + +## படிப்படியான உருவாக்கம் {#step-by-step} + +உருவாக்கத்தை எளிதாக்க, நாம் நிலைகளில் தொடர்கிறோம். ஒவ்வொரு படியும் GitHub-இல் ஒரு கிளை. + +### தொடங்குதல் {#getting-started} + +UNIX அல்லது Linux ([WSL](https://learn.microsoft.com/en-us/windows/wsl/install) உட்பட) கீழ் தொடங்குவதற்கான படிகள் உள்ளன + +1. உங்களிடம் ஏற்கனவே இல்லை என்றால், [Python](https://www.python.org/downloads/)-ஐ பதிவிறக்கம் செய்து நிறுவவும். + +2. GitHub களஞ்சியத்தை நகலெடுக்கவும். + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. [`uv`](https://docs.astral.sh/uv/getting-started/installation/)-ஐ நிறுவவும். உங்கள் கணினியில் உள்ள கட்டளை வித்தியாசமாக இருக்கலாம். + + ```sh + pipx install uv + ``` + +4. நூலகங்களைப் பதிவிறக்கவும். + + ```sh + uv sync + ``` + +5. மெய்நிகர் சூழலைச் செயல்படுத்தவும். + + ```sh + source .venv/bin/activate + ``` + +6. Python மற்றும் Web3 சரியாக வேலை செய்கின்றனவா என்பதைச் சரிபார்க்க, `python3`-ஐ இயக்கி, இந்த நிரலை அதற்கு வழங்கவும். நீங்கள் அதை `>>>` ப்ராம்ட்டில் உள்ளிடலாம்; ஒரு கோப்பை உருவாக்க வேண்டிய அவசியமில்லை. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### பிளாக்செயினிலிருந்து படித்தல் {#read-blockchain} + +அடுத்த படி பிளாக்செயினிலிருந்து படிப்பதாகும். அதைச் செய்ய, நீங்கள் `02-read-quote` கிளைக்கு மாற வேண்டும், பின்னர் நிரலை இயக்க `uv`-ஐப் பயன்படுத்தவும். + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +நீங்கள் `Quote` பொருட்களின் பட்டியலைப் பெறுவீர்கள், ஒவ்வொன்றும் ஒரு நேரமுத்திரை, ஒரு விலை, மற்றும் சொத்து (தற்போது எப்போதும் `WETH/USDC`) ஆகியவற்றுடன். + +இதோ ஒரு வரிக்கு வரி விளக்கம். + +```python +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 +``` + +நமக்குத் தேவையான நூலகங்களை இறக்குமதி செய்யவும். அவை பயன்படுத்தப்படும்போது கீழே விளக்கப்பட்டுள்ளன. + +```python +print = functools.partial(print, flush=True) +``` + +Python-இன் `print`-ஐ, வெளியீட்டை உடனடியாக எப்போதும் ஃப்ளஷ் செய்யும் ஒரு பதிப்புடன் மாற்றுகிறது. இது நீண்ட நேரம் இயங்கும் ஒரு ஸ்கிரிப்டில் பயனுள்ளதாக இருக்கும், ஏனெனில் நாம் நிலை புதுப்பிப்புகள் அல்லது பிழைதிருத்த வெளியீட்டிற்காகக் காத்திருக்க விரும்பவில்லை. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +மெயின்நெட்டிற்குச் செல்ல ஒரு URL. நீங்கள் [சேவையாக முனை](/developers/docs/nodes-and-clients/nodes-as-a-service/) என்பதிலிருந்து ஒன்றைப் பெறலாம் அல்லது [Chainlist](https://chainlist.org/chain/1)-இல் விளம்பரப்படுத்தப்பட்டவற்றில் ஒன்றைப் பயன்படுத்தலாம். + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +ஒரு எத்தேரியம் மெயின்நெட் பிளாக் பொதுவாக ஒவ்வொரு பன்னிரண்டு வினாடிகளுக்கும் நடக்கும், எனவே இவை ஒரு காலப்பகுதியில் நடக்கும் என்று நாம் எதிர்பார்க்கும் பிளாக்குகளின் எண்ணிக்கையாகும். இது ஒரு துல்லியமான எண்ணிக்கை அல்ல என்பதை நினைவில் கொள்ளவும். [பிளாக் ப்ரோபோஸர்](/developers/docs/consensus-mechanisms/pos/block-proposal/) செயலிழந்தால், அந்த பிளாக் தவிர்க்கப்பட்டு, அடுத்த பிளாக்கிற்கான நேரம் 24 வினாடிகள் ஆகும். ஒரு நேரமுத்திரைக்கு சரியான பிளாக்கைப் பெற நாம் விரும்பினால், நாம் [பைனரி தேடலை](https://en.wikipedia.org/wiki/Binary_search)ப் பயன்படுத்துவோம். இருப்பினும், இது எங்கள் நோக்கங்களுக்குப் போதுமான அளவு நெருக்கமாக உள்ளது. எதிர்காலத்தைக் கணிப்பது ஒரு துல்லியமான அறிவியல் அல்ல. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +சுழற்சியின் அளவு. நாம் ஒவ்வொரு சுழற்சிக்கும் ஒரு முறை மேற்கோள்களை மதிப்பாய்வு செய்து, அடுத்த சுழற்சியின் முடிவில் மதிப்பை மதிப்பிட முயற்சிக்கிறோம். + +```python +# நாம் படிக்கும் பூலின் முகவரி +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +மேற்கோள் மதிப்புகள் Uniswap 3 USDC/WETH பூலிலிருந்து [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract) என்ற முகவரியில் எடுக்கப்படுகின்றன. இந்த முகவரி ஏற்கனவே செக்சம் வடிவத்தில் உள்ளது, ஆனால் குறியீட்டை மீண்டும் பயன்படுத்தக்கூடியதாக மாற்ற [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address)-ஐப் பயன்படுத்துவது நல்லது. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +நாம் தொடர்பு கொள்ள வேண்டிய இரண்டு ஒப்பந்தங்களுக்கான [ABIs](https://docs.soliditylang.org/en/latest/abi-spec.html) இவை. குறியீட்டைச் சுருக்கமாக வைத்திருக்க, நாம் அழைக்க வேண்டிய செயல்பாடுகளை மட்டுமே சேர்க்கிறோம். + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +[`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) நூலகத்தைத் தொடங்கி, ஒரு எத்தேரியம் முனையுடன் இணைக்கவும். + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +இது Python-இல் ஒரு டேட்டா கிளாஸை உருவாக்குவதற்கான ஒரு வழி. [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) தரவு வகை ஒப்பந்தத்துடன் இணைக்கப் பயன்படுகிறது. ` (frozen=True)`-ஐக் கவனிக்கவும். Python-இல் [பூலியன்கள்](https://en.wikipedia.org/wiki/Boolean_data_type) `True` அல்லது `False` என்று பெரிய எழுத்தில் வரையறுக்கப்பட்டுள்ளன. இந்த டேட்டா கிளாஸ் `frozen` ஆகும், அதாவது புலங்களை மாற்ற முடியாது. + +உள்தள்ளலைக் கவனிக்கவும். [சி-பெறப்பட்ட மொழிகளுக்கு](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages) மாறாக, Python பிளாக்குகளைக் குறிக்க உள்தள்ளலைப் பயன்படுத்துகிறது. பின்வரும் வரையறை இந்த டேட்டா கிளாஸின் ஒரு பகுதியாக இல்லை என்பதை Python இண்டர்பிரெட்டருக்குத் தெரியும், ஏனெனில் அது டேட்டா கிளாஸ் புலங்களின் அதே உள்தள்ளலில் தொடங்கவில்லை. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +தசம பின்னங்களைத் துல்லியமாகக் கையாள [`Decimal`](https://docs.python.org/3/library/decimal.html) வகை பயன்படுத்தப்படுகிறது. + +```python + def get_price(self, block: int) -> Decimal: +``` + +இது Python-இல் ஒரு செயல்பாட்டை வரையறுக்கும் வழி. வரையறை, அது இன்னும் `PoolInfo`-இன் ஒரு பகுதி என்பதைக் காட்ட உள்தள்ளப்பட்டுள்ளது. + +ஒரு டேட்டா கிளாஸின் பகுதியாக இருக்கும் ஒரு செயல்பாட்டில் முதல் அளவுரு எப்போதும் `self` ஆகும், இது இங்கு அழைத்த டேட்டா கிளாஸ் நிகழ்வு. இங்கே மற்றொரு அளவுரு, பிளாக் எண் உள்ளது. + +```python + assert block <= w3.eth.block_number, "பிளாக் எதிர்காலத்தில் உள்ளது" +``` + +நாம் எதிர்காலத்தைப் படிக்க முடிந்தால், வர்த்தகத்திற்கு நமக்கு AI தேவையில்லை. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +Web3-இலிருந்து EVM-இல் ஒரு செயல்பாட்டை அழைப்பதற்கான தொடரியல் இதுவாகும்: `.functions."().call()`. அளவுருக்கள் EVM செயல்பாட்டின் அளவுருக்களாக (ஏதேனும் இருந்தால்; இங்கே இல்லை) அல்லது பிளாக்செயின் நடத்தையை மாற்றுவதற்கான [பெயரிடப்பட்ட அளவுருக்களாக](https://en.wikipedia.org/wiki/Named_parameter) இருக்கலாம். நாம் இயக்க விரும்பும் [பிளாக் எண்ணைக்](/developers/docs/apis/json-rpc/#default-block) குறிப்பிட, இங்கே நாம் `block_identifier` ஐப் பயன்படுத்துகிறோம். + +இதன் விளைவு [இந்த struct, அணி வடிவத்தில்](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72) ஆகும். முதல் மதிப்பு என்பது இரண்டு டோக்கன்களுக்கு இடையிலான மாற்று விகிதத்தின் ஒரு செயல்பாடாகும். + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +ஆன்செயின் கணக்கீடுகளைக் குறைக்க, Uniswap v3 உண்மையான மாற்று காரணியைச் சேமிக்காமல் அதன் வர்க்க மூலத்தைச் சேமிக்கிறது. EVM மிதவைப் புள்ளி கணிதம் அல்லது பின்னங்களை ஆதரிக்காததால், உண்மையான மதிப்புக்கு பதிலாக, பதில் price296 ஆகும். + +```python + # (டோக்கன்1 க்கு டோக்கன்0) + return 1/(raw_price * self.decimal_factor) +``` + +நாம் பெறும் மூல விலை என்பது ஒவ்வொரு `token1`-க்கும் நாம் பெறும் `token0`-இன் எண்ணிக்கையாகும். நமது பூலில் `token0` என்பது USDC (அமெரிக்க டாலரின் அதே மதிப்பைக் கொண்ட ஒரு ஸ்டேபிள்காயின்) மற்றும் `token1` என்பது [WETH](https://opensea.io/learn/blockchain/what-is-weth) ஆகும். நாம் உண்மையில் விரும்பும் மதிப்பு ஒரு WETH-க்கான டாலர்களின் எண்ணிக்கை, அதன் நேர்மாறு அல்ல. + +தசம காரணி என்பது இரண்டு டோக்கன்களுக்கான [தசம காரணிகளுக்கு](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) இடையிலான விகிதமாகும். + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +இந்த டேட்டா கிளாஸ் ஒரு மேற்கோளைக் குறிக்கிறது: ஒரு குறிப்பிட்ட நேரத்தில் ஒரு குறிப்பிட்ட சொத்தின் விலை. இந்த நேரத்தில், `asset` புலம் பொருத்தமற்றது, ஏனெனில் நாம் ஒரே ஒரு பூலைப் பயன்படுத்துகிறோம், எனவே ஒரே ஒரு சொத்து உள்ளது. இருப்பினும், நாம் பின்னர் மேலும் சொத்துக்களைச் சேர்ப்போம். + +```python +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 + ) +``` + +இந்தச் செயல்பாடு ஒரு முகவரியை எடுத்து, அந்த முகவரியில் உள்ள டோக்கன் ஒப்பந்தம் பற்றிய தகவலைத் தருகிறது. ஒரு புதிய [Web3 `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) ஐ உருவாக்க, நாம் முகவரி மற்றும் பயன்பாடு பைனரி இடைமுகம் (ABI)-ஐ `w3.eth.contract`-க்கு வழங்குகிறோம். + +```python +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) + ) +``` + +இந்தச் செயல்பாடு [ஒரு குறிப்பிட்ட பூல்](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol) பற்றி நமக்குத் தேவையான அனைத்தையும் தருகிறது. `f""` தொடரியல் ஒரு [வடிவமைக்கப்பட்ட சரம்](https://docs.python.org/3/reference/lexical_analysis.html#f-strings) ஆகும். + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +ஒரு `Quote` பொருளைப் பெறவும். `block_number`-க்கான இயல்புநிலை மதிப்பு `None` (மதிப்பு இல்லை) ஆகும். + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +ஒரு பிளாக் எண் குறிப்பிடப்படவில்லை என்றால், `w3.eth.block_number`-ஐப் பயன்படுத்தவும், இது சமீபத்திய பிளாக் எண் ஆகும். இது [ஒரு `if` கூற்றிற்கான](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement) தொடரியல் ஆகும். + +இயல்புநிலையை `w3.eth.block_number` என அமைப்பது சிறப்பாக இருந்திருக்கும் என்று தோன்றலாம், ஆனால் அது நன்றாக வேலை செய்யாது, ஏனெனில் அது செயல்பாடு வரையறுக்கப்படும் நேரத்தில் பிளாக் எண்ணாக இருக்கும். நீண்டகாலமாக இயங்கும் ஒரு முகவரில் இது ஒரு பிரச்சனையாக இருக்கும். + +```python + 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 + ) +``` + +மனிதர்கள் மற்றும் பெரிய மொழி மாதிரிகள் (LLMs) படிக்கக்கூடிய வடிவத்திற்கு அதை வடிவமைக்க [the `datetime` library](https://docs.python.org/3/library/datetime.html) ஐப் பயன்படுத்தவும். மதிப்பை இரண்டு தசம இடங்களுக்கு округляಲು [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) ஐப் பயன்படுத்தவும். + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +Python இல் நீங்கள் `list[]` ஐப் பயன்படுத்தி ஒரு குறிப்பிட்ட வகையை மட்டுமே கொண்டிருக்கக்கூடிய ஒரு [பட்டியலை](https://docs.python.org/3/library/stdtypes.html#typesseq-list) வரையறுக்கிறீர்கள். + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +Python இல் ஒரு [`for` loop](https://docs.python.org/3/tutorial/controlflow.html#for-statements) பொதுவாக ஒரு பட்டியலை மீண்டும் செய்கிறது. மேற்கோள்களைக் கண்டறிய வேண்டிய பிளாக் எண்களின் பட்டியல் [`range`](https://docs.python.org/3/library/stdtypes.html#range)-இலிருந்து வருகிறது. + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +ஒவ்வொரு பிளாக் எண்ணுக்கும், ஒரு `Quote` பொருளைப் பெற்று அதை `quotes` பட்டியலில் சேர்க்கவும். பிறகு அந்தப் பட்டியலைத் திருப்பி அனுப்பவும். + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +இது ஸ்கிரிப்டின் முக்கிய குறியீடு. பூல் தகவலைப் படித்து, பன்னிரண்டு மேற்கோள்களைப் பெற்று, அவற்றை [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) செய்யவும். + +### ஒரு ப்ராம்ப்டை உருவாக்குதல் {#prompt} + +அடுத்து, இந்த மேற்கோள்களின் பட்டியலை ஒரு LLM-க்கான ப்ராம்ப்டாக மாற்றி, எதிர்பார்க்கப்படும் எதிர்கால மதிப்பைப் பெற வேண்டும். + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +வெளியீடு இப்போது ஒரு LLM-க்கான ப்ராம்ப்டாக இருக்கும், இது போன்றது: + +``` +இந்தப் மேற்கோள்களைக் கருத்தில் கொள்ளுங்கள்: +சொத்து: WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +சொத்து: WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +2026-02-02T17:56 நேரத்தில் WETH/USDC-க்கான மதிப்பு என்னவாக இருக்கும் என்று நீங்கள் எதிர்பார்க்கிறீர்கள்? + +உங்கள் பதிலை இரண்டு தசம இடங்களுக்குச் சுருக்கி ஒரு ஒற்றை எண்ணாக வழங்கவும், +வேறு எந்த உரையும் இல்லாமல். +``` + +இங்கே `WETH/USDC` மற்றும் `WBTC/WETH` ஆகிய இரண்டு சொத்துக்களுக்கான மேற்கோள்கள் இருப்பதை கவனியுங்கள். மற்றொரு சொத்திலிருந்து மேற்கோள்களைச் சேர்ப்பது கணிப்புத் துல்லியத்தை மேம்படுத்தக்கூடும். + +#### ஒரு ப்ராம்ட் எப்படி இருக்கும் {#prompt-explanation} + +இந்த ப்ராம்ட் மூன்று பிரிவுகளைக் கொண்டுள்ளது, அவை LLM ப்ராம்ட்களில் மிகவும் பொதுவானவை. + +1. தகவல். LLMகள் தங்கள் பயிற்சியிலிருந்து நிறைய தகவல்களைக் கொண்டுள்ளன, ஆனால் அவை பொதுவாக சமீபத்திய தகவல்களைக் கொண்டிருக்காது. இதனால்தான் நாம் இங்கே சமீபத்திய மேற்கோள்களைப் பெற வேண்டும். ஒரு ப்ராம்ட்டில் தகவல்களைச் சேர்ப்பது [மீட்பு மேம்படுத்தப்பட்ட உருவாக்கம் (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation) என்று அழைக்கப்படுகிறது. + +2. உண்மையான கேள்வி. இதைத்தான் நாம் தெரிந்து கொள்ள விரும்புகிறோம். + +3. வெளியீட்டு வடிவமைப்பு வழிமுறைகள். பொதுவாக, ஒரு LLM அது எப்படி மதிப்பிட்டது என்ற விளக்கத்துடன் ஒரு மதிப்பீட்டை நமக்கு வழங்கும். இது மனிதர்களுக்கு நல்லது, ஆனால் ஒரு கணினி நிரலுக்கு முடிவு மட்டுமே தேவை. + +#### குறியீடு விளக்கம் {#prompt-code} + +இதோ புதிய குறியீடு. + +```python +from datetime import datetime, timezone, timedelta +``` + +நாம் ஒரு மதிப்பீட்டை விரும்பும் நேரத்தை LLM-க்கு வழங்க வேண்டும். எதிர்காலத்தில் "n நிமிடங்கள்/மணிநேரங்கள்/நாட்கள்" என்ற நேரத்தைப் பெற, நாம் [the `timedelta` class](https://docs.python.org/3/library/datetime.html#datetime.timedelta)-ஐப் பயன்படுத்துகிறோம். + +```python +# நாம் படிக்கும் பூல்களின் முகவரிகள் +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +நாம் படிக்க வேண்டிய இரண்டு பூல்கள் உள்ளன. + +```python +@dataclass(frozen=True) +class PoolInfo: + . + . + . + reverse: bool = False + + def get_price(self, block: int) -> Decimal: + assert block <= w3.eth.block_number, "பிளாக் எதிர்காலத்தில் உள்ளது" + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (டோக்கன்1 க்கு டோக்கன்0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +WETH/USDC பூலில், `token0` (USDC) இல் இருந்து ஒரு `token1` (WETH)-ஐ வாங்க எவ்வளவு தேவை என்பதை அறிய விரும்புகிறோம். WETH/WBTC பூலில், `token0` (WBTC, இது வ்ராப்ட் பிட்காயின்) ஐ வாங்க எவ்வளவு `token1` (WETH) தேவை என்பதை நாம் அறிய விரும்புகிறோம். பூலின் விகிதம் மாற்றப்பட வேண்டுமா என்பதைக் கண்காணிக்க வேண்டும். + +```python +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 + ) +``` + +ஒரு பூல் மாற்றப்பட வேண்டுமா என்பதை அறிய, அதை `read_pool`-க்கு உள்ளீடாகப் பெறுகிறோம். மேலும், சொத்தின் சின்னம் சரியாக அமைக்கப்பட வேண்டும். + +` if else ` தொடரியல் Python-இன் [மும்மை நிபந்தனை ஆபரேட்டருக்கு](https://en.wikipedia.org/wiki/Ternary_conditional_operator) சமமானது, இது C-பெறப்பட்ட மொழியில் ` ? : ` ஆக இருக்கும். + +```python +def format_quotes(quotes: list[Quote]) -> str: + result = f"சொத்து: {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 +``` + +இந்தச் செயல்பாடு, `Quote` பொருட்களின் பட்டியலை வடிவமைக்கும் ஒரு சரத்தை உருவாக்குகிறது, அவை அனைத்தும் ஒரே சொத்துக்குப் பொருந்தும் என்று கருதி. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +Python-இல் [பல-வரி சரங்கள்](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) `"""` .... என எழுதப்படுகின்றன. `"""`. + +```python +இந்த மேற்கோள்களைக் கருத்தில் கொள்ளுங்கள்: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +இங்கே, ஒவ்வொரு மேற்கோள் பட்டியலுக்கும் `format_quotes`-உடன் ஒரு சரத்தை உருவாக்க [MapReduce](https://en.wikipedia.org/wiki/MapReduce) முறையைப் பயன்படுத்துகிறோம், பின்னர் அவற்றை ப்ராம்ட்டில் பயன்படுத்த ஒரு ஒற்றை சரமாக குறைக்கிறோம். + +```python +{asset}-க்கான மதிப்பு {expected_time} நேரத்தில் என்னவாக இருக்கும் என்று எதிர்பார்க்கிறீர்கள்? + +உங்கள் பதிலை இரண்டு தசம இடங்களுக்குச் சுருக்கி ஒரு ஒற்றை எண்ணாக வழங்கவும், +வேறு எந்த உரையும் இல்லாமல். + """ +``` + +ப்ராம்ட்டின் மீதமுள்ள பகுதி எதிர்பார்த்தபடியே உள்ளது. + +```python +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 +) +``` + +இரண்டு பூல்களையும் மதிப்பாய்வு செய்து, இரண்டிலிருந்தும் மேற்கோள்களைப் பெறவும். + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +நாம் மதிப்பீட்டை விரும்பும் எதிர்கால நேரப் புள்ளியைத் தீர்மானித்து, ப்ராம்ப்டை உருவாக்கவும். + +### ஒரு LLM உடன் இடைமுகம் செய்தல் {#interface-llm} + +அடுத்து, நாம் ஒரு உண்மையான LLM-ஐ ப்ராம்ட் செய்து, எதிர்பார்க்கப்படும் எதிர்கால மதிப்பைப் பெறுகிறோம். நான் இந்த நிரலை OpenAI-ஐப் பயன்படுத்தி எழுதினேன், எனவே நீங்கள் வேறு ஒரு வழங்குநரைப் பயன்படுத்த விரும்பினால், நீங்கள் அதை சரிசெய்ய வேண்டும். + +1. ஒரு [OpenAI கணக்கை](https://auth.openai.com/create-account)ப் பெறவும் + +2. [கணக்கிற்கு நிதியளிக்கவும்](https://platform.openai.com/settings/organization/billing/overview)—எழுதுகின்ற நேரத்தில் குறைந்தபட்சத் தொகை $5 ஆகும் + +3. [ஒரு பயன்பாட்டு நிரலாக்க இடைமுகம் (API) விசையை உருவாக்கவும்](https://platform.openai.com/settings/organization/api-keys) + +4. கட்டளை வரியில், உங்கள் நிரல் அதைப் பயன்படுத்தும் வகையில் பயன்பாட்டு நிரலாக்க இடைமுகம் (API) விசையை ஏற்றுமதி செய்யவும் + + ```sh + export OPENAI_API_KEY=sk-<விசையின் மீதமுள்ள பகுதி இங்கே வரும்> + ``` + +5. முகவரை சரிபார்த்து இயக்கவும் + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +இதோ புதிய குறியீடு. + +```python +from openai import OpenAI + +open_ai = OpenAI() # வாடிக்கையாளர் OPENAI_API_KEY சூழல் மாறியைப் படிக்கிறார் +``` + +OpenAI பயன்பாட்டு நிரலாக்க இடைமுகம் (API)-ஐ இறக்குமதி செய்து செயல்படுத்தவும். + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "பயனர்", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +பதிலை உருவாக்க OpenAI பயன்பாட்டு நிரலாக்க இடைமுகம் (API)-ஐ (`open_ai.chat.completions.create`) அழைக்கவும். + +```python +expected_price = Decimal(response.choices[0].message.content.strip()) +current_price = wethusdc_quotes[-1].price + +print ("தற்போதைய விலை:", wethusdc_quotes[-1].price) +print(f"{future_time}-இல், எதிர்பார்க்கப்படும் விலை: {expected_price} USD") + +if (expected_price > current_price): + print(f"வாங்கு, விலை {expected_price - current_price} USD உயரும் என்று எதிர்பார்க்கிறேன்") +else: + print(f"விற்பனை செய், விலை {current_price - expected_price} USD குறையும் என்று எதிர்பார்க்கிறேன்") +``` + +விலையை வெளியிட்டு, வாங்க அல்லது விற்க பரிந்துரையை வழங்கவும். + +#### கணிப்புகளைச் சோதித்தல் {#testing-the-predictions} + +இப்போது நாம் கணிப்புகளை உருவாக்க முடியும் என்பதால், நாம் பயனுள்ள கணிப்புகளை உருவாக்குகிறோமா என்பதை மதிப்பிடுவதற்கு வரலாற்றுத் தரவையும் பயன்படுத்தலாம். + +```sh +uv run test-predictor.py +``` + +எதிர்பார்க்கப்படும் முடிவு இது போன்றது: + +``` +2026-01-05T19:50-க்கான கணிப்பு: கணிக்கப்பட்டது 3138.93 USD, உண்மையானது 3218.92 USD, பிழை 79.99 USD +2026-01-06T19:56-க்கான கணிப்பு: கணிக்கப்பட்டது 3243.39 USD, உண்மையானது 3221.08 USD, பிழை 22.31 USD +2026-01-07T20:02-க்கான கணிப்பு: கணிக்கப்பட்டது 3223.24 USD, உண்மையானது 3146.89 USD, பிழை 76.35 USD +2026-01-08T20:11-க்கான கணிப்பு: கணிக்கப்பட்டது 3150.47 USD, உண்மையானது 3092.04 USD, பிழை 58.43 USD +. +. +. +2026-01-31T22:33-க்கான கணிப்பு: கணிக்கப்பட்டது 2637.73 USD, உண்மையானது 2417.77 USD, பிழை 219.96 USD +2026-02-01T22:41-க்கான கணிப்பு: கணிக்கப்பட்டது 2381.70 USD, உண்மையானது 2318.84 USD, பிழை 62.86 USD +2026-02-02T22:49-க்கான கணிப்பு: கணிக்கப்பட்டது 2234.91 USD, உண்மையானது 2349.28 USD, பிழை 114.37 USD +29 கணிப்புகளின் சராசரி கணிப்பு பிழை: 83.87103448275862068965517241 USD +ஒரு பரிந்துரைக்கான சராசரி மாற்றம்: 4.787931034482758620689655172 USD +மாற்றங்களின் நிலையான மாறுபாடு: 104.42 USD +இலாபகரமான நாட்கள்: 51.72% +இழப்பு நாட்கள்: 48.28% +``` + +சோதனையாளரின் பெரும்பகுதி முகவரைப் போலவே உள்ளது, ஆனால் புதிய அல்லது மாற்றியமைக்கப்பட்ட பகுதிகள் இங்கே உள்ளன. + +```python +CYCLES_FOR_TEST = 40 # பேக்டெஸ்டுக்கு, நாம் எத்தனை சுழற்சிகளைச் சோதிக்கிறோம் + +# நிறைய மேற்கோள்களைப் பெறவும் +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 +) +``` + +நாம் `CYCLES_FOR_TEST` (இங்கே 40 என குறிப்பிடப்பட்டுள்ளது) நாட்களை பின்னோக்கிப் பார்க்கிறோம். + +```python +# கணிப்புகளை உருவாக்கி அவற்றை உண்மையான வரலாற்றுடன் சரிபார்க்கவும் + +total_error = Decimal(0) +changes = [] +``` + +நாம் ஆர்வமாக உள்ள இரண்டு வகையான பிழைகள் உள்ளன. முதலாவது, `total_error`, கணிப்பாளர் செய்த பிழைகளின் மொத்தமாகும். + +இரண்டாவதாக, `changes`-ஐப் புரிந்து கொள்ள, முகவரின் நோக்கத்தை நாம் நினைவில் கொள்ள வேண்டும். WETH/USDC விகிதத்தை (ETH விலை) கணிப்பது அல்ல. விற்பனை மற்றும் வாங்குதல் பரிந்துரைகளை வழங்குவதே அதன் நோக்கம். விலை தற்போது $2000 ஆக இருந்து, நாளை $2010 ஆக இருக்கும் என்று கணித்தால், உண்மையான முடிவு $2020 ஆக இருந்தால், நாம் கூடுதல் பணம் சம்பாதிப்பதால் கவலைப்பட மாட்டோம். ஆனால், அது $2010 என்று கணித்து, அந்தப் பரிந்துரையின் அடிப்படையில் ETH ஐ வாங்கி, விலை $1990 ஆகக் குறைந்தால் நாம் கவலைப்படுவோம். + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +முழுமையான வரலாறு (கணிப்பிற்காகப் பயன்படுத்தப்பட்ட மதிப்புகள் மற்றும் அதை ஒப்பிடுவதற்கான உண்மையான மதிப்பு) கிடைக்கும் நிகழ்வுகளை மட்டுமே நாம் பார்க்க முடியும். இதன் பொருள் புதிய வழக்கு `CYCLES_BACK` முன்பு தொடங்கியதாக இருக்க வேண்டும். + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +முகவர் பயன்படுத்தும் மாதிரிகளின் அதே எண்ணிக்கையைப் பெற [ஸ்லைஸ்களை](https://www.w3schools.com/python/ref_func_slice.asp)ப் பயன்படுத்தவும். இங்குள்ள மற்றும் அடுத்த பிரிவிற்கு இடையிலான குறியீடு, முகவரில் நாம் வைத்திருக்கும் அதே கணிப்பு-பெறும் குறியீடு ஆகும். + +```python + 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 +``` + +கணிக்கப்பட்ட விலை, உண்மையான விலை மற்றும் கணிப்பு நேரத்தில் இருந்த விலை ஆகியவற்றைப் பெறவும். பரிந்துரை வாங்குவதா அல்லது விற்பதா என்பதைத் தீர்மானிக்க கணிப்பு நேரத்தில் இருந்த விலை நமக்குத் தேவை. + +```python + error = abs(predicted_price - real_price) + total_error += error + print (f"{prediction_time}-க்கான கணிப்பு: கணிக்கப்பட்டது {predicted_price} USD, உண்மையானது {real_price} USD, பிழை {error} USD") +``` + +பிழையைக் கண்டுபிடித்து, அதை மொத்தத்துடன் சேர்க்கவும். + +```python + recomended_action = 'வாங்க' என்றால் predicted_price > prediction_time_price else 'விற்பனை' + price_increase = real_price - prediction_time_price + changes.append(price_increase if recomended_action == 'வாங்க' else -price_increase) +``` + +`changes`-க்கு, ஒரு ETH-ஐ வாங்குவதன் அல்லது விற்பதன் பணத் தாக்கத்தை நாம் விரும்புகிறோம். எனவே முதலில், நாம் பரிந்துரையைத் தீர்மானிக்க வேண்டும், பின்னர் உண்மையான விலை எவ்வாறு மாறியது என்பதை மதிப்பீடு செய்ய வேண்டும், மற்றும் பரிந்துரை பணம் சம்பாதித்ததா (நேர்மறை மாற்றம்) அல்லது பணத்தை இழந்ததா (எதிர்மறை மாற்றம்) என்பதை மதிப்பிட வேண்டும். + +```python +print (f"{len(wethusdc_quotes)-CYCLES_BACK} கணிப்புகளுக்கான சராசரி கணிப்பு பிழை: {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} USD") +var = sum((x - mean_change) ** 2 for x in changes) / length_changes +print (f"மாற்றங்களின் நிலையான மாறுபாடு: {var.sqrt().quantize(Decimal("0.01"))} USD") +``` + +முடிவுகளைப் புகாரளிக்கவும். + +```python +print (f"இலாபகரமான நாட்கள்: {len(list(filter(lambda x: x > 0, changes)))/length_changes:.2%}") +print (f"இழப்பு நாட்கள்: {len(list(filter(lambda x: x < 0, changes)))/length_changes:.2%}") +``` + +இலாபகரமான நாட்கள் மற்றும் செலவுமிக்க நாட்களின் எண்ணிக்கையைக் கணக்கிட [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) ஐப் பயன்படுத்தவும். முடிவு ஒரு ஃபில்டர் பொருள், நீளத்தைப் பெற அதை ஒரு பட்டியலாக மாற்ற வேண்டும். + +### பரிவர்த்தனைகளைச் சமர்ப்பித்தல் {#submit-txn} + +இப்போது நாம் உண்மையில் பரிவர்த்தனைகளைச் சமர்ப்பிக்க வேண்டும். இருப்பினும், அமைப்பு நிரூபிக்கப்படுவதற்கு முன்பு, இந்த நேரத்தில் நான் உண்மையான பணத்தைச் செலவழிக்க விரும்பவில்லை. அதற்கு பதிலாக, மெயின்நெட்டின் ஒரு உள்ளூர் ஃபோர்க்கை உருவாக்கி, அந்த நெட்வொர்க்கில் "வர்த்தகம்" செய்வோம். + +ஒரு உள்ளூர் ஃபோர்க்கை உருவாக்கி, வர்த்தகத்தை இயக்குவதற்கான படிகள் இங்கே. + +1. [Foundry](https://getfoundry.sh/introduction/installation)-ஐ நிறுவவும் + +2. [`anvil`](https://getfoundry.sh/anvil/overview)-ஐத் தொடங்கவும் + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` Foundry-க்கான இயல்புநிலை URL, http://localhost:8545-இல் கேட்கிறது, எனவே நாம் பிளாக்செயினைக் கையாளப் பயன்படுத்தும் [the `cast` command](https://getfoundry.sh/cast/overview)-க்கான URL-ஐக் குறிப்பிடத் தேவையில்லை. + +3. `anvil`-இல் இயக்கும்போது, ETH உள்ள பத்து சோதனை கணக்குகள் உள்ளன—முதலாவதுக்கான சூழல் மாறிகளை அமைக்கவும் + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. நாம் பயன்படுத்த வேண்டிய ஒப்பந்தங்கள் இவை. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) என்பது நாம் உண்மையில் வர்த்தகம் செய்யப் பயன்படுத்தும் Uniswap v3 ஒப்பந்தமாகும். நாம் நேரடியாக பூல் வழியாக வர்த்தகம் செய்யலாம், ஆனால் இது மிகவும் எளிதானது. + + கீழே உள்ள இரண்டு மாறிகளும் WETH மற்றும் USDC இடையே மாற்றத் தேவையான Uniswap v3 பாதைகள் ஆகும். + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. ஒவ்வொரு சோதனை கணக்கிலும் 10,000 ETH உள்ளது. வர்த்தகத்திற்காக 1000 WETH பெற 1000 ETH-ஐ வ்ராப் செய்ய WETH ஒப்பந்தத்தைப் பயன்படுத்தவும். + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. 500 WETH-ஐ USDC-க்கு வர்த்தகம் செய்ய `SwapRouter`-ஐப் பயன்படுத்தவும். + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + `approve` அழைப்பு, `SwapRouter` எங்கள் டோக்கன்களில் சிலவற்றை செலவழிக்க அனுமதிக்கும் ஒரு அனுமதியை உருவாக்குகிறது. ஒப்பந்தங்கள் நிகழ்வுகளைக் கண்காணிக்க முடியாது, எனவே நாம் நேரடியாக `SwapRouter` ஒப்பந்தத்திற்கு டோக்கன்களை மாற்றினால், அது பணம் செலுத்தப்பட்டதை அறியாது. அதற்கு பதிலாக, `SwapRouter` ஒப்பந்தத்தை ஒரு குறிப்பிட்ட தொகையைச் செலவழிக்க அனுமதிக்கிறோம், பின்னர் `SwapRouter` அதைச் செய்கிறது. இது `SwapRouter` ஆல் அழைக்கப்படும் ஒரு செயல்பாடு மூலம் செய்யப்படுகிறது, எனவே அது வெற்றிகரமாக இருந்ததா என்பதை அறியும். + +7. உங்களிடம் இரண்டு டோக்கன்களும் போதுமான அளவு உள்ளதா என்பதைச் சரிபார்க்கவும். + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +இப்போது நம்மிடம் WETH மற்றும் USDC இருப்பதால், நாம் உண்மையில் முகவரை இயக்கலாம். + +```sh +git checkout 05-trade +uv run agent.py +``` + +வெளியீடு இது போன்று இருக்கும்: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +தற்போதைய விலை: 1843.16 +2026-02-06T23:07-இல், எதிர்பார்க்கப்படும் விலை: 1724.41 USD +வர்த்தகத்திற்கு முன் கணக்கு இருப்புகள்: +USDC இருப்பு: 927301.578272 +WETH இருப்பு: 500 +விற்பனை செய், விலை 118.75 USD குறையும் என்று எதிர்பார்க்கிறேன் +அனுமதி பரிவர்த்தனை அனுப்பப்பட்டது: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +அனுமதி பரிவர்த்தனை மைன் செய்யப்பட்டது. +விற்பனை பரிவர்த்தனை அனுப்பப்பட்டது: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +விற்பனை பரிவர்த்தனை மைன் செய்யப்பட்டது. +வர்த்தகத்திற்குப் பிறகு கணக்கு இருப்புகள்: +USDC இருப்பு: 929143.797116 +WETH இருப்பு: 499 +``` + +இதை உண்மையில் பயன்படுத்த, உங்களுக்கு சில சிறிய மாற்றங்கள் தேவை. + +- வரி 14-இல், `MAINNET_URL`-ஐ `https://eth.drpc.org` போன்ற ஒரு உண்மையான அணுகல் புள்ளிக்கு மாற்றவும் +- வரி 28-இல், `PRIVATE_KEY`-ஐ உங்கள் சொந்த தனிப்பட்ட விசைக்கு மாற்றவும் +- நீங்கள் மிகவும் செல்வந்தராக இருந்து, நிரூபிக்கப்படாத ஒரு முகவருக்காக ஒவ்வொரு நாளும் 1 ETH வாங்கவோ அல்லது விற்கவோ முடியாவிட்டால், `WETH_TRADE_AMOUNT`-ஐக் குறைக்க 29-ஐ மாற்ற விரும்பலாம் + +#### குறியீடு விளக்கம் {#trading-code} + +இதோ புதிய குறியீடு. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +படி 4-இல் நாம் பயன்படுத்திய அதே மாறிகள். + +```python +WETH_TRADE_AMOUNT=1 +``` + +வர்த்தகம் செய்ய வேண்டிய தொகை. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +உண்மையில் வர்த்தகம் செய்ய, நமக்கு `approve` செயல்பாடு தேவை. நாம் முன்பும் பின்பும் இருப்புகளைக் காட்ட விரும்புகிறோம், எனவே நமக்கு `balanceOf` உம் தேவை. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +`SwapRouter` பயன்பாடு பைனரி இடைமுகம் (ABI)-இல் நமக்கு `exactInput` மட்டுமே தேவை. சரியாக ஒரு WETH-ஐ வாங்க நாம் பயன்படுத்தக்கூடிய `exactOutput` என்ற தொடர்புடைய செயல்பாடு உள்ளது, ஆனால் எளிமைக்காக நாம் இரண்டு நிகழ்வுகளிலும் `exactInput`-ஐ மட்டுமே பயன்படுத்துகிறோம். + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +[`கணக்கு`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) மற்றும் `SwapRouter` ஒப்பந்தத்திற்கான Web3 வரையறைகள். + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +பரிவர்த்தனை அளவுருக்கள். ஒவ்வொரு முறையும் [நான்ஸ்](https://en.wikipedia.org/wiki/Cryptographic_nonce) மாற வேண்டும் என்பதால், இங்கு நமக்கு ஒரு செயல்பாடு தேவை. + +```python +def approve_token(contract: Contract, amount: int): +``` + +`SwapRouter`-க்கு ஒரு டோக்கன் அனுமதியை அங்கீகரிக்கவும். + +```python + txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params()) + signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY) + tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction) +``` + +Web3-இல் ஒரு பரிவர்த்தனையை இப்படித்தான் அனுப்புகிறோம். முதலில் பரிவர்த்தனையை உருவாக்க [the `Contract` object](https://web3py.readthedocs.io/en/stable/web3.contract.html) ஐப் பயன்படுத்துகிறோம். பின்னர் பரிவர்த்தனையில் கையொப்பமிட `PRIVATE_KEY` ஐப் பயன்படுத்தி [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) ஐப் பயன்படுத்துகிறோம். இறுதியாக, பரிவர்த்தனையை அனுப்ப [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) ஐப் பயன்படுத்துகிறோம். + +```python + print(f"அனுமதி பரிவர்த்தனை அனுப்பப்பட்டது: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("அனுமதி பரிவர்த்தனை மைன் செய்யப்பட்டது.") +``` + +[`w3.eth.wait_for_transaction_receipt`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) பரிவர்த்தனை மைன் செய்யப்படும் வரை காத்திருக்கிறது. தேவைப்பட்டால் அது ரசீதைத் திருப்பித் தருகிறது. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +இவை WETH விற்கும் போது அளவுருக்கள். + +```python +def make_buy_params(quote: Quote) -> dict: + return { + "path": USDC_TO_WETH, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals, + "amountOutMinimum": 0, + } +``` + +`SELL_PARAMS`-க்கு மாறாக, வாங்குதல் அளவுருக்கள் மாறலாம். உள்ளீட்டுத் தொகை 1 WETH இன் விலை, இது `quote`-இல் கிடைக்கிறது. + +```python +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"வாங்க பரிவர்த்தனை அனுப்பப்பட்டது: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("வாங்க பரிவர்த்தனை மைன் செய்யப்பட்டது.") + + +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"விற்பனை பரிவர்த்தனை அனுப்பப்பட்டது: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("விற்பனை பரிவர்த்தனை மைன் செய்யப்பட்டது.") +``` + +`buy()` மற்றும் `sell()` செயல்பாடுகள் கிட்டத்தட்ட ஒரே மாதிரியானவை. முதலில் நாம் `SwapRouter`-க்கு போதுமான அனுமதியை அங்கீகரிக்கிறோம், பின்னர் அதை சரியான பாதை மற்றும் தொகையுடன் அழைக்கிறோம். + +```python +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} இருப்பு: {Decimal(token0_balance) / Decimal(10 ** wethusdc_pool.token0.decimals)}") + print(f"{wethusdc_pool.token1.symbol} இருப்பு: {Decimal(token1_balance) / Decimal(10 ** wethusdc_pool.token1.decimals)}") +``` + +இரண்டு நாணயங்களிலும் பயனர் இருப்புகளைப் புகாரளிக்கவும். + +```python +print("வர்த்தகத்திற்கு முன் கணக்கு இருப்புகள்:") +balances() + +if (expected_price > current_price): + print(f"வாங்கு, விலை {expected_price - current_price} USD உயரும் என்று எதிர்பார்க்கிறேன்") + buy(wethusdc_quotes[-1]) +else: + print(f"விற்பனை செய், விலை {current_price - expected_price} USD குறையும் என்று எதிர்பார்க்கிறேன்") + sell() + +print("வர்த்தகத்திற்குப் பிறகு கணக்கு இருப்புகள்:") +balances() +``` + +இந்த முகவர் தற்போது ஒரு முறை மட்டுமே வேலை செய்கிறது. இருப்பினும், நீங்கள் அதை [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html)-இலிருந்து இயக்குவதன் மூலமாகவோ அல்லது வரிகள் 368-400-ஐ ஒரு லூப்பில் வைத்து, அடுத்த சுழற்சிக்கான நேரம் வரும் வரை காத்திருக்க [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep)-ஐப் பயன்படுத்துவதன் மூலமாகவோ அதைத் தொடர்ந்து வேலை செய்யும்படி மாற்றலாம். + +## சாத்தியமான மேம்பாடுகள் {#improvements} + +இது ஒரு முழுமையான தயாரிப்பு பதிப்பு அல்ல; இது அடிப்படைகளைக் கற்பிப்பதற்கான ஒரு எடுத்துக்காட்டு மட்டுமே. மேம்பாடுகளுக்கான சில யோசனைகள் இங்கே உள்ளன. + +### புத்திசாலித்தனமான வர்த்தகம் {#smart-trading} + +என்ன செய்ய வேண்டும் என்பதைத் தீர்மானிக்கும் போது முகவர் புறக்கணிக்கும் இரண்டு முக்கியமான உண்மைகள் உள்ளன. + +- _எதிர்பார்க்கப்படும் மாற்றத்தின் அளவு_. விலை குறையும் என்று எதிர்பார்க்கப்பட்டால், சரிவின் அளவைப் பொருட்படுத்தாமல் முகவர் ஒரு குறிப்பிட்ட அளவு `WETH`-ஐ விற்கிறது. + சிறு மாற்றங்களைப் புறக்கணித்து, விலை எவ்வளவு குறையும் என்று நாம் எதிர்பார்க்கிறோம் என்பதன் அடிப்படையில் விற்பது நல்லது என்று வாதிடலாம். +- _தற்போதைய போர்ட்ஃபோலியோ_. உங்கள் போர்ட்ஃபோலியோவில் 10% WETH-இல் இருந்தால், விலை உயரும் என்று நீங்கள் நினைத்தால், மேலும் வாங்குவது அர்த்தமுள்ளதாக இருக்கும். ஆனால் உங்கள் போர்ட்ஃபோலியோவில் 90% WETH-இல் இருந்தால், நீங்கள் போதுமான அளவு வெளிப்பட்டிருக்கலாம், மேலும் வாங்க வேண்டிய அவசியமில்லை. விலை குறையும் என்று நீங்கள் எதிர்பார்த்தால் இதன் நேர்மாறானது உண்மை. + +### உங்கள் வர்த்தக உத்தியை ரகசியமாக வைத்திருக்க விரும்பினால் என்ன செய்வது? {#secret} + +AI விற்பனையாளர்கள் நீங்கள் அவர்களின் LLMகளுக்கு அனுப்பும் வினவல்களைப் பார்க்க முடியும், இது உங்கள் முகவருடன் நீங்கள் உருவாக்கிய மேதை வர்த்தக அமைப்பை வெளிப்படுத்தக்கூடும். பலர் பயன்படுத்தும் ஒரு வர்த்தக அமைப்பு பயனற்றது, ஏனெனில் நீங்கள் வாங்க விரும்பும் போது பலர் வாங்க முயற்சிப்பார்கள் (மற்றும் விலை உயரும்) மற்றும் நீங்கள் விற்க விரும்பும் போது விற்க முயற்சிப்பார்கள் (மற்றும் விலை குறைகிறது). + +இந்த சிக்கலைத் தவிர்க்க, நீங்கள் உள்நாட்டில் ஒரு LLM-ஐ இயக்கலாம், எடுத்துக்காட்டாக, [LM-Studio](https://lmstudio.ai/)-ஐப் பயன்படுத்தி. + +### AI பாட்-டிலிருந்து AI முகவருக்கு {#bot-to-agent} + +இது [ஒரு AI பாட், AI முகவர் அல்ல](/ai-agents/#ai-agents-vs-ai-bots) என்பதற்கு நீங்கள் ஒரு நல்ல வாதத்தை முன்வைக்கலாம். இது முன்னரே வரையறுக்கப்பட்ட தகவலை நம்பியிருக்கும் ஒப்பீட்டளவில் எளிமையான உத்தியைச் செயல்படுத்துகிறது. உதாரணமாக, Uniswap v3 பூல்களின் பட்டியலையும் அவற்றின் சமீபத்திய மதிப்புகளையும் வழங்கி, எந்தக் கலவை சிறந்த முன்கணிப்பு மதிப்பைக் கொண்டுள்ளது என்று கேட்பதன் மூலம் நாம் சுய-முன்னேற்றத்தை இயக்கலாம். + +### ஸ்லிப்பேஜ் பாதுகாப்பு {#slippage-protection} + +தற்போது எந்த [ஸ்லிப்பேஜ் பாதுகாப்பும்](https://uniswapv3book.com/milestone_3/slippage-protection.html) இல்லை. தற்போதைய மேற்கோள் $2000 ஆக இருந்து, எதிர்பார்க்கப்படும் விலை $2100 ஆக இருந்தால், முகவர் வாங்கும். இருப்பினும், முகவர் வாங்குவதற்கு முன்பு விலை $2200 ஆக உயர்ந்தால், இனி வாங்குவதில் அர்த்தமில்லை. + +ஸ்லிப்பேஜ் பாதுகாப்பைச் செயல்படுத்த, [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325)-இன் 325 மற்றும் 334 வரிகளில் ஒரு `amountOutMinimum` மதிப்பைக் குறிப்பிடவும். + +## முடிவுரை {#conclusion} + +நம்புகிறோம், இப்போது நீங்கள் AI முகவர்களுடன் தொடங்குவதற்கு போதுமான அளவு அறிந்திருப்பீர்கள். இது இந்த விஷயத்தின் முழுமையான கண்ணோட்டம் அல்ல; அதற்காக முழு புத்தகங்களும் அர்ப்பணிக்கப்பட்டுள்ளன, ஆனால் இது நீங்கள் தொடங்குவதற்கு போதுமானது. நல்ல அதிர்ஷ்டம்! + +[எனது மேலும் பணிகளை இங்கே பார்க்கவும்](https://cryptodocguy.pro/). diff --git a/public/content/translations/te/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/te/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..984b808d028 --- /dev/null +++ b/public/content/translations/te/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,978 @@ +--- +title: "ఇతీరియముపై మీ స్వంత AI ట్రేడింగ్ ఏజెంట్‌ను తయారు చేయండి" +description: "ఈ ట్యుటోరియల్‌లో మీరు ఒక సాధారణ AI ట్రేడింగ్ ఏజెంట్‌ను ఎలా తయారు చేయాలో నేర్చుకుంటారు. ఈ ఏజెంట్ బ్లాక్ చైను నుండి సమాచారాన్ని చదువుతుంది, ఆ సమాచారం ఆధారంగా సిఫార్సు కోసం LLMని అడుగుతుంది, LLM సిఫార్సు చేసిన ట్రేడ్‌ను నిర్వహిస్తుంది, ఆపై వేచి ఉండి, పునరావృతం చేస్తుంది." +author: Ori Pomerantz +tags: [ "AI", "ట్రేడింగ్", "ఏజెంట్", "python" ] +skill: intermediate +published: 2026-02-13 +lang: te +sidebarDepth: 3 +--- + +ఈ ట్యుటోరియల్‌లో మీరు ఒక సాధారణ AI ట్రేడింగ్ ఏజెంట్‌ను ఎలా నిర్మించాలో నేర్చుకుంటారు. ఈ ఏజెంట్ ఈ దశలను ఉపయోగించి పనిచేస్తుంది: + +1. టోకెన్ యొక్క ప్రస్తుత మరియు గత ధరలను, అలాగే ఇతర సంభావ్యంగా సంబంధిత సమాచారాన్ని చదవండి +2. ఈ సమాచారంతో, అది ఎలా సంబంధితంగా ఉండవచ్చో వివరించడానికి నేపథ్య సమాచారంతో పాటు ఒక క్వెరీని నిర్మించండి +3. క్వెరీని సమర్పించి, అంచనా వేయబడిన ధరను తిరిగి పొందండి +4. సిఫార్సు ఆధారంగా ట్రేడ్ చేయండి +5. వేచి ఉండి, పునరావృతం చేయండి + +ఈ ఏజెంట్ సమాచారాన్ని ఎలా చదవాలో, దానిని ఉపయోగపడే జవాబును ఇచ్చే క్వెరీగా అనువదించాలో, మరియు ఆ జవాబును ఎలా ఉపయోగించాలో ప్రదర్శిస్తుంది. ఇవన్నీ AI ఏజెంట్‌కు అవసరమైన దశలు. ఈ ఏజెంట్ Pythonలో అమలు చేయబడింది, ఎందుకంటే ఇది AIలో ఉపయోగించే అత్యంత సాధారణ భాష. + +## ఇది ఎందుకు చేయాలి? {#why-do-this} + +స్వయంచాలక ట్రేడింగ్ ఏజెంట్లు అభివృద్ధి చేసేవారిని ట్రేడింగ్ వ్యూహాన్ని ఎంచుకోవడానికి మరియు అమలు చేయడానికి అనుమతిస్తాయి. [AI agents](/ai-agents) మరింత సంక్లిష్టమైన మరియు డైనమిక్ ట్రేడింగ్ వ్యూహాలకు అనుమతిస్తాయి, అభివృద్ధి చేసేవాడు ఉపయోగించాలని కూడా పరిగణించని సమాచారం మరియు అల్గారిథమ్‌లను ఉపయోగించే అవకాశం ఉంది. + +## ఉపకరణాలు {#tools} + +ఈ ట్యుటోరియల్ కోట్స్ మరియు ట్రేడింగ్ కోసం [Python](https://www.python.org/), [Web3 లైబ్రరీ](https://web3py.readthedocs.io/en/stable/), మరియు [Uniswap v3](https://github.com/Uniswap/v3-periphery)ని ఉపయోగిస్తుంది. + +### Python ఎందుకు? {#python} + +AI కోసం అత్యంత విస్తృతంగా ఉపయోగించే భాష [Python](https://www.python.org/), కాబట్టి మేము ఇక్కడ దానిని ఉపయోగిస్తాము. మీకు Python తెలియకపోయినా చింతించకండి. భాష చాలా స్పష్టంగా ఉంటుంది, మరియు అది ఏమి చేస్తుందో నేను ఖచ్చితంగా వివరిస్తాను. + +[Web3 లైబ్రరీ](https://web3py.readthedocs.io/en/stable/) అనేది అత్యంత సాధారణ Python ఇతీరియము API. ఇది ఉపయోగించడానికి చాలా సులభం. + +### బ్లాక్ చైనుపై ట్రేడింగ్ {#trading-on-blockchain} + +[అనేక వికేంద్రీకృత ఎక్స్ఛేంజ్‌లు (DEX)](/apps/categories/defi/) ఉన్నాయి, ఇవి ఇతీరియముపై టోకెన్‌లను ట్రేడ్ చేయడానికి మిమ్మల్ని అనుమతిస్తాయి. అయినప్పటికీ, [ఆర్బిట్రేజ్](/developers/docs/smart-contracts/composability/#better-user-experience) కారణంగా అవి ఒకే రకమైన ఎక్స్ఛేంజ్ రేట్లను కలిగి ఉంటాయి. + +[Uniswap](https://app.uniswap.org/) అనేది విస్తృతంగా ఉపయోగించే DEX, దీనిని మనం కోట్స్ (టోకెన్ సాపేక్ష విలువలను చూడటానికి) మరియు ట్రేడ్‌ల కోసం ఉపయోగించవచ్చు. + +### OpenAI {#openai} + +ఒక పెద్ద భాషా నమూనా కోసం, నేను [OpenAI](https://openai.com/)తో ప్రారంభించాలని ఎంచుకున్నాను. ఈ ట్యుటోరియల్‌లోని అప్లికేషన్‌ను అమలు చేయడానికి మీరు API యాక్సెస్ కోసం చెల్లించాలి. $5 కనీస చెల్లింపు కంటే ఎక్కువ సరిపోతుంది. + +## అభివృద్ధి, దశలవారీగా {#step-by-step} + +అభివృద్ధిని సులభతరం చేయడానికి, మేము దశలవారీగా ముందుకు సాగుతాము. ప్రతి దశ GitHubలో ఒక బ్రాంచ్. + +### ప్రారంభించడం {#getting-started} + +UNIX లేదా Linux కింద ప్రారంభించడానికి దశలు ఉన్నాయి ([WSL](https://learn.microsoft.com/en-us/windows/wsl/install)తో సహా) + +1. మీ దగ్గర ఇప్పటికే లేకపోతే, [Python](https://www.python.org/downloads/)ను డౌన్‌లోడ్ చేసి ఇన్‌స్టాల్ చేయండి. + +2. GitHub రిపోజిటరీని క్లోన్ చేయండి. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. [`uv`](https://docs.astral.sh/uv/getting-started/installation/)ని ఇన్‌స్టాల్ చేయండి. మీ సిస్టమ్‌లోని కమాండ్ భిన్నంగా ఉండవచ్చు. + + ```sh + pipx install uv + ``` + +4. లైబ్రరీలను డౌన్‌లోడ్ చేయండి. + + ```sh + uv sync + ``` + +5. వర్చువల్ ఎన్విరాన్మెంట్‌ను యాక్టివేట్ చేయండి. + + ```sh + source .venv/bin/activate + ``` + +6. Python మరియు Web3 సరిగ్గా పనిచేస్తున్నాయో లేదో ధృవీకరించడానికి, `python3`ను అమలు చేసి, దానికి ఈ ప్రోగ్రామ్‌ను అందించండి. మీరు దానిని `>>>` ప్రాంప్ట్ వద్ద నమోదు చేయవచ్చు; ఫైల్‌ను సృష్టించాల్సిన అవసరం లేదు. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### బ్లాక్ చైను నుండి చదవడం {#read-blockchain} + +తదుపరి దశ బ్లాక్ చైను నుండి చదవడం. అలా చేయడానికి, మీరు `02-read-quote` బ్రాంచ్‌కు మారి, ఆపై ప్రోగ్రామ్‌ను అమలు చేయడానికి `uv`ని ఉపయోగించాలి. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +మీరు `Quote` ఆబ్జెక్ట్‌ల జాబితాను అందుకోవాలి, ప్రతి దానిలో టైమ్‌స్టాంప్, ధర, మరియు ఆస్తి (ప్రస్తుతం ఎల్లప్పుడూ `WETH/USDC`) ఉంటాయి. + +ఇక్కడ పంక్తి పంక్తి వివరణ ఉంది. + +```python +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 +``` + +మాకు అవసరమైన లైబ్రరీలను దిగుమతి చేయండి. వాటిని ఉపయోగించినప్పుడు కింద వివరించబడ్డాయి. + +```python +print = functools.partial(print, flush=True) +``` + +Python యొక్క `print`ను అవుట్‌పుట్‌ను తక్షణమే ఫ్లష్ చేసే వెర్షన్‌తో భర్తీ చేస్తుంది. ఇది దీర్ఘకాలం నడిచే స్క్రిప్ట్‌లో ఉపయోగపడుతుంది, ఎందుకంటే మేము స్టేటస్ అప్‌డేట్‌లు లేదా డీబగ్గింగ్ అవుట్‌పుట్ కోసం వేచి ఉండాలనుకోము. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +మెయిన్‌నెట్‌కు చేరుకోవడానికి ఒక URL. మీరు [నోడ్ యాజ్ ఎ సర్వీస్](/developers/docs/nodes-and-clients/nodes-as-a-service/) నుండి ఒకటి పొందవచ్చు లేదా [Chainlist](https://chainlist.org/chain/1)లో ప్రచారం చేయబడిన వాటిలో ఒకటి ఉపయోగించవచ్చు. + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +ఒక ఇతీరియము మెయిన్‌నెట్ బ్లాక్ సాధారణంగా ప్రతి పన్నెండు సెకన్లకు జరుగుతుంది, కాబట్టి ఇవి ఒక కాల వ్యవధిలో జరగాలని మేము ఆశించే బ్లాక్‌ల సంఖ్య. ఇది ఖచ్చితమైన సంఖ్య కాదని గమనించండి. [బ్లాక్ ప్రపోజర్](/developers/docs/consensus-mechanisms/pos/block-proposal/) డౌన్ అయినప్పుడు, ఆ బ్లాక్ దాటవేయబడుతుంది మరియు తదుపరి బ్లాక్ సమయం 24 సెకన్లు. మేము టైమ్‌స్టాంప్ కోసం ఖచ్చితమైన బ్లాక్‌ను పొందాలనుకుంటే, మేము [బైనరీ శోధన](https://en.wikipedia.org/wiki/Binary_search)ను ఉపయోగిస్తాము. అయినప్పటికీ, ఇది మన ప్రయోజనాలకు తగినంత దగ్గరగా ఉంది. భవిష్యత్తును అంచనా వేయడం ఖచ్చితమైన శాస్త్రం కాదు. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +చక్రం యొక్క పరిమాణం. మేము ప్రతి చక్రానికి ఒకసారి కోట్‌లను సమీక్షించి, తదుపరి చక్రం చివరిలో విలువను అంచనా వేయడానికి ప్రయత్నిస్తాము. + +```python +# The address of the pool we're reading +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +కోట్ విలువలు Uniswap 3 USDC/WETH పూల్ నుండి చిరునామా [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract) వద్ద తీసుకోబడ్డాయి. ఈ చిరునామా ఇప్పటికే చెక్‌సమ్ రూపంలో ఉంది, కానీ కోడ్‌ను పునర్వినియోగపరచడానికి [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address)ను ఉపయోగించడం మంచిది. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +ఇవి మేము సంప్రదించాల్సిన రెండు కాంట్రాక్టుల కోసం [ABIలు](https://docs.soliditylang.org/en/latest/abi-spec.html). కోడ్‌ను సంక్షిప్తంగా ఉంచడానికి, మేము కాల్ చేయాల్సిన ఫంక్షన్‌లను మాత్రమే చేర్చుతాము. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +[`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) లైబ్రరీని ప్రారంభించి, ఒక ఇతీరియము నోడ్‌కు కనెక్ట్ చేయండి. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +ఇది Pythonలో డేటా క్లాస్‌ను సృష్టించడానికి ఒక మార్గం. [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) డేటా రకం కాంట్రాక్ట్‌కు కనెక్ట్ చేయడానికి ఉపయోగించబడుతుంది. `(frozen=True)`ను గమనించండి. Pythonలో [బూలియన్‌లు](https://en.wikipedia.org/wiki/Boolean_data_type) `True` లేదా `False`గా నిర్వచించబడ్డాయి, క్యాపిటలైజ్ చేయబడ్డాయి. ఈ డేటా క్లాస్ `ఫ్రోజెన్` అంటే ఫీల్డ్‌లను మార్చలేము. + +ఇండెంటేషన్‌ను గమనించండి. [C-ఉత్పన్న భాషలకు](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages) విరుద్ధంగా, Python బ్లాక్‌లను సూచించడానికి ఇండెంటేషన్‌ను ఉపయోగిస్తుంది. Python ఇంటర్‌ప్రిటర్ తదుపరి నిర్వచనం ఈ డేటా క్లాస్‌లో భాగం కాదని తెలుసుకుంటుంది, ఎందుకంటే ఇది డేటా క్లాస్ ఫీల్డ్‌ల వలె అదే ఇండెంటేషన్‌లో ప్రారంభం కాదు. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +[`Decimal`](https://docs.python.org/3/library/decimal.html) రకం దశాంశ భిన్నాలను ఖచ్చితంగా నిర్వహించడానికి ఉపయోగించబడుతుంది. + +```python + def get_price(self, block: int) -> Decimal: +``` + +ఇది Pythonలో ఫంక్షన్‌ను నిర్వచించే మార్గం. నిర్వచనం `PoolInfo`లో ఇంకా భాగమని చూపించడానికి ఇండెంట్ చేయబడింది. + +డేటా క్లాస్‌లో భాగమైన ఫంక్షన్‌లో మొదటి పరామితి ఎల్లప్పుడూ `self`, ఇక్కడ పిలిచిన డేటా క్లాస్ ఉదాహరణ. ఇక్కడ మరొక పరామితి, బ్లాక్ నంబర్ ఉంది. + +```python + assert block <= w3.eth.block_number, "Block is in the future" +``` + +మనం భవిష్యత్తును చదవగలిగితే, ట్రేడింగ్ కోసం AI అవసరం లేదు. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +EVM నుండి Web3పై ఫంక్షన్‌ను కాల్ చేయడానికి సింటాక్స్ ఇది: `.functions.().call()`. పరామితులు EVM ఫంక్షన్ యొక్క పరామితులు (ఏవైనా ఉంటే; ఇక్కడ లేవు) లేదా బ్లాక్ చైను ప్రవర్తనను సవరించడానికి [పేరు పెట్టబడిన పరామితులు](https://en.wikipedia.org/wiki/Named_parameter) కావచ్చు. ఇక్కడ మేము ఒకటి, `block_identifier`ను, మనం అమలు చేయాలనుకుంటున్న [బ్లాక్ నంబర్](/developers/docs/apis/json-rpc/#default-block)ను పేర్కొనడానికి ఉపయోగిస్తాము. + +ఫలితం [ఈ నిర్మాణం, శ్రేణి రూపంలో](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). మొదటి విలువ రెండు టోకెన్‌ల మధ్య మార్పిడి రేటు యొక్క ఫంక్షన్. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +ఆన్‌చైన్ గణనలను తగ్గించడానికి, Uniswap v3 వాస్తవ మార్పిడి కారకాన్ని నిల్వ చేయదు కానీ దాని వర్గమూలాన్ని నిల్వ చేస్తుంది. EVM ఫ్లోటింగ్ పాయింట్ గణితం లేదా భిన్నాలకు మద్దతు ఇవ్వనందున, వాస్తవ విలువకు బదులుగా, ప్రతిస్పందన price296 + +```python + # (token1 per token0) + return 1/(raw_price * self.decimal_factor) +``` + +మనం పొందే ముడి ధర ప్రతి `token1`కి మనం పొందే `token0` సంఖ్య. మా పూల్‌లో `token0` USDC (యుఎస్ డాలర్‌తో సమానమైన విలువ కలిగిన స్టేబుల్‌కాయిన్) మరియు `token1` [WETH](https://opensea.io/learn/blockchain/what-is-weth). మనం నిజంగా కోరుకునే విలువ WETHకు డాలర్ల సంఖ్య, దాని విలోమం కాదు. + +దశాంశ కారకం రెండు టోకెన్‌ల కోసం [దశాంశ కారకాల](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) మధ్య నిష్పత్తి. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +ఈ డేటా క్లాస్ ఒక కోట్‌ను సూచిస్తుంది: ఒక నిర్దిష్ట సమయంలో ఒక నిర్దిష్ట ఆస్తి యొక్క ధర. ఈ సమయంలో, `asset` ఫీల్డ్ అసంబద్ధం ఎందుకంటే మనం ఒకే పూల్‌ను ఉపయోగిస్తాము మరియు అందువల్ల ఒకే ఆస్తిని కలిగి ఉంటాము. అయినప్పటికీ, మేము తరువాత మరిన్ని ఆస్తులను జోడిస్తాము. + +```python +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 + ) +``` + +ఈ ఫంక్షన్ ఒక చిరునామాను తీసుకుని, ఆ చిరునామాలోని టోకెన్ కాంట్రాక్ట్ గురించిన సమాచారాన్ని తిరిగి ఇస్తుంది. కొత్త [Web3 `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html)ని సృష్టించడానికి, మేము `w3.eth.contract`కు చిరునామా మరియు ABIని అందిస్తాము. + +```python +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) + ) +``` + +ఈ ఫంక్షన్ [ఒక నిర్దిష్ట పూల్](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol) గురించి మనకు కావలసినవన్నీ తిరిగి ఇస్తుంది. `f""` సింటాక్స్ ఒక [ఫార్మాట్ చేయబడిన స్ట్రింగ్](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +`Quote` ఆబ్జెక్ట్‌ను పొందండి. `block_number` కోసం డిఫాల్ట్ విలువ `None` (విలువ లేదు). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +బ్లాక్ నంబర్ పేర్కొనబడకపోతే, `w3.eth.block_number`ను ఉపయోగించండి, ఇది తాజా బ్లాక్ నంబర్. ఇది [ఒక `if` స్టేట్‌మెంట్](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement) కోసం సింటాక్స్. + +డిఫాల్ట్‌ను `w3.eth.block_number`కు సెట్ చేయడం మంచిదని అనిపించవచ్చు, కానీ అది సరిగ్గా పనిచేయదు ఎందుకంటే అది ఫంక్షన్ నిర్వచించబడిన సమయంలోని బ్లాక్ నంబర్ అవుతుంది. దీర్ఘకాలం నడిచే ఏజెంట్‌లో, ఇది ఒక సమస్య అవుతుంది. + +```python + 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 + ) +``` + +మానవులకు మరియు పెద్ద భాషా నమూనాలకు (LLM) చదవగలిగే ఫార్మాట్‌కు ఫార్మాట్ చేయడానికి [`datetime` లైబ్రరీ](https://docs.python.org/3/library/datetime.html)ని ఉపయోగించండి. విలువను రెండు దశాంశ స్థానాలకు గుండ్రంగా చేయడానికి [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize)ను ఉపయోగించండి. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +Pythonలో మీరు `list[]`ను ఉపయోగించి ఒక నిర్దిష్ట రకాన్ని మాత్రమే కలిగి ఉండే [జాబితాను](https://docs.python.org/3/library/stdtypes.html#typesseq-list) నిర్వచిస్తారు. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +Pythonలో [`for` లూప్](https://docs.python.org/3/tutorial/controlflow.html#for-statements) సాధారణంగా జాబితాపై పునరావృతమవుతుంది. కోట్‌లను కనుగొనడానికి బ్లాక్ నంబర్‌ల జాబితా [`range`](https://docs.python.org/3/library/stdtypes.html#range) నుండి వస్తుంది. + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +ప్రతి బ్లాక్ నంబర్ కోసం, `Quote` ఆబ్జెక్ట్‌ను పొంది, దానిని `quotes` జాబితాకు చేర్చండి. ఆ జాబితాను తిరిగి ఇవ్వండి. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +ఇది స్క్రిప్ట్ యొక్క ప్రధాన కోడ్. పూల్ సమాచారాన్ని చదివి, పన్నెండు కోట్‌లను పొంది, వాటిని [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) చేయండి. + +### ఒక ప్రాంప్ట్ సృష్టిస్తోంది {#prompt} + +తరువాత, మనం ఈ కోట్‌ల జాబితాను LLM కోసం ఒక ప్రాంప్ట్‌గా మార్చాలి మరియు ఊహించిన భవిష్యత్ విలువను పొందాలి. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +ఇప్పుడు అవుట్‌పుట్ LLM కోసం ఒక ప్రాంప్ట్‌గా ఉంటుంది, ఇలాంటిది: + +``` +ఈ కోట్‌లను బట్టి: +ఆస్తి: WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +ఆస్తి: WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +2026-02-02T17:56 సమయంలో WETH/USDC కోసం విలువ ఏమని మీరు ఆశిస్తారు? + +మీ సమాధానాన్ని రెండు దశాంశ స్థానాలకు గుండ్రంగా, ఇతర టెక్స్ట్ లేకుండా ఒకే సంఖ్యగా అందించండి. +``` + +ఇక్కడ రెండు ఆస్తులు, `WETH/USDC` మరియు `WBTC/WETH` కోసం కోట్‌లు ఉన్నాయని గమనించండి. మరొక ఆస్తి నుండి కోట్‌లను జోడించడం అంచనా యొక్క ఖచ్చితత్వాన్ని మెరుగుపరచవచ్చు. + +#### ఒక ప్రాంప్ట్ ఎలా కనిపిస్తుంది {#prompt-explanation} + +ఈ ప్రాంప్ట్‌లో మూడు విభాగాలు ఉన్నాయి, ఇవి LLM ప్రాంప్ట్‌లలో చాలా సాధారణం. + +1. సమాచారం. LLMలకు వాటి శిక్షణ నుండి చాలా సమాచారం ఉంటుంది, కానీ అవి సాధారణంగా తాజా సమాచారాన్ని కలిగి ఉండవు. ఇక్కడ తాజా కోట్‌లను తిరిగి పొందడానికి ఇది కారణం. ఒక ప్రాంప్ట్‌కు సమాచారాన్ని జోడించడాన్ని [రిట్రీవల్ ఆగ్మెంటెడ్ జనరేషన్ (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation) అంటారు. + +2. అసలు ప్రశ్న. ఇది మనం తెలుసుకోవాలనుకుంటున్నది. + +3. అవుట్‌పుట్ ఫార్మాటింగ్ సూచనలు. సాధారణంగా, ఒక LLM దానిని ఎలా చేరుకుందో వివరణతో ఒక అంచనాను ఇస్తుంది. ఇది మానవులకు మంచిది, కానీ కంప్యూటర్ ప్రోగ్రామ్‌కు కేవలం చివరి ఫలితం మాత్రమే అవసరం. + +#### కోడ్ వివరణ {#prompt-code} + +ఇక్కడ కొత్త కోడ్ ఉంది. + +```python +from datetime import datetime, timezone, timedelta +``` + +మేము ఏ సమయం కోసం అంచనా వేయాలనుకుంటున్నామో ఆ సమయాన్ని LLMకి అందించాలి. భవిష్యత్తులో "n నిమిషాలు/గంటలు/రోజులు" సమయం పొందడానికి, మేము [`timedelta` క్లాస్](https://docs.python.org/3/library/datetime.html#datetime.timedelta)ను ఉపయోగిస్తాము. + +```python +# The addresses of the pools we're reading +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +మేము చదవాల్సిన రెండు పూల్‌లు ఉన్నాయి. + +```python +@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 # (token1 per token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +WETH/USDC పూల్‌లో, `token1` (WETH) ఒకటి కొనడానికి ఎన్ని `token0` (USDC) అవసరమో తెలుసుకోవాలనుకుంటున్నాము. WETH/WBTC పూల్‌లో, `token0` (WBTC, ఇది చుట్టబడిన Bitcoin) ఒకటి కొనడానికి ఎన్ని `token1` (WETH) అవసరమో తెలుసుకోవాలనుకుంటున్నాము. పూల్ నిష్పత్తిని తిప్పికొట్టాల్సిన అవసరం ఉందో లేదో మనం ట్రాక్ చేయాలి. + +```python +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 + ) +``` + +పూల్‌ను తిప్పికొట్టాల్సిన అవసరం ఉందో లేదో తెలుసుకోవడానికి, మేము దానిని `read_pool`కి ఇన్‌పుట్‌గా పొందుతాము. అలాగే, ఆస్తి చిహ్నాన్ని సరిగ్గా సెటప్ చేయాలి. + +` if else ` సింటాక్స్ C-ఉత్పన్న భాషలో ` ? : ` అయిన టర్నరీ షరతులతో కూడిన ఆపరేటర్ [ternary conditional operator](https://en.wikipedia.org/wiki/Ternary_conditional_operator) యొక్క Python సమానమైనది. + +```python +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 +``` + +ఈ ఫంక్షన్ `Quote` ఆబ్జెక్ట్‌ల జాబితాను ఫార్మాట్ చేసే స్ట్రింగ్‌ను నిర్మిస్తుంది, అవన్నీ ఒకే ఆస్తికి వర్తిస్తాయని ఊహిస్తుంది. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +Pythonలో [బహుళ-పంక్తి స్ట్రింగ్ లిటరల్స్](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) `"""` .... అని వ్రాయబడ్డాయి. `"""`. + +```python +ఈ కోట్‌లను బట్టి: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +ఇక్కడ, మేము `format_quotes`తో ప్రతి కోట్ జాబితాకు ఒక స్ట్రింగ్‌ను రూపొందించడానికి [మ్యాప్‌రిడ్యూస్](https://en.wikipedia.org/wiki/MapReduce) నమూనాను ఉపయోగిస్తాము, ఆపై వాటిని ప్రాంప్ట్‌లో ఉపయోగించడానికి ఒకే స్ట్రింగ్‌గా తగ్గిస్తాము. + +```python +{asset} కోసం {expected_time} సమయంలో విలువ ఏమని మీరు ఆశిస్తారు? + +మీ సమాధానాన్ని రెండు దశాంశ స్థానాలకు గుండ్రంగా, ఇతర టెక్స్ట్ లేకుండా ఒకే సంఖ్యగా అందించండి. + """ +``` + +ప్రాంప్ట్ యొక్క మిగిలిన భాగం ఊహించిన విధంగానే ఉంటుంది. + +```python +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 +) +``` + +రెండు పూల్‌లను సమీక్షించి, రెండింటి నుండి కోట్‌లను పొందండి. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +మనం ఏ భవిష్యత్ సమయ బిందువు కోసం అంచనా వేయాలనుకుంటున్నామో నిర్ణయించి, ప్రాంప్ట్‌ను సృష్టించండి. + +### LLMతో ఇంటర్‌ఫేస్ చేయడం {#interface-llm} + +తరువాత, మేము వాస్తవ LLMని ప్రాంప్ట్ చేసి, ఊహించిన భవిష్యత్ విలువను పొందుతాము. నేను ఈ ప్రోగ్రామ్‌ను OpenAI ఉపయోగించి వ్రాశాను, కాబట్టి మీరు వేరే ప్రొవైడర్‌ను ఉపయోగించాలనుకుంటే, మీరు దానిని సర్దుబాటు చేయాలి. + +1. [OpenAI ఖాతాను](https://auth.openai.com/create-account) పొందండి + +2. [ఖాతాకు నిధులు సమకూర్చండి](https://platform.openai.com/settings/organization/billing/overview)—వ్రాత సమయంలో కనీస మొత్తం $5 + +3. [API కీని సృష్టించండి](https://platform.openai.com/settings/organization/api-keys) + +4. కమాండ్ లైన్‌లో, మీ ప్రోగ్రామ్ దానిని ఉపయోగించడానికి API కీని ఎగుమతి చేయండి + + ```sh + export OPENAI_API_KEY=sk-<కీ యొక్క మిగిలిన భాగం ఇక్కడ వస్తుంది> + ``` + +5. ఏజెంట్‌ను చెక్అవుట్ చేసి అమలు చేయండి + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +ఇక్కడ కొత్త కోడ్ ఉంది. + +```python +from openai import OpenAI + +open_ai = OpenAI() # క్లయింట్ OPENAI_API_KEY ఎన్విరాన్మెంట్ వేరియబుల్‌ను చదువుతుంది +``` + +OpenAI APIని దిగుమతి చేసి, ప్రారంభించండి. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +ప్రతిస్పందనను సృష్టించడానికి OpenAI API (`open_ai.chat.completions.create`)ని కాల్ చేయండి. + +```python +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") +``` + +ధరను అవుట్‌పుట్ చేసి, కొనుగోలు లేదా అమ్మకం సిఫార్సును అందించండి. + +#### అంచనాలను పరీక్షించడం {#testing-the-predictions} + +ఇప్పుడు మనం అంచనాలను రూపొందించగలం కాబట్టి, మనం ఉపయోగకరమైన అంచనాలను ఉత్పత్తి చేస్తున్నామా లేదా అని అంచనా వేయడానికి చారిత్రక డేటాను కూడా ఉపయోగించవచ్చు. + +```sh +uv run test-predictor.py +``` + +ఊహించిన ఫలితం ఇలాంటిది: + +``` +2026-01-05T19:50 కోసం అంచనా: అంచనా 3138.93 USD, వాస్తవం 3218.92 USD, లోపం 79.99 USD +2026-01-06T19:56 కోసం అంచనా: అంచనా 3243.39 USD, వాస్తవం 3221.08 USD, లోపం 22.31 USD +2026-01-07T20:02 కోసం అంచనా: అంచనా 3223.24 USD, వాస్తవం 3146.89 USD, లోపం 76.35 USD +2026-01-08T20:11 కోసం అంచనా: అంచనా 3150.47 USD, వాస్తవం 3092.04 USD, లోపం 58.43 USD +. +. +. +2026-01-31T22:33 కోసం అంచనా: అంచనా 2637.73 USD, వాస్తవం 2417.77 USD, లోపం 219.96 USD +2026-02-01T22:41 కోసం అంచనా: అంచనా 2381.70 USD, వాస్తవం 2318.84 USD, లోపం 62.86 USD +2026-02-02T22:49 కోసం అంచనా: అంచనా 2234.91 USD, వాస్తవం 2349.28 USD, లోపం 114.37 USD +29 అంచనాలలో సగటు అంచనా లోపం: 83.87103448275862068965517241 USD +సిఫార్సు başına సగటు మార్పు: 4.787931034482758620689655172 USD +మార్పుల ప్రామాణిక విచలనం: 104.42 USD +లాభదాయక రోజులు: 51.72% +నష్టపోయే రోజులు: 48.28% +``` + +టెస్టర్ యొక్క చాలా భాగం ఏజెంట్‌తో సమానంగా ఉంటుంది, కానీ ఇక్కడ కొత్త లేదా సవరించిన భాగాలు ఉన్నాయి. + +```python +CYCLES_FOR_TEST = 40 # బ్యాక్‌టెస్ట్ కోసం, మేము ఎన్ని చక్రాలను పరీక్షిస్తాము + +# చాలా కోట్‌లను పొందండి +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 +) +``` + +మేము `CYCLES_FOR_TEST` (ఇక్కడ 40గా పేర్కొనబడింది) రోజుల వెనుకకు చూస్తాము. + +```python +# అంచనాలను సృష్టించి, వాటిని వాస్తవ చరిత్రతో సరిచూడండి + +total_error = Decimal(0) +changes = [] +``` + +మనం ఆసక్తి చూపే రెండు రకాల లోపాలు ఉన్నాయి. మొదటిది, `total_error`, కేవలం ప్రిడిక్టర్ చేసిన లోపాల మొత్తం. + +రెండవది, `changes`ను అర్థం చేసుకోవడానికి, మనం ఏజెంట్ యొక్క ఉద్దేశ్యాన్ని గుర్తుంచుకోవాలి. ఇది WETH/USDC నిష్పత్తిని (ETH ధర) అంచనా వేయడం కాదు. ఇది అమ్మకం మరియు కొనుగోలు సిఫార్సులను జారీ చేయడం. ప్రస్తుతం ధర $2000 అయితే మరియు రేపు $2010 అని అంచనా వేస్తే, వాస్తవ ఫలితం $2020 అయితే మరియు మనం అదనపు డబ్బు సంపాదిస్తే మనకు అభ్యంతరం లేదు. కానీ అది $2010 అని అంచనా వేసి, ఆ సిఫార్సు ఆధారంగా ETH కొనుగోలు చేసి, ధర $1990కి పడిపోతే మనం అభ్యంతరం చెబుతాము. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +మనం పూర్తి చరిత్ర (అంచనా కోసం ఉపయోగించిన విలువలు మరియు దానితో పోల్చడానికి నిజ-ప్రపంచ విలువ) అందుబాటులో ఉన్న కేసులను మాత్రమే చూడగలం. దీని అర్థం కొత్త కేసు `CYCLES_BACK` క్రితం ప్రారంభమైనది అయి ఉండాలి. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +ఏజెంట్ ఉపయోగించే నమూనాల సంఖ్యతో సమానమైన నమూనాలను పొందడానికి [స్లైస్‌లను](https://www.w3schools.com/python/ref_func_slice.asp) ఉపయోగించండి. ఇక్కడి నుండి తదుపరి విభాగానికి మధ్య ఉన్న కోడ్ ఏజెంట్‌లో మనం కలిగి ఉన్న అంచనా-పొందే కోడ్‌తో సమానంగా ఉంటుంది. + +```python + 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 +``` + +అంచనా వేసిన ధర, నిజమైన ధర, మరియు అంచనా వేసిన సమయంలోని ధరను పొందండి. సిఫార్సు కొనుగోలు లేదా అమ్మకం కోసమా అని నిర్ణయించడానికి మనకు అంచనా సమయంలోని ధర అవసరం. + +```python + 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") +``` + +లోపాన్ని కనుగొని, దానిని మొత్తానికి జోడించండి. + +```python + recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell' + price_increase = real_price - prediction_time_price + changes.append(price_increase if recomended_action == 'buy' else -price_increase) +``` + +`changes` కోసం, మనం ఒక ETH కొనుగోలు లేదా అమ్మకం యొక్క ద్రవ్య ప్రభావాన్ని కోరుకుంటున్నాము. కాబట్టి మొదట, మనం సిఫార్సును నిర్ణయించాలి, ఆపై వాస్తవ ధర ఎలా మారిందో అంచనా వేయాలి, మరియు సిఫార్సు డబ్బు సంపాదించిందా (సానుకూల మార్పు) లేదా డబ్బు ఖర్చు చేసిందా (ప్రతికూల మార్పు) అని అంచనా వేయాలి. + +```python +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") +``` + +ఫలితాలను నివేదించండి. + +```python +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%}") +``` + +లాభదాయక రోజుల సంఖ్య మరియు నష్టదాయక రోజుల సంఖ్యను లెక్కించడానికి [`filter`](https://www.w3schools.com/python/ref_func_filter.asp)ను ఉపయోగించండి. ఫలితం ఫిల్టర్ ఆబ్జెక్ట్, దాని పొడవును పొందడానికి మనం జాబితాగా మార్చాలి. + +### లావాదేవీలను సమర్పించడం {#submit-txn} + +ఇప్పుడు మనం నిజంగా లావాదేవీలను సమర్పించాలి. అయినప్పటికీ, వ్యవస్థ నిరూపించబడకముందే నేను ఈ సమయంలో నిజమైన డబ్బు ఖర్చు చేయాలనుకోవడం లేదు. బదులుగా, మేము మెయిన్‌నెట్ యొక్క స్థానిక ఫోర్క్‌ను సృష్టిస్తాము మరియు ఆ నెట్‌వర్క్‌లో "ట్రేడ్" చేస్తాము. + +స్థానిక ఫోర్క్‌ను సృష్టించడానికి మరియు ట్రేడింగ్‌ను ప్రారంభించడానికి ఇక్కడ దశలు ఉన్నాయి. + +1. [Foundry](https://getfoundry.sh/introduction/installation) ఇన్‌స్టాల్ చేయండి + +2. [`anvil`](https://getfoundry.sh/anvil/overview)ని ప్రారంభించండి + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` ఫౌండ్రీ కోసం డిఫాల్ట్ URL, http://localhost:8545లో వింటోంది, కాబట్టి మనం బ్లాక్ చైనును మార్చడానికి ఉపయోగించే [`cast` కమాండ్](https://getfoundry.sh/cast/overview) కోసం URLను పేర్కొనవలసిన అవసరం లేదు. + +3. `anvil`లో నడుస్తున్నప్పుడు, ETH ఉన్న పది టెస్ట్ ఖాతాలు ఉన్నాయి—మొదటి దాని కోసం ఎన్విరాన్మెంట్ వేరియబుల్స్ సెట్ చేయండి + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. ఇవి మనం ఉపయోగించాల్సిన కాంట్రాక్టులు. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) అనేది మనం నిజంగా ట్రేడ్ చేయడానికి ఉపయోగించే Uniswap v3 కాంట్రాక్ట్. మనం నేరుగా పూల్ ద్వారా ట్రేడ్ చేయవచ్చు, కానీ ఇది చాలా సులభం. + + రెండు దిగువ వేరియబుల్స్ WETH మరియు USDC మధ్య మార్పిడి చేయడానికి అవసరమైన Uniswap v3 మార్గాలు. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. ప్రతి టెస్ట్ ఖాతాకు 10,000 ETH ఉన్నాయి. ట్రేడింగ్ కోసం 1000 WETH పొందడానికి 1000 ETH చుట్టడానికి WETH కాంట్రాక్ట్‌ను ఉపయోగించండి. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. USDC కోసం 500 WETH ట్రేడ్ చేయడానికి `SwapRouter`ను ఉపయోగించండి. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + `approve` కాల్ `SwapRouter` మన టోకెన్‌లలో కొన్నింటిని ఖర్చు చేయడానికి అనుమతించే అనుమతిని సృష్టిస్తుంది. కాంట్రాక్టులు ఈవెంట్‌లను పర్యవేక్షించలేవు, కాబట్టి మనం నేరుగా `SwapRouter` కాంట్రాక్ట్‌కు టోకెన్‌లను బదిలీ చేస్తే, దానికి చెల్లింపు జరిగిందని తెలియదు. బదులుగా, మేము `SwapRouter` కాంట్రాక్ట్‌కు నిర్దిష్ట మొత్తాన్ని ఖర్చు చేయడానికి అనుమతిస్తాము, ఆపై `SwapRouter` దానిని చేస్తుంది. ఇది `SwapRouter` ద్వారా పిలువబడే ఫంక్షన్ ద్వారా జరుగుతుంది, కాబట్టి ఇది విజయవంతమైందో లేదో తెలుసుకుంటుంది. + +7. మీకు రెండు టోకెన్‌లు తగినంతగా ఉన్నాయని ధృవీకరించండి. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +ఇప్పుడు మనకు WETH మరియు USDC ఉన్నాయి, మనం నిజంగా ఏజెంట్‌ను అమలు చేయవచ్చు. + +```sh +git checkout 05-trade +uv run agent.py +``` + +అవుట్‌పుట్ ఇలాంటిదిగా ఉంటుంది: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +ప్రస్తుత ధర: 1843.16 +2026-02-06T23:07లో, ఊహించిన ధర: 1724.41 USD +ట్రేడ్‌కు ముందు ఖాతా బ్యాలెన్స్‌లు: +USDC బ్యాలెన్స్: 927301.578272 +WETH బ్యాలెన్స్: 500 +అమ్ము, ధర 118.75 USD తగ్గుతుందని నేను ఆశిస్తున్నాను +అనుమతి లావాదేవీ పంపబడింది: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +అనుమతి లావాదేవీ మైన్ చేయబడింది. +అమ్మకం లావాదేవీ పంపబడింది: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +అమ్మకం లావాదేవీ మైన్ చేయబడింది. +ట్రేడ్ తర్వాత ఖాతా బ్యాలెన్స్‌లు: +USDC బ్యాలెన్స్: 929143.797116 +WETH బ్యాలెన్స్: 499 +``` + +నిజంగా దానిని ఉపయోగించడానికి, మీకు కొన్ని చిన్న మార్పులు అవసరం. + +- లైన్ 14లో, `MAINNET_URL`ను నిజమైన యాక్సెస్ పాయింట్‌కు మార్చండి, ఉదాహరణకు `https://eth.drpc.org` +- లైన్ 28లో, `PRIVATE_KEY`ను మీ స్వంత ప్రైవేట్ కీకి మార్చండి +- మీరు చాలా ధనవంతులు అయితే మరియు నిరూపించబడని ఏజెంట్ కోసం ప్రతి రోజు 1 ETH కొనుగోలు లేదా అమ్మకం చేయగలిగితే తప్ప, మీరు 29ని మార్చి `WETH_TRADE_AMOUNT`ను తగ్గించాలనుకోవచ్చు + +#### కోడ్ వివరణ {#trading-code} + +ఇక్కడ కొత్త కోడ్ ఉంది. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +మనం దశ 4లో ఉపయోగించిన అవే వేరియబుల్స్. + +```python +WETH_TRADE_AMOUNT=1 +``` + +ట్రేడ్ చేయవలసిన మొత్తం. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +నిజంగా ట్రేడ్ చేయడానికి, మనకు `approve` ఫంక్షన్ అవసరం. మనం ముందు మరియు తరువాత బ్యాలెన్స్‌లను కూడా చూపించాలనుకుంటున్నాము, కాబట్టి మనకు `balanceOf` కూడా అవసరం. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +`SwapRouter` ABIలో మనకు కేవలం `exactInput` అవసరం. సంబంధిత ఫంక్షన్, `exactOutput` ఉంది, దానిని మనం సరిగ్గా ఒక WETH కొనడానికి ఉపయోగించవచ్చు, కానీ సులభతరం చేయడానికి మనం రెండు సందర్భాలలోనూ కేవలం `exactInput`ను ఉపయోగిస్తాము. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +[`account`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) మరియు `SwapRouter` కాంట్రాక్ట్ కోసం Web3 నిర్వచనాలు. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +లావాదేవీ పరామితులు. ఇక్కడ మనకు ఒక ఫంక్షన్ అవసరం ఎందుకంటే [నాన్స్](https://en.wikipedia.org/wiki/Cryptographic_nonce) ప్రతిసారీ మారాలి. + +```python +def approve_token(contract: Contract, amount: int): +``` + +`SwapRouter` కోసం టోకెన్ అనుమతిని ఆమోదించండి. + +```python + txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params()) + signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY) + tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction) +``` + +ఇది మనం Web3లో లావాదేవీని పంపే విధానం. మొదట మనం లావాదేవీని నిర్మించడానికి [`Contract` ఆబ్జెక్ట్](https://web3py.readthedocs.io/en/stable/web3.contract.html)ను ఉపయోగిస్తాము. అప్పుడు మనం `PRIVATE_KEY`ని ఉపయోగించి, లావాదేవీని సంతకం చేయడానికి [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction)ను ఉపయోగిస్తాము. చివరగా, మనం లావాదేవీని పంపడానికి [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction)ను ఉపయోగిస్తాము. + +```python + 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`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) లావాదేవీ మైన్ చేయబడే వరకు వేచి ఉంటుంది. అవసరమైతే అది రసీదును తిరిగి ఇస్తుంది. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +ఇవి WETH అమ్మేటప్పుడు పరామితులు. + +```python +def make_buy_params(quote: Quote) -> dict: + return { + "path": USDC_TO_WETH, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals, + "amountOutMinimum": 0, + } +``` + +`SELL_PARAMS`కి విరుద్ధంగా, కొనుగోలు పరామితులు మారవచ్చు. ఇన్‌పుట్ మొత్తం 1 WETH ఖర్చు, `quote`లో అందుబాటులో ఉంటుంది. + +```python +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.") +``` + +`buy()` మరియు `sell()` ఫంక్షన్‌లు దాదాపు ఒకేలా ఉంటాయి. మొదట మనం `SwapRouter` కోసం తగినంత అనుమతిని ఆమోదిస్తాము, ఆపై మనం దానిని సరైన మార్గం మరియు మొత్తంతో కాల్ చేస్తాము. + +```python +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)}") +``` + +రెండు కరెన్సీలలో వినియోగదారుల బ్యాలెన్స్‌లను నివేదించండి. + +```python +print("ట్రేడ్‌కు ముందు ఖాతా బ్యాలెన్స్‌లు:") +balances() + +if (expected_price > current_price): + print(f"కొను, ధర {expected_price - current_price} USD పెరుగుతుందని నేను ఆశిస్తున్నాను") + buy(wethusdc_quotes[-1]) +else: + print(f"అమ్ము, ధర {current_price - expected_price} USD తగ్గుతుందని నేను ఆశిస్తున్నాను") + sell() + +print("ట్రేడ్ తర్వాత ఖాతా బ్యాలెన్స్‌లు:") +balances() +``` + +ఈ ఏజెంట్ ప్రస్తుతం ఒకసారి మాత్రమే పనిచేస్తుంది. అయినప్పటికీ, మీరు దానిని [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) నుండి అమలు చేయడం ద్వారా లేదా 368-400 పంక్తులను లూప్‌లో చుట్టి, తదుపరి చక్రం కోసం వేచి ఉండటానికి [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep)ను ఉపయోగించడం ద్వారా నిరంతరం పనిచేసేలా మార్చవచ్చు. + +## సాధ్యమయ్యే మెరుగుదలలు {#improvements} + +ఇది పూర్తి ఉత్పత్తి వెర్షన్ కాదు; ఇది కేవలం ప్రాథమికాలను బోధించడానికి ఒక ఉదాహరణ. మెరుగుదలల కోసం ఇక్కడ కొన్ని ఆలోచనలు ఉన్నాయి. + +### స్మార్టర్ ట్రేడింగ్ {#smart-trading} + +ఏమి చేయాలో నిర్ణయించేటప్పుడు ఏజెంట్ విస్మరించే రెండు ముఖ్యమైన వాస్తవాలు ఉన్నాయి. + +- _అంచనా వేసిన మార్పు యొక్క పరిమాణం_. ధర తగ్గుతుందని ఊహించినప్పుడు, తగ్గుదల పరిమాణంతో సంబంధం లేకుండా ఏజెంట్ స్థిరమైన మొత్తంలో `WETH`ను అమ్ముతుంది. + వాస్తవానికి, చిన్న మార్పులను విస్మరించి, మనం ఎంత ధర తగ్గుతుందని ఊహిస్తున్నామో దాని ఆధారంగా అమ్మడం మంచిది. +- _ప్రస్తుత పోర్ట్‌ఫోలియో_. మీ పోర్ట్‌ఫోలియోలో 10% WETHలో ఉంటే మరియు ధర పెరుగుతుందని మీరు భావిస్తే, బహుశా మరింత కొనడం అర్ధవంతం కావచ్చు. కానీ మీ పోర్ట్‌ఫోలియోలో 90% WETHలో ఉంటే, మీరు తగినంతగా బహిర్గతం కావచ్చు మరియు మరింత కొనాల్సిన అవసరం లేదు. మీరు ధర తగ్గుతుందని ఊహించినప్పుడు దీనికి విరుద్ధంగా ఉంటుంది. + +### మీరు మీ ట్రేడింగ్ వ్యూహాన్ని రహస్యంగా ఉంచాలనుకుంటే ఏమిటి? {#secret} + +AI విక్రేతలు మీరు వారి LLMలకు పంపే ప్రశ్నలను చూడగలరు, ఇది మీరు మీ ఏజెంట్‌తో అభివృద్ధి చేసిన మేధావి ట్రేడింగ్ వ్యవస్థను బహిర్గతం చేయగలదు. చాలా మంది ఉపయోగించే ట్రేడింగ్ సిస్టమ్ పనికిరానిది, ఎందుకంటే మీరు కొనాలనుకున్నప్పుడు చాలా మంది కొనడానికి ప్రయత్నిస్తారు (మరియు ధర పెరుగుతుంది) మరియు మీరు అమ్మాలనుకున్నప్పుడు అమ్మడానికి ప్రయత్నిస్తారు (మరియు ధర తగ్గుతుంది). + +ఈ సమస్యను నివారించడానికి మీరు స్థానికంగా LLMని అమలు చేయవచ్చు, ఉదాహరణకు, [LM-Studio](https://lmstudio.ai/)ని ఉపయోగించి. + +### AI బాట్ నుండి AI ఏజెంట్‌కు {#bot-to-agent} + +ఇది [AI బాట్, AI ఏజెంట్ కాదు](/ai-agents/#ai-agents-vs-ai-bots) అని మీరు మంచి వాదన చేయవచ్చు. ఇది ముందే నిర్వచించబడిన సమాచారంపై ఆధారపడే సాపేక్షంగా సరళమైన వ్యూహాన్ని అమలు చేస్తుంది. మనం స్వీయ-మెరుగుదలను ప్రారంభించవచ్చు, ఉదాహరణకు, Uniswap v3 పూల్‌ల జాబితా మరియు వాటి తాజా విలువలను అందించి, ఏ కలయిక ఉత్తమ అంచనా విలువను కలిగి ఉందో అడగడం ద్వారా. + +### స్లిపేజ్ రక్షణ {#slippage-protection} + +ప్రస్తుతం [స్లిపేజ్ రక్షణ](https://uniswapv3book.com/milestone_3/slippage-protection.html) లేదు. ప్రస్తుత కోట్ $2000 అయితే, మరియు ఊహించిన ధర $2100 అయితే, ఏజెంట్ కొనుగోలు చేస్తుంది. అయినప్పటికీ, ఏజెంట్ కొనుగోలు చేయడానికి ముందు ఖర్చు $2200కి పెరిగితే, ఇకపై కొనడం అర్ధవంతం కాదు. + +స్లిపేజ్ రక్షణను అమలు చేయడానికి, [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325) యొక్క 325 మరియు 334 పంక్తులలో `amountOutMinimum` విలువను పేర్కొనండి. + +## ముగింపు {#conclusion} + +ఆశాజనకంగా, ఇప్పుడు మీకు AI ఏజెంట్లతో ప్రారంభించడానికి తగినంత తెలుసు. ఇది విషయంపై సమగ్రమైన అవలోకనం కాదు; దాని కోసం పూర్తి పుస్తకాలు అంకితం చేయబడ్డాయి, కానీ ఇది మిమ్మల్ని ప్రారంభించడానికి సరిపోతుంది. శుభం కలుగు గాక! + +[నా మరిన్ని పనుల కోసం ఇక్కడ చూడండి](https://cryptodocguy.pro/). diff --git a/public/content/translations/tr/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/tr/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..fb36a8be891 --- /dev/null +++ b/public/content/translations/tr/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "Ethereum üzerinde kendi yapay zeka alım satım aracınızı oluşturun" +description: "Bu öğreticide, basit bir yapay zeka alım satım aracısı yapmayı öğreneceksiniz. Bu aracı, blokzincirden bilgileri okur, bu bilgilere dayanarak bir LLM'den tavsiye ister, LLM'in önerdiği alım satımı gerçekleştirir ve ardından bekleyip tekrar eder." +author: Ori Pomerantz +tags: [ "Yapay Zeka", "alım satım", "aracı", "python" ] +skill: intermediate +published: 2026-02-13 +lang: tr +sidebarDepth: 3 +--- + +Bu öğreticide, basit bir yapay zeka alım satım aracısı oluşturmayı öğreneceksiniz. Bu aracı şu adımları kullanarak çalışır: + +1. Bir jetonun mevcut ve geçmiş fiyatlarının yanı sıra diğer potansiyel olarak ilgili bilgileri okuyun +2. Bu bilgilerle birlikte, nasıl ilgili olabileceğini açıklamak için arka plan bilgilerini de içeren bir sorgu oluşturun +3. Sorguyu gönderin ve tahmini bir fiyat alın +4. Tavsiyeye göre alım satım yapın +5. Bekleyin ve tekrarlayın + +Bu aracı, bilgilerin nasıl okunacağını, kullanılabilir bir yanıt veren bir sorguya nasıl çevrileceğini ve bu yanıtın nasıl kullanılacağını gösterir. Bunların hepsi bir yapay zeka aracısı için gerekli adımlardır. Bu aracı Python'da uygulanmıştır çünkü yapay zekada kullanılan en yaygın dildir. + +## Bunu neden yapmalı? {#why-do-this} + +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ı](/ai-agents), 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 {#tools} + +Bu öğretici, teklifler ve alım satım için [Python](https://www.python.org/), [Web3 kütüphanesi](https://web3py.readthedocs.io/en/stable/) ve [Uniswap v3](https://github.com/Uniswap/v3-periphery) kullanır. + +### Neden Python? {#python} + +Yapay zeka için en yaygın kullanılan dil [Python](https://www.python.org/) 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](https://web3py.readthedocs.io/en/stable/), en yaygın Python Ethereum API'sidir. Kullanımı oldukça kolaydır. + +### Blokzincirde alım satım {#trading-on-blockchain} + +Ethereum'da jeton alım satımı yapmanızı sağlayan [birçok merkeziyetsiz borsa (DEX)](/apps/categories/defi/) vardır. Ancak, [arbitraj](/developers/docs/smart-contracts/composability/#better-user-experience) nedeniyle benzer döviz kurlarına sahip olma eğilimindedirler. + +[Uniswap](https://app.uniswap.org/), 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 {#openai} + +Büyük bir dil modeli için [OpenAI](https://openai.com/) 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 {#step-by-step} + +Geliştirmeyi basitleştirmek için aşamalar halinde ilerliyoruz. Her adım GitHub'da bir daldır. + +### Başlarken {#getting-started} + +UNIX veya Linux ([WSL](https://learn.microsoft.com/en-us/windows/wsl/install) dahil) altında başlamak için adımlar vardır + +1. Henüz sahip değilseniz, [Python](https://www.python.org/downloads/) indirip kurun. + +2. GitHub deposunu klonlayın. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. [`uv`](https://docs.astral.sh/uv/getting-started/installation/) yükleyin. Sisteminizdeki komut farklı olabilir. + + ```sh + pipx install uv + ``` + +4. Kütüphaneleri indirin. + + ```sh + uv sync + ``` + +5. Sanal ortamı etkinleştirin. + + ```sh + source .venv/bin/activate + ``` + +6. Python ve Web3'ün doğru çalıştığını doğrulamak için `python3`'ü çalıştırın ve ona bu programı sağlayın. Bunu `>>>` istemine girebilirsiniz; dosya oluşturmanıza gerek yoktur. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### Blokzincirden okuma {#read-blockchain} + +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. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +Her biri bir zaman damgası, bir fiyat ve varlık (şu anda her zaman `WETH/USDC`) içeren bir `Quote` nesneleri listesi almalısınız. + +İşte satır satır bir açıklama. + +```python +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 +``` + +İhtiyacımız olan kütüphaneleri içe aktarın. Kullanıldıklarında aşağıda açıklanmıştır. + +```python +print = 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. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +Ana ağa ulaşmak için bir URL. [Hizmet olarak düğüm](/developers/docs/nodes-and-clients/nodes-as-a-service/) sağlayıcısından bir tane alabilir veya [Chainlist](https://chainlist.org/chain/1) içinde reklamı yapılanlardan birini kullanabilirsiniz. + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +Bir Ethereum ana ağ 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](/developers/docs/consensus-mechanisms/pos/block-proposal/) 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](https://en.wikipedia.org/wiki/Binary_search) kullanırdık. Ancak, bu amaçlarımız için yeterince yakındır. Geleceği tahmin etmek kesin bir bilim değildir. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +Döngünün boyutu. Döngü başına bir kez teklifleri gözden geçiririz ve bir sonraki döngünün sonundaki değeri tahmin etmeye çalışırız. + +```python +# Okuduğumuz havuzun adresi +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +Teklif değerleri, [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract) 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`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) kullanmak daha iyidir. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +Bunlar, iletişim kurmamız gereken iki sözleşme için [ABI'lerdir](https://docs.soliditylang.org/en/latest/abi-spec.html). Kodu kısa tutmak için yalnızca çağırmamız gereken işlevleri dahil ediyoruz. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +[`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) kütüphanesini başlatın ve bir Ethereum düğümüne bağlanın. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +Bu, Python'da bir veri sınıfı oluşturmanın bir yoludur. [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) veri türü, sözleşmeye bağlanmak için kullanılır. ` (frozen=True)` ifadesine dikkat edin. Python'da [booleanlar](https://en.wikipedia.org/wiki/Boolean_data_type) 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](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages) 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. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +[`Decimal`](https://docs.python.org/3/library/decimal.html) türü, ondalık kesirleri doğru bir şekilde işlemek için kullanılır. + +```python + 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ı. + +```python + assert block <= w3.eth.block_number, "Blok gelecekte" +``` + +Geleceği okuyabilseydik, alım satım için yapay zekaya ihtiyacımız olmazdı. + +```python + 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: `.functions.().call()`. Parametreler EVM fonksiyonunun parametreleri (varsa; burada yok) veya blokzincir davranışını değiştirmek için [adlandırılmış parametreler](https://en.wikipedia.org/wiki/Named_parameter) olabilir. Burada, içinde çalışmak istediğimiz [blok numarasını](/developers/docs/apis/json-rpc/#default-block) belirtmek için `block_identifier`'ı kullanıyoruz. + +Sonuç [bu yapıdır, dizi biçiminde](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). İlk değer, iki jeton arasındaki döviz kurunun bir fonksiyonudur. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +Zincir üstü hesaplamaları azaltmak için Uniswap v3 gerçek değişim faktörünü değil, karekökünü saklar. EVM kayan noktalı matematiği veya kesirleri desteklemediğinden, gerçek değer yerine yanıt price296 olur + +```python + # (token0 başına token1) + return 1/(raw_price * self.decimal_factor) +``` + +Aldığımız ham fiyat, her bir `token1` için aldığımız `token0` sayısıdır. Havuzumuzda `token0` USDC'dir (ABD doları ile aynı değere sahip bir sabit para) ve `token1` [WETH](https://opensea.io/learn/blockchain/what-is-weth)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](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) arasındaki orandır. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +Bu veri sınıfı bir teklifi temsil eder: belirli bir zamanda belirli bir varlığın fiyatı. Bu noktada, `asset` alanı ilgisizdir çünkü tek bir havuz kullanıyoruz ve bu nedenle tek bir varlığa sahibiz. Ancak, daha sonra daha fazla varlık ekleyeceğiz. + +```python +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 + ) +``` + +Bu işlev bir adresi alır ve o adresteki jeton sözleşmesi hakkında bilgi döndürür. Yeni bir [Web3 `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) oluşturmak için, adresi ve ABI'yi `w3.eth.contract`'a sağlarız. + +```python +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) + ) +``` + +Bu işlev, [belirli bir havuz](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol) hakkında ihtiyacımız olan her şeyi döndürür. `f""` sözdizimi [biçimlendirilmiş bir dizedir](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def 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). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +Bir blok numarası belirtilmemişse, en son blok numarası olan `w3.eth.block_number`'ı kullanın. Bu [bir `if` ifadesinin](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement) 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. + +```python + block = w3.eth.get_block(block_number) + price = pool.get_price(block_number) + return Quote( + timestamp=datetime.fromtimestamp(block.timestamp, timezone.utc).isoformat(), + price=price.quantize(Decimal("0.01")), + asset=pool.asset + ) +``` + +İnsanlar ve büyük dil modelleri (LLM'ler) için okunabilir bir biçime biçimlendirmek üzere [`datetime` kütüphanesini](https://docs.python.org/3/library/datetime.html) kullanın. Değeri iki ondalık basamağa yuvarlamak için [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) kullanın. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +Python'da, `list[]` kullanarak yalnızca belirli bir türü içerebilen bir [liste](https://docs.python.org/3/library/stdtypes.html#typesseq-list) tanımlarsınız. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +Python'da bir [`for` döngüsü](https://docs.python.org/3/tutorial/controlflow.html#for-statements) genellikle bir liste üzerinde yinelenir. Teklif bulunacak blok numaraları listesi [`range`](https://docs.python.org/3/library/stdtypes.html#range) fonksiyonundan gelir. + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +Her blok numarası için bir `Quote` nesnesi alın ve bunu `quotes` listesine ekleyin. Ardından bu listeyi döndürün. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +Bu, betiğin ana kodudur. Havuz bilgilerini okuyun, on iki teklif alın ve bunları [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) ile yazdırın. + +### Bir istem oluşturma {#prompt} + +Ardından, bu teklif listesini bir LLM için bir isteme dönüştürmemiz ve beklenen bir gelecek değeri elde etmemiz gerekiyor. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +Çıktı şimdi bir LLM'ye bir istem olacak, şuna benzer: + +``` +Bu teklifler verildiğinde: +Varlık: WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +Varlık: WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +2026-02-02T17:56 zamanında WETH/USDC değerinin ne olmasını beklersiniz? + +Cevabınızı iki ondalık basamağa yuvarlanmış tek bir sayı olarak, +başka metin olmadan verin. +``` + +Burada iki varlık için teklifler olduğuna dikkat edin, `WETH/USDC` ve `WBTC/WETH`. Başka bir varlıktan teklif eklemek tahmin doğruluğunu artırabilir. + +#### Bir istemin nasıl göründüğü {#prompt-explanation} + +Bu istem, LLM istemlerinde oldukça yaygın olan üç bölüm içerir. + +1. Bilgi. LLM'ler eğitimlerinden çok fazla bilgiye sahiptir, ancak genellikle en güncel bilgilere sahip değillerdir. En son teklifleri burada almamızın nedeni budur. Bir isteme bilgi eklemeye [geri getirme artırılmış üretim (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation) denir. + +2. Asıl soru. Bilmek istediğimiz şey bu. + +3. Çıktı biçimlendirme talimatları. Normalde, bir LLM bize bu tahmine nasıl ulaştığının bir açıklamasıyla birlikte bir tahmin verir. Bu insanlar için daha iyidir, ancak bir bilgisayar programının yalnızca sonuca ihtiyacı vardır. + +#### Kod açıklaması {#prompt-code} + +İşte yeni kod. + +```python +from datetime import datetime, timezone, timedelta +``` + +LLM'e bir tahmin istediğimiz zamanı sağlamamız gerekiyor. Gelecekte "n dakika/saat/gün" zamanını elde etmek için [`timedelta` sınıfını](https://docs.python.org/3/library/datetime.html#datetime.timedelta) kullanırız. + +```python +# Okuduğumuz havuzların adresleri +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +Okumamız gereken iki havuzumuz var. + +```python +@dataclass(frozen=True) +class PoolInfo: + . + . + . + reverse: bool = False + + def get_price(self, block: int) -> Decimal: + assert block <= w3.eth.block_number, "Blok gelecekte" + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (token0 başına token1) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +WETH/USDC havuzunda, bir adet `token1` (WETH) satın almak için kaç adet `token0` (USDC) gerektiğini bilmek istiyoruz. WETH/WBTC havuzunda, bir adet `token0` (WBTC, sarılmış Bitcoin'dir) satın almak için kaç adet `token1` (WETH) gerektiğini bilmek istiyoruz. Havuzun oranının tersine çevrilmesi gerekip gerekmediğini izlememiz gerekiyor. + +```python +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 + ) +``` + +Bir havuzun tersine çevrilmesi gerekip gerekmediğini bilmek için bunu `read_pool`'a girdi olarak almamız gerekiyor. Ayrıca, varlık sembolünün doğru bir şekilde ayarlanması gerekir. + +` if else ` söz dizimi, C türevi bir dilde ` ?` şeklinde olan [üçlü koşullu operatörün](https://en.wikipedia.org/wiki/Ternary_conditional_operator) Python eşdeğeridir. ` : `. + +```python +def format_quotes(quotes: list[Quote]) -> str: + result = f"Varlık: {quotes[0].asset}\n" + for quote in quotes: + result += f"\t{quote.timestamp[0:16]} {quote.price.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)}\n" + return result +``` + +Bu işlev, hepsinin aynı varlığa uygulandığını varsayarak bir `Quote` nesne listesini biçimlendiren bir dize oluşturur. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +Python'da [çok satırlı dize sabitleri](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) `"""` .... şeklinde yazılır. `"""`. + +```python +Bu teklifler verildiğinde: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +Burada, her teklif listesi için `format_quotes` ile bir dize oluşturmak için [MapReduce](https://en.wikipedia.org/wiki/MapReduce) desenini kullanırız, ardından bunları istemde kullanılmak üzere tek bir dizede birleştiririz. + +```python +{expected_time} zamanında {asset} için değerin ne olmasını beklersiniz? + +Cevabınızı iki ondalık basamağa yuvarlanmış tek bir sayı olarak, +başka bir metin olmadan verin. + """ +``` + +İstemin geri kalanı beklendiği gibidir. + +```python +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 +) +``` + +İki havuzu gözden geçirin ve her ikisinden de teklifler alın. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(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 {#interface-llm} + +Ardından, gerçek bir LLM'yi sorgularız ve beklenen bir gelecek değeri alırız. Bu programı OpenAI kullanarak yazdım, bu yüzden farklı bir sağlayıcı kullanmak isterseniz, onu ayarlamanız gerekecektir. + +1. Bir [OpenAI hesabı](https://auth.openai.com/create-account) edinin + +2. [Hesaba para yatırın](https://platform.openai.com/settings/organization/billing/overview)—bu yazının yazıldığı sırada asgari tutar 5$'dır + +3. [Bir API anahtarı oluşturun](https://platform.openai.com/settings/organization/api-keys) + +4. Komut satırında, programınızın kullanabilmesi için API anahtarını dışa aktarın + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. Ajanı kullanıma alın ve çalıştırın + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +İşte yeni kod. + +```python +from openai import OpenAI + +open_ai = OpenAI() # İstemci, OPENAI_API_KEY ortam değişkenini okur +``` + +OpenAI API'sini içe aktarın ve başlatın. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +Yanıtı oluşturmak için OpenAI API'sini (`open_ai.chat.completions.create`) çağırın. + +```python +expected_price = Decimal(response.choices[0].message.content.strip()) +current_price = wethusdc_quotes[-1].price + +print ("Mevcut fiyat:", wethusdc_quotes[-1].price) +print(f"{future_time} zamanında, beklenen fiyat: {expected_price} USD") + +if (expected_price > current_price): + print(f"Al, fiyatın {expected_price - current_price} USD artmasını bekliyorum") +else: + print(f"Sat, fiyatın {current_price - expected_price} USD düşmesini bekliyorum") +``` + +Fiyatı çıktılayın ve bir al veya sat tavsiyesi verin. + +#### Tahminleri test etme {#testing-the-predictions} + +Artık tahminler üretebildiğimize göre, yararlı tahminler üretip üretmediğimizi değerlendirmek için geçmiş verileri de kullanabiliriz. + +```sh +uv run test-predictor.py +``` + +Beklenen sonuç şuna benzer: + +``` +2026-01-05T19:50 için tahmin: tahmin edilen 3138.93 USD, gerçek 3218.92 USD, hata 79.99 USD +2026-01-06T19:56 için tahmin: tahmin edilen 3243.39 USD, gerçek 3221.08 USD, hata 22.31 USD +2026-01-07T20:02 için tahmin: tahmin edilen 3223.24 USD, gerçek 3146.89 USD, hata 76.35 USD +2026-01-08T20:11 için tahmin: tahmin edilen 3150.47 USD, gerçek 3092.04 USD, hata 58.43 USD +. +. +. +2026-01-31T22:33 için tahmin: tahmin edilen 2637.73 USD, gerçek 2417.77 USD, hata 219.96 USD +2026-02-01T22:41 için tahmin: tahmin edilen 2381.70 USD, gerçek 2318.84 USD, hata 62.86 USD +2026-02-02T22:49 için tahmin: tahmin edilen 2234.91 USD, gerçek 2349.28 USD, hata 114.37 USD +29 tahmin üzerinden ortalama tahmin hatası: 83.87103448275862068965517241 USD +Tavsiye başına ortalama değişim: 4.787931034482758620689655172 USD +Değişikliklerin standart sapması: 104.42 USD +Kârlı günler: %51.72 +Zararlı günler: %48.28 +``` + +Test edicinin çoğu aracı ile aynıdır, ancak burada yeni veya değiştirilmiş kısımlar bulunmaktadır. + +```python +CYCLES_FOR_TEST = 40 # Geriye dönük test için kaç döngü test edeceğimiz + +# Çok sayıda teklif al +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 +) +``` + +`CYCLES_FOR_TEST` (burada 40 olarak belirtilmiştir) gün geriye bakıyoruz. + +```python +# Tahminler oluşturun ve bunları gerçek geçmişe karşı kontrol edin + +total_error = Decimal(0) +changes = [] +``` + +İ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_. + +```python +for 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. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +Aracının kullandığı sayıyla aynı sayıda örnek almak için [dilimleri](https://www.w3schools.com/python/ref_func_slice.asp) kullanın. Buradan bir sonraki segmente kadar olan kod, aracıda sahip olduğumuz tahmin alma koduyla aynıdır. + +```python + 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 +``` + +Tahmini fiyatı, gerçek fiyatı ve tahmin anındaki fiyatı alın. Tavsiyenin alım mı yoksa satım mı olduğunu belirlemek için tahmin anındaki fiyata ihtiyacımız var. + +```python + error = abs(predicted_price - real_price) + total_error += error + 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. + +```python + recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell' + price_increase = real_price - prediction_time_price + changes.append(price_increase if recomended_action == 'buy' else -price_increase) +``` + +`changes` için, bir ETH almanın veya satmanın parasal etkisini istiyoruz. Bu 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. + +```python +print (f"{len(wethusdc_quotes)-CYCLES_BACK} tahmin üzerinden ortalama tahmin hatası: {total_error / Decimal(len(wethusdc_quotes)-CYCLES_BACK)} USD") + +length_changes = Decimal(len(changes)) +mean_change = sum(changes, Decimal(0)) / length_changes +print (f"Tavsiye başına ortalama değişim: {mean_change} USD") +var = sum((x - mean_change) ** 2 for x in changes) / length_changes +print (f"Değişikliklerin standart sapması: {var.sqrt().quantize(Decimal("0.01"))} USD") +``` + +Sonuçları raporlayın. + +```python +print (f"Kârlı günler: {len(list(filter(lambda x: x > 0, changes)))/length_changes:.2%}") +print (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`](https://www.w3schools.com/python/ref_func_filter.asp) kullanın. Sonuç bir filtre nesnesidir, uzunluğunu almak için bunu bir listeye dönüştürmemiz gerekir. + +### İşlemleri gönderme {#submit-txn} + +Şimdi gerçekten işlemleri göndermemiz gerekiyor. Ancak, sistem kanıtlanmadan önce bu noktada gerçek para harcamak istemiyorum. Bunun yerine, ana ağın yerel bir çatalını oluşturacağız ve o ağda "alım satım" yapacağız. + +Yerel bir çatal oluşturma ve alım satımı etkinleştirme adımları şunlardır. + +1. [Foundry](https://getfoundry.sh/introduction/installation) yükleyin + +2. [`anvil`](https://getfoundry.sh/anvil/overview) başlatın + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil`, Foundry için varsayılan URL olan http://localhost:8545 adresinde dinliyor, bu nedenle blokzinciri değiştirmek için kullandığımız [`cast` komutu](https://getfoundry.sh/cast/overview) için URL belirtmemize gerek yok. + +3. `anvil`'de çalıştırıldığında, ETH'ye sahip on test hesabı vardır—ilk hesap için ortam değişkenlerini ayarlayın + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. Bunlar kullanmamız gereken sözleşmeler. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol), 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. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. Test hesaplarının her birinde 10.000 ETH bulunur. Alım satım için 1000 WETH elde etmek üzere 1000 ETH sarmalamak için WETH sözleşmesini kullanın. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. 500 WETH'yi USDC ile takas etmek için `SwapRouter`'ı kullanın. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + `approve` çağrısı, `SwapRouter`'ın jetonlarımızın bir kısmını harcamasına izin veren bir ödenek oluşturur. Sözleşmeler olayları izleyemez, bu nedenle jetonları doğrudan `SwapRouter` sözleşmesine aktarırsak, ödendiğini bilemez. Bunun yerine, `SwapRouter` sözleşmesinin belirli bir miktarı harcamasına izin veririz ve ardından `SwapRouter` bunu yapar. Bu, `SwapRouter` tarafından çağrılan bir işlev aracılığıyla yapılır, böylece başarılı olup olmadığını bilir. + +7. Her iki jetondan da yeterli miktarda olduğunu doğrulayın. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +Artık WETH ve USDC'ye sahip olduğumuza göre, aracıyı gerçekten çalıştırabiliriz. + +```sh +git checkout 05-trade +uv run agent.py +``` + +Çıktı şuna benzer olacaktır: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +Mevcut fiyat: 1843.16 +2026-02-06T23:07 zamanında beklenen fiyat: 1724.41 USD +Alım satım öncesi hesap bakiyeleri: +USDC Bakiyesi: 927301.578272 +WETH Bakiyesi: 500 +Sat, fiyatın 118.75 USD düşmesini bekliyorum +Onay işlemi gönderildi: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +Onay işlemi çıkarıldı. +Satış işlemi gönderildi: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +Satış işlemi çıkarıldı. +Alım satım sonrası hesap bakiyeleri: +USDC Bakiyesi: 929143.797116 +WETH Bakiyesi: 499 +``` + +Gerçekten kullanmak için birkaç küçük değişiklik yapmanız gerekir. + +- 14. satırda, `MAINNET_URL`'yi `https://eth.drpc.org` gibi gerçek bir erişim noktasına değiştirin +- 28. satırda, `PRIVATE_KEY`'i kendi özel anahtarınızla değiştirin +- Çok zengin değilseniz ve kanıtlanmamış bir aracı için her gün 1 ETH alıp satamıyorsanız, `WETH_TRADE_AMOUNT`'ı azaltmak için 29. satırı değiştirmek isteyebilirsiniz + +#### Kod açıklaması {#trading-code} + +İşte yeni kod. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +4. adımda kullandığımız aynı değişkenler. + +```python +WETH_TRADE_AMOUNT=1 +``` + +Alım satım yapılacak miktar. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +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. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +`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. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +[`account`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) ve `SwapRouter` sözleşmesi için Web3 tanımları. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +İşlem parametreleri. Burada bir fonksiyona ihtiyacımız var çünkü [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) her seferinde değişmelidir. + +```python +def approve_token(contract: Contract, amount: int): +``` + +`SwapRouter` için bir jeton ödeneğini onaylayın. + +```python + txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params()) + signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY) + tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction) +``` + +Web3'te bu şekilde bir işlem gönderiyoruz. İlk olarak, işlemi oluşturmak için [`Sözleşme` nesnesini](https://web3py.readthedocs.io/en/stable/web3.contract.html) kullanırız. Ardından, işlemi `PRIVATE_KEY` kullanarak imzalamak için [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) kullanırız. Son olarak, işlemi göndermek için [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) kullanırız. + +```python + print(f"Onay işlemi gönderildi: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Onay işlemi çıkarıldı.") +``` + +[`w3.eth.wait_for_transaction_receipt`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt), işlem çıkarılana kadar bekler. Gerekirse makbuzu döndürür. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +Bunlar WETH satarken kullanılan parametrelerdir. + +```python +def make_buy_params(quote: Quote) -> dict: + return { + "path": USDC_TO_WETH, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals, + "amountOutMinimum": 0, + } +``` + +`SELL_PARAMS`'ın aksine, satın alma parametreleri değişebilir. Giriş tutarı, `quote`'da mevcut olan 1 WETH'nin maliyetidir. + +```python +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"Alım işlemi gönderildi: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Alım işlemi çıkarıldı.") + + +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"Satış işlemi gönderildi: {tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("Satış işlemi çıkarıldı.") +``` + +`buy()` ve `sell()` fonksiyonları neredeyse aynıdır. Önce `SwapRouter` için yeterli bir ödenek onaylarız ve sonra onu doğru yol ve miktar ile çağırırız. + +```python +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} Bakiye: {Decimal(token0_balance) / Decimal(10 ** wethusdc_pool.token0.decimals)}") + 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. + +```python +print("Alım satım öncesi hesap bakiyeleri:") +balances() + +if (expected_price > current_price): + print(f"Al, fiyatın {expected_price - current_price} USD artmasını bekliyorum") + buy(wethusdc_quotes[-1]) +else: + print(f"Sat, fiyatın {current_price - expected_price} USD düşmesini bekliyorum") + sell() + +print("Alım satım sonrası hesap bakiyeleri:") +balances() +``` + +Bu aracı şu anda yalnızca bir kez çalışır. Ancak, [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) üzerinden çalıştırarak veya 368-400 satırlarını bir döngüye alıp [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) kullanarak bir sonraki döngü zamanı gelene kadar bekleyerek sürekli çalışmasını sağlayabilirsiniz. + +## Olası iyileştirmeler {#improvements} + +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 {#smart-trading} + +Aracının ne yapacağına karar verirken göz ardı ettiği iki önemli gerçek var. + +- _Beklenen değişimin büyüklüğü_. Aracı, düşüşün büyüklüğüne bakılmaksızın, fiyatın düşmesi bekleniyorsa sabit bir miktarda `WETH` satar. + Tartışmalı olarak, küçük değişiklikleri görmezden gelmek ve fiyatın ne kadar düşmesini beklediğimize göre satış yapmak daha iyi olurdu. +- _Mevcut portföy_. Portföyünüzün %10'u WETH'de ise ve fiyatın artacağını düşünüyorsanız, daha fazla satın almak muhtemelen mantıklıdır. Ancak portföyünüzün %90'ı WETH'de ise, yeterince riske maruz kalmış olabilirsiniz ve daha fazla satın almanıza gerek yoktur. Fiyatın düşmesini bekliyorsanız, tam tersi geçerlidir. + +### Ticaret stratejinizi gizli tutmak isterseniz ne olur? {#secret} + +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](https://lmstudio.ai/) kullanarak yerel olarak bir LLM çalıştırabilirsiniz. + +### Yapay zeka botundan yapay zeka aracısına {#bot-to-agent} + +Bunun [bir yapay zeka botu değil, bir yapay zeka aracısı](/ai-agents/#ai-agents-vs-ai-bots) 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ı {#slippage-protection} + +Şu anda [kayma koruması](https://uniswapv3book.com/milestone_3/slippage-protection.html) 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`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325) dosyasının 325 ve 334. satırlarında bir `amountOutMinimum` değeri belirtin. + +## Sonuç {#conclusion} + +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](https://cryptodocguy.pro/). diff --git a/public/content/translations/uk/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/uk/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..7d08fbbc285 --- /dev/null +++ b/public/content/translations/uk/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,652 @@ +--- +title: "Створіть власного торгового AI-агента на Ethereum" +description: "У цьому посібнику ви дізнаєтеся, як створити простого торгового AI-агента. Цей агент зчитує інформацію з блокчейну, запитує в LLM рекомендацію на основі цієї інформації, виконує рекомендовану LLM угоду, а потім чекає й повторює." +author: Ori Pomerantz +tags: [ "ШІ", "торгівля", "агент", "python" ] +skill: intermediate +published: 2026-02-13 +lang: uk +sidebarDepth: 3 +--- + +У цьому посібнику ви дізнаєтеся, як створити простого торгового AI-агента. Цей агент працює за такими кроками: + +1. Зчитування поточних і минулих цін токена, а також іншої потенційно релевантної інформації +2. Створення запиту з цією інформацією, а також довідкової інформації для пояснення її можливої релевантності +3. Надсилання запиту й отримання прогнозованої ціни у відповідь +4. Здійснення угоди на основі рекомендації +5. Очікування та повторення + +Цей агент демонструє, як зчитувати інформацію, перетворювати її на запит, що дає корисну відповідь, і використовувати цю відповідь. Усе це — кроки, необхідні для AI-агента. Цей агент реалізовано на Python, оскільки це найпоширеніша мова, що використовується в ШІ. + +## Навіщо це робити? {#why-do-this} + +Автоматизовані торгові агенти дозволяють розробникам вибирати й виконувати торгову стратегію. [AI-агенти](/ai-agents) уможливлюють складніші та динамічніші торгові стратегії, потенційно використовуючи інформацію й алгоритми, які розробник навіть не розглядав для використання. + +## Інструменти {#tools} + +У цьому посібнику для отримання котирувань і торгівлі використовуються [Python](https://www.python.org/), [бібліотека Web3](https://web3py.readthedocs.io/en/stable/) та [Uniswap v3](https://github.com/Uniswap/v3-periphery). + +### Чому Python? {#python} + +Найпоширенішою мовою для ШІ є [Python](https://www.python.org/), тому ми використовуємо її тут. Не хвилюйтеся, якщо ви не знаєте Python. Мова дуже зрозуміла, і я поясню, що саме вона робить. + +[Бібліотека Web3](https://web3py.readthedocs.io/en/stable/) — це найпоширеніший API Python для Ethereum. Вона досить проста у використанні. + +### Торгівля на блокчейні {#trading-on-blockchain} + +Існує [багато розподілених бірж (DEX)](/apps/categories/defi/), які дозволяють торгувати токенами на Ethereum. Однак вони, як правило, мають схожі обмінні курси через [арбітраж](/developers/docs/smart-contracts/composability/#better-user-experience). + +[Uniswap](https://app.uniswap.org/) — це широко використовувана DEX, яку ми можемо використовувати як для отримання котирувань (щоб побачити відносні вартості токенів), так і для угод. + +### OpenAI {#openai} + +Для роботи з великою мовною моделлю я вирішив почати з [OpenAI](https://openai.com/). Щоб запустити програму з цього посібника, вам потрібно буде сплатити за доступ до API. Мінімального платежу в 5 доларів США більш ніж достатньо. + +## Розробка, крок за кроком {#step-by-step} + +Щоб спростити розробку, ми будемо діяти поетапно. Кожен крок — це гілка в GitHub. + +### Початок роботи {#getting-started} + +Нижче наведено кроки для початку роботи в UNIX або Linux (включно з [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) + +1. Якщо у вас ще немає [Python](https://www.python.org/downloads/), завантажте й установіть його. + +2. Клонуйте репозиторій GitHub. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started\ncd 260215-ai-agent + ``` + +3. Установіть [`uv`](https://docs.astral.sh/uv/getting-started/installation/). Команда у вашій системі може відрізнятися. + + ```sh + pipx install uv + ``` + +4. Завантажте бібліотеки. + + ```sh + uv sync + ``` + +5. Активуйте віртуальне середовище. + + ```sh + source .venv/bin/activate + ``` + +6. Щоб перевірити, чи Python і Web3 працюють правильно, запустіть `python3` і надайте йому цю програму. Ви можете ввести її в командному рядку `>>>`; створювати файл не потрібно. + + ```python + from web3 import Web3\nMAINNET_URL = \"https://eth.drpc.org\"\nw3 = Web3(Web3.HTTPProvider(MAINNET_URL))\nw3.eth.block_number\nquit() + ``` + +### Читання з блокчейну {#read-blockchain} + +Наступний крок — читання з блокчейну. Для цього вам потрібно перейти до гілки `02-read-quote`, а потім використати `uv` для запуску програми. + +```sh +git checkout 02-read-quote\nuv run agent.py +``` + +Ви повинні отримати список об’єктів `Quote`, кожен з яких містить часову мітку, ціну та актив (наразі це завжди `WETH/USDC`). + +Ось пояснення рядок за рядком. + +```python +from web3 import Web3\nfrom web3.contract import Contract\nfrom decimal import Decimal, ROUND_HALF_UP\nfrom dataclasses import dataclass\nfrom datetime import datetime, timezone\nfrom pprint import pprint\nimport time\nimport functools\nimport sys +``` + +Імпортуйте бібліотеки, які нам потрібні. Вони пояснюються нижче, коли використовуються. + +```python +print = functools.partial(print, flush=True) +``` + +Замінює `print` Python на версію, яка завжди негайно скидає вивід. Це корисно в довготривалому сценарії, оскільки ми не хочемо чекати оновлень статусу або виводу для налагодження. + +```python +MAINNET_URL = \"https://eth.drpc.org\" +``` + +URL-адреса для доступу до основної мережі. Ви можете отримати її в [постачальника вузлів як послуги](/developers/docs/nodes-and-clients/nodes-as-a-service/) або використати одну з тих, що рекламуються в [Chainlist](https://chainlist.org/chain/1). + +```python +BLOCK_TIME_SECONDS = 12\nMINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS)\nHOUR_BLOCKS = MINUTE_BLOCKS * 60\nDAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +Блок в основній мережі Ethereum зазвичай з’являється кожні дванадцять секунд, тож це кількість блоків, які, як ми очікуємо, з’являться за певний період часу. Зауважте, що це не точне число. Коли [пропонувач блоку](/developers/docs/consensus-mechanisms/pos/block-proposal/) не працює, цей блок пропускається, і час до наступного блоку становить 24 секунди. Якби ми хотіли отримати точний блок для часової мітки, ми б використали [двійковий пошук](https://en.wikipedia.org/wiki/Binary_search). Однак для наших цілей цього достатньо. Прогнозування майбутнього — не точна наука. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +Розмір циклу. Ми переглядаємо котирування раз за цикл і намагаємося оцінити вартість наприкінці наступного циклу. + +```python +# Адреса пулу, з якого ми читаємо\nWETHUSDC_ADDRESS = Web3.to_checksum_address(\"0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640\") +``` + +Значення котирувань беруться з пулу Uniswap 3 USDC/WETH за адресою [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract). Ця адреса вже має формат контрольної суми, але краще використовувати [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address), щоб зробити код придатним для повторного використання. + +```python +POOL_ABI = [\n { \"name\": \"slot0\", ... },\n { \"name\": \"token0\", ... },\n { \"name\": \"token1\", ... },\n]\n\nERC20_ABI = [\n { \"name\": \"symbol\", ... },\n { \"name\": \"decimals\", ... }\n] +``` + +Це [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html) для двох контрактів, з якими нам потрібно взаємодіяти. Щоб код був стислим, ми включаємо лише ті функції, які нам потрібно викликати. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +Ініціюйте бібліотеку [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) і підключіться до вузла Ethereum. + +```python +@dataclass(frozen=True)\nclass ERC20Token:\n address: str\n symbol: str\n decimals: int\n contract: Contract +``` + +Це один зі способів створення класу даних у Python. Тип даних [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) використовується для підключення до контракту. Зверніть увагу на `(frozen=True)`. У Python [булеві значення](https://en.wikipedia.org/wiki/Boolean_data_type) визначаються як `True` або `False` з великої літери. Цей клас даних є `frozen` (замороженим), тобто його поля не можна змінювати. + +Зверніть увагу на відступ. На відміну від [мов, що походять від C](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages), Python використовує відступи для позначення блоків. Інтерпретатор Python знає, що наступне визначення не є частиною цього класу даних, оскільки воно не починається з того ж відступу, що й поля класу даних. + +```python +@dataclass(frozen=True)\nclass PoolInfo:\n address: str\n token0: ERC20Token\n token1: ERC20Token\n contract: Contract\n asset: str\n decimal_factor: Decimal = 1 +``` + +Тип [`Decimal`](https://docs.python.org/3/library/decimal.html) використовується для точної роботи з десятковими дробами. + +```python + def get_price(self, block: int) -> Decimal: +``` + +Це спосіб визначення функції в Python. Визначення має відступ, щоб показати, що воно все ще є частиною `PoolInfo`. + +У функції, яка є частиною класу даних, першим параметром завжди є `self` — екземпляр класу даних, який її викликав. Тут є ще один параметр — номер блоку. + +```python + assert block <= w3.eth.block_number, \"Блок із майбутнього\" +``` + +Якби ми могли читати майбутнє, нам не знадобився б ШІ для торгівлі. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +Синтаксис виклику функції на EVM з Web3 такий: `<об'єкт контракту>.functions.<назва функції>().call(<параметри>). Параметрами можуть бути параметри функції EVM (якщо вони є; тут їх немає) або [іменовані параметри](https://en.wikipedia.org/wiki/Named_parameter) для зміни поведінки блокчейну. Тут ми використовуємо один з них, `block_identifier`, щоб указати [номер блоку](/developers/docs/apis/json-rpc/#default-block), у якому ми хочемо виконати операцію. + +Результатом є [ця структура у формі масиву](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). Перше значення — це функція обмінного курсу між двома токенами. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +Щоб зменшити кількість обчислень в мережі, Uniswap v3 зберігає не фактичний коефіцієнт обміну, а його квадратний корінь. Оскільки EVM не підтримує математику з рухомою комою або дроби, замість фактичного значення відповідь — ціна296 + +```python + # (токен1 за токен0)\n return 1/(raw_price * self.decimal_factor) +``` + +Необроблена ціна, яку ми отримуємо, — це кількість `token0`, яку ми отримуємо за кожен `token1`. У нашому пулі `token0` — це USDC (стейблкоїн з такою ж вартістю, що й долар США), а `token1` — [WETH](https://opensea.io/learn/blockchain/what-is-weth). Значення, яке нам насправді потрібне, — це кількість доларів за WETH, а не обернене значення. + +Десятковий коефіцієнт — це співвідношення між [десятковими коефіцієнтами](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) для двох токенів. + +```python +@dataclass(frozen=True)\nclass Quote:\n timestamp: str\n price: Decimal\n asset: str +``` + +Цей клас даних представляє котирування: ціну певного активу в певний момент часу. На цьому етапі поле `asset` не має значення, оскільки ми використовуємо один пул і, отже, маємо один актив. Однак пізніше ми додамо більше активів. + +```python +def read_token(address: str) -> ERC20Token:\n token = w3.eth.contract(address=address, abi=ERC20_ABI)\n symbol = token.functions.symbol().call()\n decimals = token.functions.decimals().call()\n\n return ERC20Token(\n address=address,\n symbol=symbol,\n decimals=decimals,\n contract=token\n ) +``` + +Ця функція приймає адресу й повертає інформацію про контракт токена за цією адресою. Щоб створити новий [`Contract` Web3](https://web3py.readthedocs.io/en/stable/web3.contract.html), ми надаємо адресу й ABI для `w3.eth.contract`. + +```python +def read_pool(address: str) -> PoolInfo:\n pool_contract = w3.eth.contract(address=address, abi=POOL_ABI)\n token0Address = pool_contract.functions.token0().call()\n token1Address = pool_contract.functions.token1().call()\n token0 = read_token(token0Address)\n token1 = read_token(token1Address)\n\n return PoolInfo(\n address=address,\n asset=f\"{token1.symbol}/{token0.symbol}\",\n token0=token0,\n token1=token1,\n contract=pool_contract,\n decimal_factor=Decimal(10) ** Decimal(token0.decimals - token1.decimals)\n ) +``` + +Ця функція повертає все, що нам потрібно про [конкретний пул](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol). Синтаксис `f\"<рядок>\"` — це [форматований рядок](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +Отримати об’єкт `Quote`. Значенням за замовчуванням для `block_number` є `None` (немає значення). + +```python + if block_number is None:\n block_number = w3.eth.block_number +``` + +Якщо номер блоку не вказано, використовуйте `w3.eth.block_number`, що є номером останнього блоку. Це синтаксис [інструкції `if`](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement). + +Може здатися, що було б краще просто встановити значення за замовчуванням на `w3.eth.block_number`, але це не спрацює належним чином, оскільки це буде номер блоку на момент визначення функції. У довготривалому агенті це було б проблемою. + +```python + block = w3.eth.get_block(block_number)\n price = pool.get_price(block_number)\n return Quote(\n timestamp=datetime.fromtimestamp(block.timestamp, timezone.utc).isoformat(),\n price=price.quantize(Decimal(\"0.01\")),\n asset=pool.asset\n ) +``` + +Використовуйте [бібліотеку `datetime`](https://docs.python.org/3/library/datetime.html), щоб відформатувати його у формат, читабельний для людей і великих мовних моделей (LLM). Використовуйте [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize), щоб округлити значення до двох знаків після коми. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +У Python ви визначаєте [список](https://docs.python.org/3/library/stdtypes.html#typesseq-list), який може містити лише певний тип, за допомогою `list[<тип>]`. + +```python + quotes = []\n for block in range(start_block, end_block + 1, step): +``` + +У Python [цикл `for`](https://docs.python.org/3/tutorial/controlflow.html#for-statements) зазвичай ітерує по списку. Список номерів блоків для пошуку котирувань походить від [`range`](https://docs.python.org/3/library/stdtypes.html#range). + +```python + quote = get_quote(pool, block)\n quotes.append(quote)\n return quotes +``` + +Для кожного номера блоку отримайте об’єкт `Quote` і додайте його до списку `quotes`. Потім поверніть цей список. + +```python +pool = read_pool(WETHUSDC_ADDRESS)\nquotes = get_quotes(\n pool,\n w3.eth.block_number - 12*CYCLE_BLOCKS,\n w3.eth.block_number,\n CYCLE_BLOCKS\n)\n\npprint(quotes) +``` + +Це основний код скрипту. Прочитайте інформацію про пул, отримайте дванадцять котирувань і виведіть їх за допомогою [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint). + +### Створення запиту {#prompt} + +Далі нам потрібно перетворити цей список котирувань на запит для LLM та отримати очікуване майбутнє значення. + +```sh +git checkout 03-create-prompt\nuv run agent.py +``` + +Тепер вивід буде запитом до LLM, схожим на: + +``` +З огляду на ці котирування:\nАктив: WETH/USDC\n 2026-01-20T16:34 3016.21\n .\n .\n .\n 2026-02-01T17:49 2299.10\n\nАктив: WBTC/WETH\n 2026-01-20T16:34 29.84\n .\n .\n .\n 2026-02-01T17:50 33.46\n\n\nЯке значення для WETH/USDC ви очікуєте на момент 2026-02-02T17:56?\n\nНадайте відповідь у вигляді одного числа, заокругленого до двох знаків після коми,\nбез будь-якого іншого тексту. +``` + +Зверніть увагу, що тут є котирування для двох активів, `WETH/USDC` і `WBTC/WETH`. Додавання котирувань з іншого активу може підвищити точність прогнозу. + +#### Як виглядає запит {#prompt-explanation} + +Цей запит містить три розділи, які досить поширені в запитах до LLM. + +1. Інформація. LLM мають багато інформації зі свого навчання, але зазвичай не мають найновішої. Ось чому нам потрібно отримувати тут найновіші котирування. Додавання інформації до запиту називається [генерація, доповнена пошуком (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +2. Власне запитання. Це те, що ми хочемо дізнатися. + +3. Інструкції з форматування виводу. Зазвичай LLM дає нам оцінку з поясненням того, як вона її отримала. Це краще для людей, але комп’ютерній програмі потрібен лише кінцевий результат. + +#### Пояснення коду {#prompt-code} + +Ось новий код. + +```python +from datetime import datetime, timezone, timedelta +``` + +Нам потрібно надати LLM час, для якого ми хочемо отримати оцінку. Щоб отримати час «n хвилин/годин/днів» у майбутньому, ми використовуємо [клас `timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta). + +```python +# Адреси пулів, які ми читаємо\nWETHUSDC_ADDRESS = Web3.to_checksum_address(\"0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640\")\nWETHWBTC_ADDRESS = Web3.to_checksum_address(\"0xCBCdF9626bC03E24f779434178A73a0B4bad62eD\") +``` + +У нас є два пули, які нам потрібно прочитати. + +```python +@dataclass(frozen=True)\nclass PoolInfo:\n .\n .\n .\n reverse: bool = False\n\n def get_price(self, block: int) -> Decimal:\n assert block <= w3.eth.block_number, \"Блок із майбутнього\"\n sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0])\n raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (токен1 за токен0)\n if self.reverse:\n return 1/(raw_price * self.decimal_factor)\n else:\n return raw_price * self.decimal_factor +``` + +У пулі WETH/USDC ми хочемо знати, скільки `token0` (USDC) нам потрібно, щоб купити один `token1` (WETH). У пулі WETH/WBTC ми хочемо знати, скільки `token1` (WETH) нам потрібно, щоб купити один `token0` (WBTC, який є обгорнутим Bitcoin). Нам потрібно відстежувати, чи потрібно змінювати співвідношення пулу на обернене. + +```python +def read_pool(address: str, reverse: bool = False) -> PoolInfo:\n .\n .\n .\n\n return PoolInfo(\n .\n .\n .\n\n asset= f\"{token1.symbol}/{token0.symbol}\" if reverse else f\"{token0.symbol}/{token1.symbol}\",\n reverse=reverse\n ) +``` + +Щоб знати, чи потрібно змінювати співвідношення пулу на обернене, ми отримуємо це як вхідні дані для `read_pool`. Крім того, символ активу має бути налаштований правильно. + +Синтаксис ` if else ` є еквівалентом Python [тернарного умовного оператора](https://en.wikipedia.org/wiki/Ternary_conditional_operator), який у мові, що походить від C, виглядав би як ` ? : `. + +```python +def format_quotes(quotes: list[Quote]) -> str:\n result = f\"Актив: {quotes[0].asset}\n\"\n for quote in quotes:\n result += f\"\t{quote.timestamp[0:16]} {quote.price.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)}\n\"\n return result +``` + +Ця функція створює рядок, який форматує список об’єктів `Quote`, припускаючи, що всі вони застосовуються до одного активу. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str:\n return f\"\"\" +``` + +У Python [багаторядкові рядкові літерали](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) записуються як `\"\"\"`.... `\"\"\"`. + +```python +З огляду на ці котирування:\n{\n functools.reduce(lambda acc, q: acc + '\n' + q,\n map(lambda q: format_quotes(q), quotes))\n} +``` + +Тут ми використовуємо патерн [MapReduce](https://en.wikipedia.org/wiki/MapReduce), щоб згенерувати рядок для кожного списку котирувань за допомогою `format_quotes`, а потім об’єднати їх в один рядок для використання в запиті. + +```python +Яке значення для {asset} ви очікуєте на момент {expected_time}?\n\nНадайте відповідь у вигляді одного числа, заокругленого до двох знаків після коми,\nбез будь-якого іншого тексту.\n \"\"\" +``` + +Решта запиту така, як і очікувалося. + +```python +wethusdc_pool = read_pool(WETHUSDC_ADDRESS, True)\nwethusdc_quotes = get_quotes(\n wethusdc_pool,\n w3.eth.block_number - 12*CYCLE_BLOCKS,\n w3.eth.block_number,\n CYCLE_BLOCKS,\n)\n\nwethwbtc_pool = read_pool(WETHWBTC_ADDRESS)\nwethwbtc_quotes = get_quotes(\n wethwbtc_pool,\n w3.eth.block_number - 12*CYCLE_BLOCKS,\n w3.eth.block_number,\n CYCLE_BLOCKS\n) +``` + +Перегляньте два пули й отримайте котирування з обох. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16]\n\nprint(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +Визначте майбутній момент часу, для якого ми хочемо отримати оцінку, і створіть запит. + +### Взаємодія з LLM {#interface-llm} + +Далі ми надсилаємо запит до фактичної LLM і отримуємо очікуване майбутнє значення. Я написав цю програму, використовуючи OpenAI, тому, якщо ви хочете використовувати іншого провайдера, вам потрібно буде її скоригувати. + +1. Створіть [обліковий запис OpenAI](https://auth.openai.com/create-account) + +2. [Поповніть рахунок](https://platform.openai.com/settings/organization/billing/overview) — на момент написання статті мінімальна сума становить 5 доларів США + +3. [Створіть ключ API](https://platform.openai.com/settings/organization/api-keys) + +4. У командному рядку експортуйте ключ API, щоб ваша програма могла його використовувати + + ```sh + export OPENAI_API_KEY=sk-<решта ключа вводиться сюди> + ``` + +5. Перейдіть до гілки та запустіть агент + + ```sh + git checkout 04-interface-llm\nuv run agent.py + ``` + +Ось новий код. + +```python +from openai import OpenAI\n\nopen_ai = OpenAI() # Клієнт зчитує змінну середовища OPENAI_API_KEY +``` + +Імпортуйте та створіть екземпляр OpenAI API. + +```python +response = open_ai.chat.completions.create(\n model=\"gpt-4-turbo\",\n messages=[\n {\"role\": \"user\", \"content\": prompt}\n ],\n temperature=0.0,\n max_tokens=16,\n) +``` + +Викличте OpenAI API (`open_ai.chat.completions.create`), щоб створити відповідь. + +```python +expected_price = Decimal(response.choices[0].message.content.strip())\ncurrent_price = wethusdc_quotes[-1].price\n\nprint (\"Поточна ціна:\", wethusdc_quotes[-1].price)\nprint(f\"На {future_time} очікувана ціна: {expected_price} USD\")\n\nif (expected_price > current_price):\n print(f\"Купувати, я очікую, що ціна зросте на {expected_price - current_price} USD\")\nelse:\n print(f\"Продавати, я очікую, що ціна впаде на {current_price - expected_price} USD\") +``` + +Виведіть ціну та надайте рекомендацію щодо купівлі чи продажу. + +#### Тестування прогнозів {#testing-the-predictions} + +Тепер, коли ми можемо генерувати прогнози, ми також можемо використовувати історичні дані для оцінки, чи створюємо ми корисні прогнози. + +```sh +uv run test-predictor.py +``` + +Очікуваний результат схожий на: + +``` +Прогноз на 2026-01-05T19:50: прогнозовано 3138.93 USD, реальна 3218.92 USD, помилка 79.99 USD\nПрогноз на 2026-01-06T19:56: прогнозовано 3243.39 USD, реальна 3221.08 USD, помилка 22.31 USD\nПрогноз на 2026-01-07T20:02: прогнозовано 3223.24 USD, реальна 3146.89 USD, помилка 76.35 USD\nПрогноз на 2026-01-08T20:11: прогнозовано 3150.47 USD, реальна 3092.04 USD, помилка 58.43 USD\n.\n.\n.\nПрогноз на 2026-01-31T22:33: прогнозовано 2637.73 USD, реальна 2417.77 USD, помилка 219.96 USD\nПрогноз на 2026-02-01T22:41: прогнозовано 2381.70 USD, реальна 2318.84 USD, помилка 62.86 USD\nПрогноз на 2026-02-02T22:49: прогнозовано 2234.91 USD, реальна 2349.28 USD, помилка 114.37 USD\nСередня помилка прогнозу за 29 прогнозів: 83.87103448275862068965517241 USD\nСередня зміна на рекомендацію: 4.787931034482758620689655172 USD\nСтандартне відхилення змін: 104.42 USD\nПрибуткові дні: 51.72%\nЗбиткові дні: 48.28% +``` + +Більша частина тестера ідентична агенту, але ось частини, які є новими або зміненими. + +```python +CYCLES_FOR_TEST = 40 # Для бектесту, скільки циклів ми тестуємо\n\n# Отримати багато котирувань\nwethusdc_pool = read_pool(WETHUSDC_ADDRESS, True)\nwethusdc_quotes = get_quotes(\n wethusdc_pool,\n w3.eth.block_number - CYCLE_BLOCKS*CYCLES_FOR_TEST,\n w3.eth.block_number,\n CYCLE_BLOCKS,\n)\n\nwethwbtc_pool = read_pool(WETHWBTC_ADDRESS)\nwethwbtc_quotes = get_quotes(\n wethwbtc_pool,\n w3.eth.block_number - CYCLE_BLOCKS*CYCLES_FOR_TEST,\n w3.eth.block_number,\n CYCLE_BLOCKS\n) +``` + +Ми розглядаємо `CYCLES_FOR_TEST` (тут вказано 40) днів назад. + +```python +# Створення прогнозів та їх перевірка на основі реальної історії\n\ntotal_error = Decimal(0)\nchanges = [] +``` + +Нас цікавлять два типи помилок. Перша, `total_error`, — це просто сума помилок, яких припустився прогнозист. + +Щоб зрозуміти другу, `changes`, нам потрібно згадати про призначення агента. Воно полягає не в тому, щоб прогнозувати співвідношення WETH/USDC (ціну ETH). Воно полягає в тому, щоб видавати рекомендації на продаж і купівлю. Якщо поточна ціна становить 2000 доларів, а завтра прогнозується 2010 доларів, ми не проти, якщо фактичний результат буде 2020 доларів і ми заробимо додаткові гроші. Але ми _проти_, якщо він прогнозував 2010 доларів і купив ETH на основі цієї рекомендації, а ціна впала до 1990 доларів. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +Ми можемо розглядати лише випадки, коли доступна повна історія (значення, що використовуються для прогнозу, і реальне значення для порівняння з ним). Це означає, що найновішим випадком має бути той, що почався `CYCLES_BACK` тому. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK]\n wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +Використовуйте [зрізи](https://www.w3schools.com/python/ref_func_slice.asp), щоб отримати таку ж кількість зразків, яку використовує агент. Код між цим і наступним сегментом — це той самий код для отримання прогнозу, який ми маємо в агенті. + +```python + predicted_price = Decimal(response.choices[0].message.content.strip())\n real_price = wethusdc_quotes[index+CYCLES_BACK].price\n prediction_time_price = wethusdc_quotes[index+CYCLES_BACK-1].price +``` + +Отримайте прогнозовану ціну, реальну ціну та ціну на момент прогнозування. Нам потрібна ціна на момент прогнозування, щоб визначити, чи була рекомендація купувати чи продавати. + +```python + error = abs(predicted_price - real_price)\n total_error += error\n print (f\"Прогноз на {prediction_time}: прогнозована ціна {predicted_price} USD, реальна ціна {real_price} USD, помилка {error} USD\") +``` + +Визначте помилку та додайте її до загальної суми. + +```python + recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell'\n price_increase = real_price - prediction_time_price\n changes.append(price_increase if recomended_action == 'buy' else -price_increase) +``` + +Для `changes` нам потрібен грошовий ефект від купівлі або продажу одного ETH. Отже, спочатку нам потрібно визначити рекомендацію, потім оцінити, як змінилася фактична ціна, і чи принесла рекомендація гроші (позитивна зміна), чи призвела до збитків (негативна зміна). + +```python +print (f\"Середня помилка прогнозування за {len(wethusdc_quotes)-CYCLES_BACK} прогнозів: {total_error / Decimal(len(wethusdc_quotes)-CYCLES_BACK)} USD\")\n\nlength_changes = Decimal(len(changes))\nmean_change = sum(changes, Decimal(0)) / length_changes\nprint (f\"Середня зміна на рекомендацію: {mean_change} USD\")\nvar = sum((x - mean_change) ** 2 for x in changes) / length_changes\nprint (f\"Стандартне відхилення змін: {var.sqrt().quantize(Decimal(\"0.01\"))} USD\") +``` + +Повідомте результати. + +```python +print (f\"Прибуткові дні: {len(list(filter(lambda x: x > 0, changes)))/length_changes:.2%}\")\nprint (f\"Збиткові дні: {len(list(filter(lambda x: x < 0, changes)))/length_changes:.2%}\") +``` + +Використовуйте [`filter`](https://www.w3schools.com/python/ref_func_filter.asp), щоб підрахувати кількість прибуткових днів і кількість збиткових днів. Результатом є об’єкт фільтра, який нам потрібно перетворити на список, щоб отримати його довжину. + +### Надсилання транзакцій {#submit-txn} + +Тепер нам потрібно фактично надсилати транзакції. Однак я не хочу витрачати реальні гроші на цьому етапі, перш ніж система буде перевірена. Натомість ми створимо локальний форк основної мережі й будемо «торгувати» в цій мережі. + +Ось кроки для створення локального форку й увімкнення торгівлі. + +1. Установіть [Foundry](https://getfoundry.sh/introduction/installation) + +2. Запустіть [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` прослуховує URL-адресу за замовчуванням для Foundry, http://localhost:8545, тому нам не потрібно вказувати URL-адресу для [команди `cast`](https://getfoundry.sh/cast/overview), яку ми використовуємо для маніпулювання блокчейном. + +3. Під час роботи в `anvil` є десять тестових облікових записів, які мають ETH — встановіть змінні середовища для першого з них + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80\nADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. Це контракти, які нам потрібно використовувати. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) — це контракт Uniswap v3, який ми використовуємо для фактичної торгівлі. Ми могли б торгувати безпосередньо через пул, але так набагато простіше. + + Дві нижні змінні — це шляхи Uniswap v3, необхідні для обміну між WETH і USDC. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\nUSDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\nPOOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640\nSWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564\nWETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\nUSDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. Кожен із тестових облікових записів має 10 000 ETH. Використовуйте контракт WETH, щоб обернути 1000 ETH і отримати 1000 WETH для торгівлі. + + ```sh + cast send $WETH_ADDRESS \"deposit()\" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. Використовуйте `SwapRouter`, щоб обміняти 500 WETH на USDC. + + ```sh + cast send $WETH_ADDRESS \"approve(address,uint256)\" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY\nMAXINT=`cast max-int uint256`\ncast send $SWAP_ROUTER \\\n \"exactInput((bytes,address,uint256,uint256,uint256))\" \\\n \"($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)\" \\\n --private-key $PRIVATE_KEY + ``` + + Виклик `approve` створює дозвіл, який дозволяє `SwapRouter` витрачати деякі з наших токенів. Контракти не можуть відстежувати події, тому якщо ми перекажемо токени безпосередньо на контракт `SwapRouter`, він не дізнається, що йому заплатили. Натомість ми дозволяємо контракту `SwapRouter` витратити певну суму, а потім `SwapRouter` робить це. Це робиться за допомогою функції, що викликається `SwapRouter`, тому він знає, чи був виклик успішним. + +7. Переконайтеся, що у вас достатньо обох токенів. + + ```sh + cast call $WETH_ADDRESS \"balanceOf(address)\" $ADDRESS | cast from-wei\necho `cast call $USDC_ADDRESS \"balanceOf(address)\" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +Тепер, коли у нас є WETH та USDC, ми можемо фактично запустити агент. + +```sh +git checkout 05-trade\nuv run agent.py +``` + +Вивід буде схожий на: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py\nПоточна ціна: 1843.16\nНа 2026-02-06T23:07, очікувана ціна: 1724.41 USD\nБаланси облікового запису перед угодою:\nБаланс USDC: 927301.578272\nБаланс WETH: 500\nПродавати, я очікую, що ціна впаде на 118.75 USD\nНадіслана транзакція схвалення: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f\nТранзакцію схвалення видобуто.\nНадіслано транзакцію продажу: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf\nТранзакцію продажу видобуто.\nБаланси облікового запису після угоди:\nБаланс USDC: 929143.797116\nБаланс WETH: 499 +``` + +Щоб фактично використовувати його, вам потрібно внести кілька незначних змін. + +- У рядку 14 змініть `MAINNET_URL` на реальну точку доступу, наприклад, `https://eth.drpc.org` +- У рядку 28 змініть `PRIVATE_KEY` на ваш власний приватний ключ +- Якщо ви не дуже багаті й не можете купувати чи продавати 1 ETH щодня для неперевіреного агента, ви можете змінити рядок 29, щоб зменшити `WETH_TRADE_AMOUNT` + +#### Пояснення коду {#trading-code} + +Ось новий код. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address(\"0xE592427A0AEce92De3Edee1F18E0157C05861564\")\nWETH_TO_USDC=bytes.fromhex(\"C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\")\nUSDC_TO_WETH=bytes.fromhex(\"A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\")\nPRIVATE_KEY=\"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80\" +``` + +Ті самі змінні, які ми використовували на кроці 4. + +```python +WETH_TRADE_AMOUNT=1 +``` + +Сума для торгівлі. + +```python +ERC20_ABI = [\n { \"name\": \"symbol\", ... },\n { \"name\": \"decimals\", ... },\n { \"name\": \"balanceOf\", ...},\n { \"name\": \"approve\", ...}\n] +``` + +Щоб фактично торгувати, нам потрібна функція `approve`. Ми також хочемо показувати баланси до та після, тому нам також потрібна `balanceOf`. + +```python +SWAP_ROUTER_ABI = [\n { \"name\": \"exactInput\", ...},\n] +``` + +В ABI `SwapRouter` нам потрібна лише `exactInput`. Існує пов'язана функція `exactOutput`, яку ми могли б використовувати, щоб купити рівно один WETH, але для простоти ми використовуємо `exactInput` в обох випадках. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY)\nswap_router = w3.eth.contract(\n address=SWAP_ROUTER_ADDRESS,\n abi=SWAP_ROUTER_ABI\n) +``` + +Визначення Web3 для [`account`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) і контракту `SwapRouter`. + +```python +def txn_params() -> dict:\n return {\n \"from\": account.address,\n \"value\": 0,\n \"gas\": 300000,\n \"nonce\": w3.eth.get_transaction_count(account.address),\n } +``` + +Параметри транзакції. Тут нам потрібна функція, оскільки [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) має змінюватися щоразу. + +```python +def approve_token(contract: Contract, amount: int): +``` + +Схвалити дозвіл на використання токенів для `SwapRouter`. + +```python + txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params())\n signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY)\n tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction) +``` + +Ось як ми надсилаємо транзакцію в Web3. Спочатку ми використовуємо [об’єкт `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) для створення транзакції. Потім ми використовуємо [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) для підписання транзакції за допомогою `PRIVATE_KEY`. Нарешті, ми використовуємо [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) для надсилання транзакції. + +```python + print(f\"Надіслано транзакцію схвалення: {tx_hash.hex()}\")\n w3.eth.wait_for_transaction_receipt(tx_hash)\n print(\"Транзакцію схвалення видобуто.\") +``` + +[`w3.eth.wait_for_transaction_receipt`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) очікує, доки транзакцію не буде видобуто. За потреби він повертає квитанцію. + +```python +SELL_PARAMS = {\n \"path\": WETH_TO_USDC,\n \"recipient\": account.address,\n \"deadline\": 2**256 - 1,\n \"amountIn\": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals,\n \"amountOutMinimum\": 0,\n} +``` + +Це параметри для продажу WETH. + +```python +def make_buy_params(quote: Quote) -> dict:\n return {\n \"path\": USDC_TO_WETH,\n \"recipient\": account.address,\n \"deadline\": 2**256 - 1,\n \"amountIn\": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals,\n \"amountOutMinimum\": 0,\n } +``` + +На відміну від `SELL_PARAMS`, параметри купівлі можуть змінюватися. Вхідна сума — це вартість 1 WETH, доступна в `quote`. + +```python +def buy(quote: Quote):\n buy_params = make_buy_params(quote)\n approve_token(wethusdc_pool.token0.contract, buy_params[\"amountIn\"])\n txn = swap_router.functions.exactInput(buy_params).build_transaction(txn_params())\n signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY)\n tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)\n print(f\"Надіслано транзакцію купівлі: {tx_hash.hex()}\")\n w3.eth.wait_for_transaction_receipt(tx_hash)\n print(\"Транзакцію купівлі видобуто.\")\n\n\ndef sell():\n approve_token(wethusdc_pool.token1.contract,\n WETH_TRADE_AMOUNT * 10**wethusdc_pool.token1.decimals)\n txn = swap_router.functions.exactInput(SELL_PARAMS).build_transaction(txn_params())\n signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY)\n tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)\n print(f\"Надіслано транзакцію продажу: {tx_hash.hex()}\")\n w3.eth.wait_for_transaction_receipt(tx_hash)\n print(\"Транзакцію продажу видобуто.\") +``` + +Функції `buy()` та `sell()` майже ідентичні. Спочатку ми схвалюємо достатній дозвіл для `SwapRouter`, а потім викликаємо його з правильним шляхом і сумою. + +```python +def balances():\n token0_balance = wethusdc_pool.token0.contract.functions.balanceOf(account.address).call()\n token1_balance = wethusdc_pool.token1.contract.functions.balanceOf(account.address).call()\n\n print(f\"{wethusdc_pool.token0.symbol} Баланс: {Decimal(token0_balance) / Decimal(10 ** wethusdc_pool.token0.decimals)}\")\n print(f\"{wethusdc_pool.token1.symbol} Баланс: {Decimal(token1_balance) / Decimal(10 ** wethusdc_pool.token1.decimals)}\") +``` + +Повідомте про баланси користувача в обох валютах. + +```python +print(\"Баланси облікового запису перед угодою:\")\nbalances()\n\nif (expected_price > current_price):\n print(f\"Купувати, я очікую, що ціна зросте на {expected_price - current_price} USD\")\n buy(wethusdc_quotes[-1])\nelse:\n print(f\"Продавати, я очікую, що ціна впаде на {current_price - expected_price} USD\")\n sell()\n\nprint(\"Баланси облікового запису після угоди:\")\nbalances() +``` + +Наразі цей агент працює лише один раз. Однак ви можете змінити його для безперервної роботи, запустивши його з [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) або обернувши рядки 368-400 у цикл і використавши [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) для очікування до настання часу для наступного циклу. + +## Можливі вдосконалення {#improvements} + +Це не повна виробнича версія; це лише приклад для вивчення основ. Ось кілька ідей для вдосконалень. + +### Розумніша торгівля {#smart-trading} + +Існують два важливі факти, які агент ігнорує, вирішуючи, що робити. + +- _Масштаб очікуваної зміни_. Агент продає фіксовану кількість `WETH`, якщо очікується падіння ціни, незалежно від масштабу падіння. + Можна стверджувати, що було б краще ігнорувати незначні зміни й продавати залежно від того, наскільки ми очікуємо падіння ціни. +- _Поточний портфель_. Якщо 10 % вашого портфеля знаходиться у WETH, і ви думаєте, що ціна зросте, ймовірно, є сенс купити ще. Але якщо 90 % вашого портфеля знаходиться у WETH, ви можете бути достатньо відкриті, і немає потреби купувати ще. Зворотне вірно, якщо ви очікуєте, що ціна впаде. + +### Що робити, якщо ви хочете зберегти свою торгову стратегію в таємниці? {#secret} + +Постачальники ШІ можуть бачити запити, які ви надсилаєте до їхніх LLM, що може розкрити геніальну торгову систему, яку ви розробили за допомогою свого агента. Торгова система, яку використовує занадто багато людей, нічого не варта, оскільки занадто багато людей намагаються купити, коли ви хочете купити (і ціна зростає), і намагаються продати, коли ви хочете продати (і ціна падає). + +Ви можете запустити LLM локально, наприклад, за допомогою [LM-Studio](https://lmstudio.ai/), щоб уникнути цієї проблеми. + +### Від AI-бота до AI-агента {#bot-to-agent} + +Можна цілком обґрунтовано стверджувати, що це [AI-бот, а не AI-агент](/ai-agents/#ai-agents-vs-ai-bots). Він реалізує відносно просту стратегію, яка покладається на заздалегідь визначену інформацію. Ми можемо увімкнути самовдосконалення, наприклад, надавши список пулів Uniswap v3 та їхні останні значення й запитавши, яка комбінація має найкращу прогностичну цінність. + +### Захист від прослизання {#slippage-protection} + +Наразі немає [захисту від прослизання](https://uniswapv3book.com/milestone_3/slippage-protection.html). Якщо поточне котирування становить 2000 доларів США, а очікувана ціна — 2100 доларів США, агент купить. Однак, якщо до того, як агент купить, вартість зросте до 2200 доларів, купувати більше немає сенсу. + +Щоб реалізувати захист від прослизання, вкажіть значення `amountOutMinimum` у рядках 325 і 334 файлу [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325). + +## Висновок {#conclusion} + +Сподіваюся, тепер ви знаєте достатньо, щоб почати роботу з AI-агентами. Це не всеосяжний огляд теми; цьому присвячені цілі книги, але цього достатньо, щоб почати. Успіхів! + +[Більше моїх робіт дивіться тут](https://cryptodocguy.pro/). diff --git a/public/content/translations/ur/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/ur/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..89fed3527c0 --- /dev/null +++ b/public/content/translations/ur/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "ایتھیریم پر اپنا خود کا AI ٹریڈنگ ایجنٹ بنائیں" +description: "اس ٹیوٹوریل میں آپ سیکھیں گے کہ ایک سادہ AI ٹریڈنگ ایجنٹ کیسے بنایا جائے۔ یہ ایجنٹ بلاک چین سے معلومات پڑھتا ہے، اس معلومات کی بنیاد پر LLM سے سفارش مانگتا ہے، LLM کی تجویز کردہ ٹریڈ کرتا ہے، اور پھر انتظار کرتا ہے اور دہراتا ہے۔" +author: Ori Pomerantz +tags: [ "AI", "ٹریڈنگ", "ایجنٹ", "python" ] +skill: intermediate +published: 2026-02-13 +lang: ur-in +sidebarDepth: 3 +--- + +اس ٹیوٹوریل میں آپ سیکھیں گے کہ ایک سادہ AI ٹریڈنگ ایجنٹ کیسے بنایا جائے۔ یہ ایجنٹ ان مراحل کا استعمال کرتے ہوئے کام کرتا ہے: + +1. کسی ٹوکن کی موجودہ اور ماضی کی قیمتیں پڑھیں، ساتھ ہی دیگر ممکنہ طور پر متعلقہ معلومات بھی +2. اس معلومات کے ساتھ ایک سوال تیار کریں، پس منظر کی معلومات کے ساتھ یہ بتانے کے لیے کہ یہ کس طرح متعلقہ ہو سکتا ہے +3. سوال جمع کریں اور ایک متوقع قیمت واپس حاصل کریں +4. سفارش کی بنیاد پر ٹریڈ کریں +5. انتظار کریں اور دہرائیں + +یہ ایجنٹ ظاہر کرتا ہے کہ معلومات کو کیسے پڑھا جائے، اسے ایک ایسے سوال میں ترجمہ کیا جائے جو قابل استعمال جواب دے، اور اس جواب کا استعمال کیا جائے۔ یہ سب ایک AI ایجنٹ کے لیے درکار اقدامات ہیں۔ اس ایجنٹ کو Python میں لاگو کیا گیا ہے کیونکہ یہ AI میں استعمال ہونے والی سب سے عام زبان ہے۔ + +## یہ کیوں کریں؟ {#why-do-this} + +خودکار ٹریڈنگ ایجنٹس ڈیولپرز کو ٹریڈنگ کی حکمت عملی منتخب کرنے اور اس پر عمل کرنے کی اجازت دیتے ہیں۔ [AI ایجنٹس](/ai-agents) زیادہ پیچیدہ اور متحرک ٹریڈنگ حکمت عملیوں کی اجازت دیتے ہیں، ممکنہ طور پر ایسی معلومات اور الگورتھم کا استعمال کرتے ہوئے جن کے استعمال پر ڈیولپر نے غور بھی نہیں کیا ہے۔ + +## ٹولز {#tools} + +یہ ٹیوٹوریل کوٹس اور ٹریڈنگ کے لیے [Python](https://www.python.org/)، [Web3 لائبریری](https://web3py.readthedocs.io/en/stable/)، اور [Uniswap v3](https://github.com/Uniswap/v3-periphery) کا استعمال کرتا ہے۔ + +### Python کیوں؟ {#python} + +AI کے لیے سب سے زیادہ استعمال ہونے والی زبان [Python](https://www.python.org/) ہے، اس لیے ہم اسے یہاں استعمال کرتے ہیں۔ اگر آپ Python نہیں جانتے تو فکر نہ کریں۔ زبان بہت واضح ہے، اور میں بالکل بتاؤں گا کہ یہ کیا کرتی ہے۔ + +[Web3 لائبریری](https://web3py.readthedocs.io/en/stable/) سب سے عام Python ایتھیریم API ہے۔ اسے استعمال کرنا بہت آسان ہے۔ + +### بلاک چین پر ٹریڈنگ {#trading-on-blockchain} + +بہت سے [ڈسٹری بیوٹڈ ایکسچینجز (DEX)](/apps/categories/defi/) ہیں جو آپ کو ایتھیریم پر ٹوکنز ٹریڈ کرنے کی اجازت دیتے ہیں۔ تاہم، [آربٹریج](/developers/docs/smart-contracts/composability/#better-user-experience) کی وجہ سے ان کی شرح تبادلہ ایک جیسی ہوتی ہے۔ + +[Uniswap](https://app.uniswap.org/) ایک وسیع پیمانے پر استعمال ہونے والا DEX ہے جسے ہم کوٹس (ٹوکن کی نسبتی قدریں دیکھنے کے لیے) اور ٹریڈز دونوں کے لیے استعمال کر سکتے ہیں۔ + +### OpenAI {#openai} + +ایک بڑے لینگویج ماڈل کے لیے، میں نے [OpenAI](https://openai.com/) کے ساتھ شروعات کرنے کا انتخاب کیا۔ اس ٹیوٹوریل میں ایپلیکیشن چلانے کے لیے آپ کو API رسائی کے لیے ادائیگی کرنی ہوگی۔ $5 کی کم از کم ادائیگی کافی سے زیادہ ہے۔ + +## ڈیولپمنٹ، مرحلہ وار {#step-by-step} + +ڈیولپمنٹ کو آسان بنانے کے لیے، ہم مراحل میں آگے بڑھتے ہیں۔ ہر مرحلہ GitHub میں ایک برانچ ہے۔ + +### شروعات کرنا {#getting-started} + +UNIX یا Linux (بشمول [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) کے تحت شروع کرنے کے لیے اقدامات ہیں + +1. اگر آپ کے پاس پہلے سے نہیں ہے تو، [Python](https://www.python.org/downloads/) ڈاؤن لوڈ اور انسٹال کریں۔ + +2. GitHub ریپوزٹری کو کلون کریں۔ + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. [`uv`](https://docs.astral.sh/uv/getting-started/installation/) انسٹال کریں۔ آپ کے سسٹم پر کمانڈ مختلف ہو سکتی ہے۔ + + ```sh + pipx install uv + ``` + +4. لائبریریاں ڈاؤن لوڈ کریں۔ + + ```sh + uv sync + ``` + +5. ورچوئل ماحول کو فعال کریں۔ + + ```sh + source .venv/bin/activate + ``` + +6. یہ تصدیق کرنے کے لیے کہ Python اور Web3 صحیح طریقے سے کام کر رہے ہیں، `python3` چلائیں اور اسے یہ پروگرام فراہم کریں۔ آپ اسے `>>>` پرامپٹ پر درج کر سکتے ہیں۔ فائل بنانے کی کوئی ضرورت نہیں ہے۔ + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### بلاک چین سے پڑھنا {#read-blockchain} + +اگلا مرحلہ بلاک چین سے پڑھنا ہے۔ ایسا کرنے کے لیے، آپ کو `02-read-quote` برانچ میں تبدیل ہونا ہوگا اور پھر پروگرام چلانے کے لیے `uv` کا استعمال کرنا ہوگا۔ + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +آپ کو `Quote` آبجیکٹس کی ایک فہرست موصول ہونی چاہیے، ہر ایک میں ایک ٹائم اسٹیمپ، ایک قیمت، اور اثاثہ (فی الحال ہمیشہ `WETH/USDC`)۔ + +یہاں لائن بہ لائن وضاحت ہے۔ + +```python +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 +``` + +ہمیں جن لائبریریوں کی ضرورت ہے انہیں درآمد کریں۔ ان کی وضاحت نیچے کی گئی ہے جب استعمال کیا جاتا ہے۔ + +```python +print = functools.partial(print, flush=True) +``` + +Python کے `print` کو ایک ایسے ورژن سے بدل دیتا ہے جو ہمیشہ آؤٹ پٹ کو فوری طور پر فلش کرتا ہے۔ یہ ایک طویل چلنے والے اسکرپٹ میں مفید ہے کیونکہ ہم اسٹیٹس اپ ڈیٹس یا ڈیبگنگ آؤٹ پٹ کا انتظار نہیں کرنا چاہتے۔ + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +مین نیٹ تک پہنچنے کے لیے ایک URL۔ آپ اسے [نوڈ بطور سروس](/developers/docs/nodes-and-clients/nodes-as-a-service/) سے حاصل کر سکتے ہیں یا [Chainlist](https://chainlist.org/chain/1) میں مشتہر کردہ میں سے ایک استعمال کر سکتے ہیں۔ + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +ایک ایتھیریم مین نیٹ بلاک عام طور پر ہر بارہ سیکنڈ میں ہوتا ہے، لہذا یہ ان بلاکس کی تعداد ہے جن کی ہم ایک مدت میں ہونے کی توقع کریں گے۔ نوٹ کریں کہ یہ ایک درست اعداد و شمار نہیں ہے۔ جب [بلاک پروپوزر](/developers/docs/consensus-mechanisms/pos/block-proposal/) ڈاؤن ہوتا ہے، تو اس بلاک کو چھوڑ دیا جاتا ہے، اور اگلے بلاک کا وقت 24 سیکنڈ ہوتا ہے۔ اگر ہم کسی ٹائم اسٹیمپ کے لیے بالکل درست بلاک حاصل کرنا چاہتے ہیں، تو ہم [بائنری سرچ](https://en.wikipedia.org/wiki/Binary_search) استعمال کریں گے۔ تاہم، یہ ہمارے مقاصد کے لیے کافی قریب ہے۔ مستقبل کی پیش گوئی کوئی قطعی سائنس نہیں ہے۔ + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +سائیکل کا سائز۔ ہم ہر سائیکل میں ایک بار کوٹس کا جائزہ لیتے ہیں اور اگلے سائیکل کے اختتام پر قدر کا تخمینہ لگانے کی کوشش کرتے ہیں۔ + +```python +# جس پول کو ہم پڑھ رہے ہیں اس کا پتہ +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +کوٹ کی قدریں Uniswap 3 USDC/WETH پول سے ایڈریس [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract) پر لی جاتی ہیں۔ یہ پتہ پہلے سے ہی چیکسم فارم میں ہے، لیکن کوڈ کو دوبارہ قابل استعمال بنانے کے لیے [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) استعمال کرنا بہتر ہے۔ + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +یہ ان دو معاہدوں کے لیے [ABIs](https://docs.soliditylang.org/en/latest/abi-spec.html) ہیں جن سے ہمیں رابطہ کرنے کی ضرورت ہے۔ کوڈ کو مختصر رکھنے کے لیے، ہم صرف ان فنکشنز کو شامل کرتے ہیں جنہیں ہمیں کال کرنے کی ضرورت ہے۔ + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +[`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) لائبریری شروع کریں اور ایک ایتھیریم نوڈ سے جڑیں۔ + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +یہ Python میں ڈیٹا کلاس بنانے کا ایک طریقہ ہے۔ [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) ڈیٹا ٹائپ معاہدے سے جڑنے کے لیے استعمال ہوتا ہے۔ ` (frozen=True)` پر غور کریں۔ Python میں [بولینز](https://en.wikipedia.org/wiki/Boolean_data_type) کو `True` یا `False` کے طور پر بیان کیا جاتا ہے، جو بڑے حروف میں ہوتے ہیں۔ یہ ڈیٹا کلاس `frozen` ہے، جس کا مطلب ہے کہ فیلڈز میں ترمیم نہیں کی جا سکتی۔ + +انڈینٹیشن پر غور کریں۔ [C- اخذ کردہ زبانوں](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages) کے برعکس، Python بلاکس کو ظاہر کرنے کے لیے انڈینٹیشن کا استعمال کرتا ہے۔ Python مترجم جانتا ہے کہ درج ذیل تعریف اس ڈیٹا کلاس کا حصہ نہیں ہے کیونکہ یہ ڈیٹا کلاس فیلڈز کی طرح اسی انڈینٹیشن سے شروع نہیں ہوتی ہے۔ + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +[`Decimal`](https://docs.python.org/3/library/decimal.html) قسم اعشاریہ کسروں کو درست طریقے سے ہینڈل کرنے کے لیے استعمال ہوتی ہے۔ + +```python + def get_price(self, block: int) -> Decimal: +``` + +یہ Python میں ایک فنکشن کی تعریف کرنے کا طریقہ ہے۔ تعریف کو یہ دکھانے کے لیے انڈینٹ کیا گیا ہے کہ یہ اب بھی `PoolInfo` کا حصہ ہے۔ + +ایک فنکشن میں جو ڈیٹا کلاس کا حصہ ہے، پہلا پیرامیٹر ہمیشہ `self` ہوتا ہے، ڈیٹا کلاس کا وہ نمونہ جو یہاں کال کیا گیا ہے۔ یہاں ایک اور پیرامیٹر ہے، بلاک نمبر۔ + +```python + assert block <= w3.eth.block_number, "بلاک مستقبل میں ہے" +``` + +اگر ہم مستقبل پڑھ سکتے، تو ہمیں ٹریڈنگ کے لیے AI کی ضرورت نہیں ہوتی۔ + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +Web3 سے EVM پر فنکشن کال کرنے کا سنٹیکس یہ ہے: `.functions.().call()`۔ پیرامیٹرز EVM فنکشن کے پیرامیٹرز ہو سکتے ہیں (اگر کوئی ہو؛ یہاں کوئی نہیں ہیں) یا بلاک چین کے رویے میں ترمیم کے لیے [نامزد پیرامیٹرز](https://en.wikipedia.org/wiki/Named_parameter)۔ یہاں ہم ایک، `block_identifier`، [بلاک نمبر](/developers/docs/apis/json-rpc/#default-block) کی وضاحت کے لیے استعمال کرتے ہیں جس میں ہم چلنا چاہتے ہیں۔ + +نتیجہ [یہ struct ہے، ارے فارم میں](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72)۔ پہلی قدر دو ٹوکنز کے درمیان شرح تبادلہ کا ایک فنکشن ہے۔ + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +آن چین حسابات کو کم کرنے کے لیے، Uniswap v3 اصل ایکسچینج فیکٹر کو ذخیرہ نہیں کرتا بلکہ اس کا مربع جڑ ذخیرہ کرتا ہے۔ چونکہ EVM فلوٹنگ پوائنٹ ریاضی یا کسروں کو سپورٹ نہیں کرتا، اس لیے اصل قدر کے بجائے، جواب price296 ہوتا ہے۔ + +```python + # (ٹوکن1 فی ٹوکن0) + return 1/(raw_price * self.decimal_factor) +``` + +ہمیں جو خام قیمت ملتی ہے وہ ہر `token1` کے لیے ملنے والے `token0` کی تعداد ہے۔ ہمارے پول میں `token0` USDC (امریکی ڈالر کے برابر قدر والا stablecoin) ہے اور `token1` [WETH](https://opensea.io/learn/blockchain/what-is-weth) ہے۔ جو قدر ہم واقعی چاہتے ہیں وہ فی WETH ڈالر کی تعداد ہے، نہ کہ اس کا الٹ۔ + +اعشاریہ عنصر دو ٹوکنز کے لیے [اعشاریہ عوامل](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) کے درمیان کا تناسب ہے۔ + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +یہ ڈیٹا کلاس ایک کوٹ کی نمائندگی کرتی ہے: ایک مخصوص وقت پر ایک مخصوص اثاثہ کی قیمت۔ اس وقت، `asset` فیلڈ غیر متعلقہ ہے کیونکہ ہم ایک ہی پول استعمال کرتے ہیں اور اس لیے ہمارے پاس ایک ہی اثاثہ ہے۔ تاہم، ہم بعد میں مزید اثاثے شامل کریں گے۔ + +```python +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 + ) +``` + +یہ فنکشن ایک پتہ لیتا ہے اور اس پتے پر ٹوکن معاہدے کے بارے میں معلومات واپس کرتا ہے۔ ایک نیا [Web3 `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) بنانے کے لیے، ہم `w3.eth.contract` کو پتہ اور ABI فراہم کرتے ہیں۔ + +```python +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) + ) +``` + +یہ فنکشن [ایک مخصوص پول](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol) کے بارے میں ہمیں درکار ہر چیز واپس کرتا ہے۔ سنٹیکس `f""` ایک [فارمیٹڈ سٹرنگ](https://docs.python.org/3/reference/lexical_analysis.html#f-strings) ہے۔ + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +ایک `Quote` آبجیکٹ حاصل کریں۔ `block_number` کے لیے ڈیفالٹ ویلیو `None` (کوئی ویلیو نہیں) ہے۔ + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +اگر بلاک نمبر کی وضاحت نہیں کی گئی تھی، تو `w3.eth.block_number` استعمال کریں، جو کہ تازہ ترین بلاک نمبر ہے۔ یہ [ایک `if` اسٹیٹمنٹ](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement) کے لیے سنٹیکس ہے۔ + +ایسا لگ سکتا ہے کہ ڈیفالٹ کو صرف `w3.eth.block_number` پر سیٹ کرنا بہتر ہوتا، لیکن یہ اچھی طرح سے کام نہیں کرتا کیونکہ یہ فنکشن کی تعریف کے وقت بلاک نمبر ہوتا۔ ایک طویل چلنے والے ایجنٹ میں، یہ ایک مسئلہ ہوگا۔ + +```python + 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 + ) +``` + +اسے انسانوں اور بڑے لینگویج ماڈلز (LLMs) کے لیے پڑھنے کے قابل فارمیٹ میں فارمیٹ کرنے کے لیے [`datetime` لائبریری](https://docs.python.org/3/library/datetime.html) کا استعمال کریں۔ قدر کو دو اعشاریہ مقامات تک راؤنڈ کرنے کے لیے [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) کا استعمال کریں۔ + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +Python میں آپ `list[]` کا استعمال کرتے ہوئے ایک [فہرست](https://docs.python.org/3/library/stdtypes.html#typesseq-list) کی تعریف کرتے ہیں جو صرف ایک مخصوص قسم پر مشتمل ہو سکتی ہے۔ + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +Python میں [`for` لوپ](https://docs.python.org/3/tutorial/controlflow.html#for-statements) عام طور پر ایک فہرست پر تکرار کرتا ہے۔ کوٹس تلاش کرنے کے لیے بلاک نمبروں کی فہرست [`range`](https://docs.python.org/3/library/stdtypes.html#range) سے آتی ہے۔ + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +ہر بلاک نمبر کے لیے، ایک `Quote` آبجیکٹ حاصل کریں اور اسے `quotes` فہرست میں شامل کریں۔ پھر اس فہرست کو واپس کریں۔ + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +یہ اسکرپٹ کا مرکزی کوڈ ہے۔ پول کی معلومات پڑھیں، بارہ کوٹس حاصل کریں، اور انہیں [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) کریں۔ + +### ایک پرامپٹ بنانا {#prompt} + +اگلا، ہمیں کوٹس کی اس فہرست کو LLM کے لیے ایک پرامپٹ میں تبدیل کرنا ہوگا اور ایک متوقع مستقبل کی قدر حاصل کرنی ہوگی۔ + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +آؤٹ پٹ اب LLM کو ایک پرامپٹ ہوگا، جیسا کہ: + +``` +یہ کوٹس دیے گئے ہیں: +اثاثہ: WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +اثاثہ: WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +آپ توقع کریں گے کہ WETH/USDC کی قیمت وقت 2026-02-02T17:56 پر کیا ہوگی؟ + +اپنا جواب دو اعشاریہ مقامات تک گول ایک ہی نمبر کے طور پر فراہم کریں، +کسی اور متن کے بغیر۔ +``` + +غور کریں کہ یہاں دو اثاثوں کے لیے کوٹس ہیں، `WETH/USDC` اور `WBTC/WETH`۔ کسی دوسرے اثاثے سے کوٹس شامل کرنے سے پیشین گوئی کی درستگی بہتر ہو سکتی ہے۔ + +#### ایک پرامپٹ کیسا لگتا ہے {#prompt-explanation} + +اس پرامپٹ میں تین حصے ہیں، جو LLM پرامپٹس میں بہت عام ہیں۔ + +1. معلومات۔ LLMs کے پاس اپنی تربیت سے بہت سی معلومات ہوتی ہیں، لیکن ان کے پاس عام طور پر تازہ ترین معلومات نہیں ہوتیں۔ یہی وجہ ہے کہ ہمیں یہاں تازہ ترین کوٹس بازیافت کرنے کی ضرورت ہے۔ ایک پرامپٹ میں معلومات شامل کرنا [ریٹریول آگمینٹڈ جنریشن (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation) کہلاتا ہے۔ + +2. اصل سوال۔ یہ وہ ہے جو ہم جاننا چاہتے ہیں۔ + +3. آؤٹ پٹ فارمیٹنگ ہدایات۔ عام طور پر، ایک LLM ہمیں ایک تخمینہ دے گا جس میں یہ بتایا جائے گا کہ یہ اس تک کیسے پہنچا۔ یہ انسانوں کے لیے بہتر ہے، لیکن ایک کمپیوٹر پروگرام کو صرف نچلی لائن کی ضرورت ہوتی ہے۔ + +#### کوڈ کی وضاحت {#prompt-code} + +یہاں نیا کوڈ ہے۔ + +```python +from datetime import datetime, timezone, timedelta +``` + +ہمیں LLM کو وہ وقت فراہم کرنے کی ضرورت ہے جس کے لیے ہم تخمینہ چاہتے ہیں۔ مستقبل میں "n منٹ/گھنٹے/دن" کا وقت حاصل کرنے کے لیے، ہم [`timedelta` کلاس](https://docs.python.org/3/library/datetime.html#datetime.timedelta) کا استعمال کرتے ہیں۔ + +```python +# جن پولز کو ہم پڑھ رہے ہیں ان کے پتے +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +ہمارے پاس دو پول ہیں جنہیں ہمیں پڑھنے کی ضرورت ہے۔ + +```python +@dataclass(frozen=True) +class PoolInfo: + . + . + . + reverse: bool = False + + def get_price(self, block: int) -> Decimal: + assert block <= w3.eth.block_number, "بلاک مستقبل میں ہے" + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (ٹوکن1 فی ٹوکن0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +WETH/USDC پول میں، ہم جاننا چاہتے ہیں کہ `token0` (USDC) میں سے کتنے کی ضرورت ہے تاکہ `token1` (WETH) کا ایک خریدا جا سکے۔ WETH/WBTC پول میں، ہم جاننا چاہتے ہیں کہ `token1` (WETH) میں سے کتنے کی ضرورت ہے تاکہ `token0` (WBTC, جو کہ لپیٹا ہوا Bitcoin ہے) کا ایک خریدا جا سکے۔ ہمیں یہ ٹریک کرنے کی ضرورت ہے کہ آیا پول کے تناسب کو الٹنے کی ضرورت ہے۔ + +```python +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 + ) +``` + +یہ جاننے کے لیے کہ آیا کسی پول کو الٹنے کی ضرورت ہے، ہم اسے `read_pool` کے ان پٹ کے طور پر حاصل کرتے ہیں۔ اس کے علاوہ، اثاثہ کی علامت کو صحیح طریقے سے ترتیب دینے کی ضرورت ہے۔ + +سنٹیکس ` if else ` [ٹرنری کنڈیشنل آپریٹر](https://en.wikipedia.org/wiki/Ternary_conditional_operator) کا Python مساوی ہے، جو C-اخذ کردہ زبان میں ` ? : ` ہوگا۔ + +```python +def format_quotes(quotes: list[Quote]) -> str: + result = f"اثاثہ: {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 +``` + +یہ فنکشن ایک سٹرنگ بناتا ہے جو `Quote` آبجیکٹس کی فہرست کو فارمیٹ کرتا ہے، یہ فرض کرتے ہوئے کہ وہ سب ایک ہی اثاثے پر لاگو ہوتے ہیں۔ + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +Python میں [ملٹی لائن سٹرنگ لٹرلز](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) کو `"""` .... کے طور پر لکھا جاتا ہے۔ `"""`۔ + +```python +یہ کوٹس دیے گئے ہیں: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +یہاں، ہم ہر کوٹ فہرست کے لیے `format_quotes` کے ساتھ ایک سٹرنگ بنانے کے لیے [MapReduce](https://en.wikipedia.org/wiki/MapReduce) پیٹرن کا استعمال کرتے ہیں، پھر انہیں پرامپٹ میں استعمال کے لیے ایک ہی سٹرنگ میں کم کرتے ہیں۔ + +```python +آپ توقع کریں گے کہ {asset} کی قیمت وقت {expected_time} پر کیا ہوگی؟ + +اپنا جواب دو اعشاریہ مقامات تک گول ایک ہی نمبر کے طور پر فراہم کریں، +کسی اور متن کے بغیر۔ + """ +``` + +پرامپٹ کا باقی حصہ توقع کے مطابق ہے۔ + +```python +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 +) +``` + +دونوں پولز کا جائزہ لیں اور دونوں سے کوٹس حاصل کریں۔ + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +مستقبل کے اس وقت کا تعین کریں جس کے لیے ہم تخمینہ چاہتے ہیں، اور پرامپٹ بنائیں۔ + +### LLM کے ساتھ انٹرفیسنگ {#interface-llm} + +اگلا، ہم ایک حقیقی LLM کو پرامپٹ کرتے ہیں اور ایک متوقع مستقبل کی قدر حاصل کرتے ہیں۔ میں نے یہ پروگرام OpenAI کا استعمال کرتے ہوئے لکھا ہے، لہذا اگر آپ کوئی مختلف فراہم کنندہ استعمال کرنا چاہتے ہیں، تو آپ کو اسے ایڈجسٹ کرنا ہوگا۔ + +1. ایک [OpenAI اکاؤنٹ](https://auth.openai.com/create-account) حاصل کریں + +2. [اکاؤنٹ میں فنڈ ڈالیں](https://platform.openai.com/settings/organization/billing/overview)—لکھنے کے وقت کم از کم رقم $5 ہے + +3. [ایک API کلید بنائیں](https://platform.openai.com/settings/organization/api-keys) + +4. کمانڈ لائن میں، API کلید برآمد کریں تاکہ آپ کا پروگرام اسے استعمال کر سکے + + ```sh + export OPENAI_API_KEY=sk-<باقی کلید یہاں جاتی ہے> + ``` + +5. ایجنٹ کو چیک آؤٹ کریں اور چلائیں + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +یہاں نیا کوڈ ہے۔ + +```python +from openai import OpenAI + +open_ai = OpenAI() # کلائنٹ OPENAI_API_KEY ماحولیاتی متغیر کو پڑھتا ہے +``` + +OpenAI API کو امپورٹ اور انسٹینٹیٹ کریں۔ + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +جواب بنانے کے لیے OpenAI API (`open_ai.chat.completions.create`) کو کال کریں۔ + +```python +expected_price = Decimal(response.choices[0].message.content.strip()) +current_price = wethusdc_quotes[-1].price + +print ("موجودہ قیمت:", wethusdc_quotes[-1].price) +print(f"{future_time} میں، متوقع قیمت: {expected_price} USD") + +if (expected_price > current_price): + print(f"خریدیں، مجھے امید ہے کہ قیمت {expected_price - current_price} USD تک بڑھ جائے گی") +else: + print(f"بیچیں، مجھے امید ہے کہ قیمت {current_price - expected_price} USD تک گر جائے گی") +``` + +قیمت کو آؤٹ پٹ کریں اور خرید و فروخت کی سفارش فراہم کریں۔ + +#### پیشین گوئیوں کی جانچ {#testing-the-predictions} + +اب جب کہ ہم پیشین گوئیاں پیدا کر سکتے ہیں، ہم تاریخی ڈیٹا کا بھی استعمال کر سکتے ہیں تاکہ یہ اندازہ لگایا جا سکے کہ کیا ہم مفید پیشین گوئیاں پیدا کرتے ہیں۔ + +```sh +uv run test-predictor.py +``` + +متوقع نتیجہ اس جیسا ہے: + +``` +2026-01-05T19:50 کے لیے پیشین گوئی: پیشین گوئی 3138.93 USD، اصل 3218.92 USD، غلطی 79.99 USD +2026-01-06T19:56 کے لیے پیشین گوئی: پیشین گوئی 3243.39 USD، اصل 3221.08 USD، غلطی 22.31 USD +2026-01-07T20:02 کے لیے پیشین گوئی: پیشین گوئی 3223.24 USD، اصل 3146.89 USD، غلطی 76.35 USD +2026-01-08T20:11 کے لیے پیشین گوئی: پیشین گوئی 3150.47 USD، اصل 3092.04 USD، غلطی 58.43 USD +. +. +. +2026-01-31T22:33 کے لیے پیشین گوئی: پیشین گوئی 2637.73 USD، اصل 2417.77 USD، غلطی 219.96 USD +2026-02-01T22:41 کے لیے پیشین گوئی: پیشین گوئی 2381.70 USD، اصل 2318.84 USD، غلطی 62.86 USD +2026-02-02T22:49 کے لیے پیشین گوئی: پیشین گوئی 2234.91 USD، اصل 2349.28 USD، غلطی 114.37 USD +29 پیشین گوئیوں پر اوسط پیشین گوئی کی غلطی: 83.87103448275862068965517241 USD +فی سفارش اوسط تبدیلی: 4.787931034482758620689655172 USD +تبدیلیوں کا معیاری تغیر: 104.42 USD +منافع بخش دن: 51.72% +نقصان والے دن: 48.28% +``` + +ٹیسٹر کا زیادہ تر حصہ ایجنٹ کی طرح ہی ہے، لیکن یہاں وہ حصے ہیں جو نئے یا ترمیم شدہ ہیں۔ + +```python +CYCLES_FOR_TEST = 40 # بیک ٹیسٹ کے لیے، ہم کتنے سائیکلوں کی جانچ کرتے ہیں + +# بہت سے کوٹس حاصل کریں +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 +) +``` + +ہم `CYCLES_FOR_TEST` (یہاں 40 کے طور پر بیان کیا گیا ہے) دن پیچھے دیکھتے ہیں۔ + +```python +# پیشین گوئیاں بنائیں اور انہیں حقیقی تاریخ کے خلاف چیک کریں + +total_error = Decimal(0) +changes = [] +``` + +دو قسم کی غلطیاں ہیں جن میں ہمیں دلچسپی ہے۔ پہلا، `total_error`، صرف پیش گو نے کی گئی غلطیوں کا مجموعہ ہے۔ + +دوسرے، `changes`، کو سمجھنے کے لیے، ہمیں ایجنٹ کے مقصد کو یاد رکھنے کی ضرورت ہے۔ یہ WETH/USDC تناسب (ETH قیمت) کی پیش گوئی کرنا نہیں ہے۔ یہ فروخت اور خرید کی سفارشات جاری کرنا ہے۔ اگر قیمت فی الحال $2000 ہے اور یہ کل $2010 کی پیش گوئی کرتا ہے، تو ہمیں کوئی اعتراض نہیں اگر اصل نتیجہ $2020 ہو اور ہم اضافی رقم کمائیں۔ لیکن ہمیں اس وقت اعتراض ہوتا ہے جب اس نے $2010 کی پیش گوئی کی ہو، اور اس سفارش کی بنیاد پر ETH خریدا ہو، اور قیمت $1990 تک گر جائے۔ + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +ہم صرف ان معاملات کو دیکھ سکتے ہیں جہاں مکمل تاریخ (پیشین گوئی کے لیے استعمال ہونے والی قدریں اور اس سے موازنہ کرنے کے لیے حقیقی دنیا کی قدر) دستیاب ہے۔ اس کا مطلب ہے کہ سب سے نیا کیس وہ ہونا چاہیے جو `CYCLES_BACK` پہلے شروع ہوا تھا۔ + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +[سلائسز](https://www.w3schools.com/python/ref_func_slice.asp) کا استعمال کریں تاکہ نمونوں کی اتنی ہی تعداد حاصل کی جا سکے جتنی ایجنٹ استعمال کرتا ہے۔ یہاں اور اگلے حصے کے درمیان کا کوڈ وہی ہے جو ایجنٹ میں موجود پیشین گوئی حاصل کرنے والا کوڈ ہے۔ + +```python + 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 +``` + +پیشین گوئی کی گئی قیمت، حقیقی قیمت، اور پیشین گوئی کے وقت کی قیمت حاصل کریں۔ یہ تعین کرنے کے لیے کہ سفارش خریدنے کی تھی یا بیچنے کی، ہمیں پیشین گوئی کے وقت کی قیمت کی ضرورت ہے۔ + +```python + 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") +``` + +غلطی کا اندازہ لگائیں، اور اسے کل میں شامل کریں۔ + +```python + recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell' + price_increase = real_price - prediction_time_price + changes.append(price_increase if recomended_action == 'buy' else -price_increase) +``` + +`changes` کے لیے، ہم ایک ETH خریدنے یا بیچنے کے مالیاتی اثرات چاہتے ہیں۔ لہذا پہلے، ہمیں سفارش کا تعین کرنے کی ضرورت ہے، پھر یہ اندازہ لگانا ہے کہ اصل قیمت کس طرح تبدیل ہوئی، اور کیا سفارش نے پیسہ کمایا (مثبت تبدیلی) یا پیسہ خرچ کیا (منفی تبدیلی)۔ + +```python +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") +``` + +نتائج کی اطلاع دیں۔ + +```python +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%}") +``` + +منافع بخش دنوں اور مہنگے دنوں کی تعداد گننے کے لیے [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) کا استعمال کریں۔ نتیجہ ایک فلٹر آبجیکٹ ہے، جسے ہمیں لمبائی حاصل کرنے کے لیے ایک فہرست میں تبدیل کرنے کی ضرورت ہے۔ + +### ٹرانزیکشنز جمع کرنا {#submit-txn} + +اب ہمیں اصل میں ٹرانزیکشنز جمع کرنے کی ضرورت ہے۔ تاہم، میں اس وقت حقیقی رقم خرچ نہیں کرنا چاہتا، اس سے پہلے کہ سسٹم ثابت ہو جائے۔ اس کے بجائے، ہم مین نیٹ کا ایک مقامی فورک بنائیں گے، اور اس نیٹ ورک پر "ٹریڈ" کریں گے۔ + +یہاں ایک مقامی فورک بنانے اور ٹریڈنگ کو فعال کرنے کے اقدامات ہیں۔ + +1. [Foundry](https://getfoundry.sh/introduction/installation) انسٹال کریں + +2. [`anvil`](https://getfoundry.sh/anvil/overview) شروع کریں + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` Foundry کے لیے ڈیفالٹ URL، http://localhost:8545 پر سن رہا ہے، لہذا ہمیں [ `cast` کمانڈ](https://getfoundry.sh/cast/overview) کے لیے URL کی وضاحت کرنے کی ضرورت نہیں ہے جسے ہم بلاک چین میں ہیرا پھیری کے لیے استعمال کرتے ہیں۔ + +3. `anvil` میں چلتے وقت، دس ٹیسٹ اکاؤنٹس ہیں جن میں ETH ہے — پہلے والے کے لیے ماحولیاتی متغیرات سیٹ کریں + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. یہ وہ معاہدے ہیں جنہیں ہمیں استعمال کرنے کی ضرورت ہے۔ [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) Uniswap v3 معاہدہ ہے جسے ہم اصل میں ٹریڈ کرنے کے لیے استعمال کرتے ہیں۔ ہم براہ راست پول کے ذریعے ٹریڈ کر سکتے ہیں، لیکن یہ بہت آسان ہے۔ + + نیچے کے دو متغیرات Uniswap v3 کے راستے ہیں جو WETH اور USDC کے درمیان تبادلہ کرنے کے لیے درکار ہیں۔ + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. ہر ٹیسٹ اکاؤنٹ میں 10,000 ETH ہیں۔ ٹریڈنگ کے لیے 1000 WETH حاصل کرنے کے لیے 1000 ETH کو لپیٹنے کے لیے WETH معاہدہ استعمال کریں۔ + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. USDC کے لیے 500 WETH ٹریڈ کرنے کے لیے `SwapRouter` کا استعمال کریں۔ + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + `approve` کال ایک الاؤنس بناتی ہے جو `SwapRouter` کو ہمارے کچھ ٹوکنز خرچ کرنے کی اجازت دیتی ہے۔ معاہدے واقعات کی نگرانی نہیں کر سکتے، لہذا اگر ہم براہ راست `SwapRouter` معاہدے میں ٹوکن منتقل کرتے ہیں، تو اسے یہ معلوم نہیں ہوگا کہ اسے ادائیگی کی گئی ہے۔ اس کے بجائے، ہم `SwapRouter` معاہدے کو ایک خاص رقم خرچ کرنے کی اجازت دیتے ہیں، اور پھر `SwapRouter` یہ کرتا ہے۔ یہ `SwapRouter` کے ذریعے کال کیے گئے فنکشن کے ذریعے کیا جاتا ہے، لہذا یہ جانتا ہے کہ آیا یہ کامیاب تھا۔ + +7. تصدیق کریں کہ آپ کے پاس دونوں ٹوکنز کافی ہیں۔ + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +اب جب کہ ہمارے پاس WETH اور USDC ہے، ہم اصل میں ایجنٹ چلا سکتے ہیں۔ + +```sh +git checkout 05-trade +uv run agent.py +``` + +آؤٹ پٹ اس جیسا نظر آئے گا: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +موجودہ قیمت: 1843.16 +2026-02-06T23:07 میں، متوقع قیمت: 1724.41 USD +ٹریڈ سے پہلے اکاؤنٹ بیلنس: +USDC بیلنس: 927301.578272 +WETH بیلنس: 500 +بیچیں، مجھے امید ہے کہ قیمت 118.75 USD تک گر جائے گی +منظور شدہ ٹرانزیکشن بھیجی گئی: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +منظور شدہ ٹرانزیکشن مائن ہو گئی۔ +فروخت ٹرانزیکشن بھیجی گئی: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +فروخت ٹرانزیکشن مائن ہو گئی۔ +ٹریڈ کے بعد اکاؤنٹ بیلنس: +USDC بیلنس: 929143.797116 +WETH بیلنس: 499 +``` + +اسے اصل میں استعمال کرنے کے لیے، آپ کو کچھ معمولی تبدیلیوں کی ضرورت ہے۔ + +- لائن 14 میں، `MAINNET_URL` کو ایک حقیقی رسائی پوائنٹ میں تبدیل کریں، جیسے `https://eth.drpc.org` +- لائن 28 میں، `PRIVATE_KEY` کو اپنی ذاتی کلید میں تبدیل کریں +- جب تک آپ بہت امیر نہ ہوں اور ایک غیر ثابت شدہ ایجنٹ کے لیے ہر روز 1 ETH خرید یا بیچ سکتے ہوں، آپ `WETH_TRADE_AMOUNT` کو کم کرنے کے لیے 29 کو تبدیل کرنا چاہ سکتے ہیں۔ + +#### کوڈ کی وضاحت {#trading-code} + +یہاں نیا کوڈ ہے۔ + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +وہی متغیرات جو ہم نے مرحلہ 4 میں استعمال کیے تھے۔ + +```python +WETH_TRADE_AMOUNT=1 +``` + +ٹریڈ کرنے کی رقم۔ + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +اصل میں ٹریڈ کرنے کے لیے، ہمیں `approve` فنکشن کی ضرورت ہے۔ ہم پہلے اور بعد میں بیلنس بھی دکھانا چاہتے ہیں، لہذا ہمیں `balanceOf` کی بھی ضرورت ہے۔ + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +`SwapRouter` ABI میں ہمیں صرف `exactInput` کی ضرورت ہے۔ ایک متعلقہ فنکشن ہے، `exactOutput`، جسے ہم بالکل ایک WETH خریدنے کے لیے استعمال کر سکتے ہیں، لیکن سادگی کے لیے ہم دونوں صورتوں میں صرف `exactInput` استعمال کرتے ہیں۔ + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +[`account`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) اور `SwapRouter` معاہدے کے لیے Web3 کی تعریفیں۔ + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +ٹرانزیکشن کے پیرامیٹرز۔ ہمیں یہاں ایک فنکشن کی ضرورت ہے کیونکہ [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) ہر بار تبدیل ہونا چاہیے۔ + +```python +def approve_token(contract: Contract, amount: int): +``` + +`SwapRouter` کے لیے ٹوکن الاؤنس منظور کریں۔ + +```python + txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params()) + signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY) + tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction) +``` + +یہ وہ طریقہ ہے جس سے ہم Web3 میں ٹرانزیکشن بھیجتے ہیں۔ پہلے ہم ٹرانزیکشن بنانے کے لیے [`Contract` آبجیکٹ](https://web3py.readthedocs.io/en/stable/web3.contract.html) کا استعمال کرتے ہیں۔ پھر ہم `PRIVATE_KEY` کا استعمال کرتے ہوئے، ٹرانزیکشن پر دستخط کرنے کے لیے [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) کا استعمال کرتے ہیں۔ آخر میں، ہم ٹرانزیکشن بھیجنے کے لیے [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) کا استعمال کرتے ہیں۔ + +```python + 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`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) ٹرانزیکشن کے مائن ہونے تک انتظار کرتا ہے۔ یہ ضرورت پڑنے پر رسید واپس کرتا ہے۔ + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +یہ WETH بیچتے وقت کے پیرامیٹرز ہیں۔ + +```python +def make_buy_params(quote: Quote) -> dict: + return { + "path": USDC_TO_WETH, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals, + "amountOutMinimum": 0, + } +``` + +`SELL_PARAMS` کے برعکس، خرید کے پیرامیٹرز تبدیل ہو سکتے ہیں۔ ان پٹ کی رقم 1 WETH کی قیمت ہے، جیسا کہ `quote` میں دستیاب ہے۔ + +```python +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.") +``` + +`buy()` اور `sell()` فنکشنز تقریباً ایک جیسے ہیں۔ پہلے ہم `SwapRouter` کے لیے کافی الاؤنس منظور کرتے ہیں، اور پھر ہم اسے صحیح راستے اور رقم کے ساتھ کال کرتے ہیں۔ + +```python +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)}") +``` + +دونوں کرنسیوں میں صارف کے بیلنس کی اطلاع دیں۔ + +```python +print("ٹریڈ سے پہلے اکاؤنٹ بیلنس:") +balances() + +if (expected_price > current_price): + print(f"خریدیں، مجھے امید ہے کہ قیمت {expected_price - current_price} USD تک بڑھ جائے گی") + buy(wethusdc_quotes[-1]) +else: + print(f"بیچیں، مجھے امید ہے کہ قیمت {current_price - expected_price} USD تک گر جائے گی") + sell() + +print("ٹریڈ کے بعد اکاؤنٹ بیلنس:") +balances() +``` + +یہ ایجنٹ فی الحال صرف ایک بار کام کرتا ہے۔ تاہم، آپ اسے [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) سے چلا کر یا لائن 368-400 کو ایک لوپ میں لپیٹ کر اور [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) کا استعمال کرکے اسے مسلسل کام کرنے کے لیے تبدیل کر سکتے ہیں تاکہ اگلے سائیکل کا وقت آنے تک انتظار کیا جا سکے۔ + +## ممکنہ بہتری {#improvements} + +یہ ایک مکمل پروڈکشن ورژن نہیں ہے؛ یہ صرف بنیادی باتیں سکھانے کے لیے ایک مثال ہے۔ یہاں بہتری کے لیے کچھ خیالات ہیں۔ + +### ہوشیار ٹریڈنگ {#smart-trading} + +دو اہم حقائق ہیں جنہیں ایجنٹ نظر انداز کرتا ہے جب وہ فیصلہ کرتا ہے کہ کیا کرنا ہے۔ + +- _متوقع تبدیلی کی شدت۔_ ایجنٹ ایک مقررہ رقم کا `WETH` فروخت کرتا ہے اگر قیمت میں کمی کی توقع ہو، چاہے کمی کی شدت کچھ بھی ہو۔ + یقیناً، معمولی تبدیلیوں کو نظر انداز کرنا اور اس بات کی بنیاد پر فروخت کرنا بہتر ہوگا کہ ہم قیمت میں کتنی کمی کی توقع کرتے ہیں۔ +- _موجودہ پورٹ فولیو۔_ اگر آپ کے پورٹ فولیو کا 10% WETH میں ہے اور آپ کو لگتا ہے کہ قیمت بڑھے گی، تو شاید مزید خریدنا سمجھ میں آتا ہے۔ لیکن اگر آپ کے پورٹ فولیو کا 90% WETH میں ہے، تو آپ کافی حد تک بے نقاب ہو سکتے ہیں، اور مزید خریدنے کی ضرورت نہیں ہے۔ الٹا سچ ہے اگر آپ کو قیمت کم ہونے کی توقع ہے۔ + +### اگر آپ اپنی ٹریڈنگ کی حکمت عملی کو خفیہ رکھنا چاہتے ہیں تو کیا ہوگا؟ {#secret} + +AI وینڈرز آپ کے LLMs کو بھیجے گئے سوالات دیکھ سکتے ہیں، جو آپ کے ایجنٹ کے ساتھ تیار کردہ جینئس ٹریڈنگ سسٹم کو بے نقاب کر سکتے ہیں۔ ایک ٹریڈنگ سسٹم جسے بہت سے لوگ استعمال کرتے ہیں وہ بیکار ہے کیونکہ بہت سے لوگ اس وقت خریدنے کی کوشش کرتے ہیں جب آپ خریدنا چاہتے ہیں (اور قیمت بڑھ جاتی ہے) اور اس وقت بیچنے کی کوشش کرتے ہیں جب آپ بیچنا چاہتے ہیں (اور قیمت گر جاتی ہے)۔ + +آپ مقامی طور پر ایک LLM چلا سکتے ہیں، مثال کے طور پر، [LM-Studio](https://lmstudio.ai/) کا استعمال کرتے ہوئے، اس مسئلے سے بچنے کے لیے۔ + +### AI بوٹ سے AI ایجنٹ تک {#bot-to-agent} + +آپ ایک اچھا کیس بنا سکتے ہیں کہ یہ [ایک AI بوٹ ہے، نہ کہ AI ایجنٹ](/ai-agents/#ai-agents-vs-ai-bots)۔ یہ ایک نسبتاً آسان حکمت عملی کو نافذ کرتا ہے جو پہلے سے طے شدہ معلومات پر انحصار کرتا ہے۔ ہم خود کو بہتر بنانے کو فعال کر سکتے ہیں، مثال کے طور پر، Uniswap v3 پولز کی ایک فہرست اور ان کی تازہ ترین قدریں فراہم کرکے اور یہ پوچھ کر کہ کس امتزاج کی بہترین پیشین گوئی کی قدر ہے۔ + +### سلپیج پروٹیکشن {#slippage-protection} + +فی الحال کوئی [سلپیج پروٹیکشن](https://uniswapv3book.com/milestone_3/slippage-protection.html) نہیں ہے۔ اگر موجودہ کوٹ $2000 ہے، اور متوقع قیمت $2100 ہے، تو ایجنٹ خریدے گا۔ تاہم، اگر ایجنٹ کے خریدنے سے پہلے لاگت $2200 تک بڑھ جاتی ہے، تو مزید خریدنے کا کوئی مطلب نہیں ہے۔ + +سلپیج پروٹیکشن کو نافذ کرنے کے لیے، [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325) کی لائن 325 اور 334 میں `amountOutMinimum` کی قدر کی وضاحت کریں۔ + +## نتیجہ {#conclusion} + +امید ہے، اب آپ AI ایجنٹس کے ساتھ شروع کرنے کے لیے کافی جانتے ہیں۔ یہ اس موضوع کا ایک جامع جائزہ نہیں ہے؛ اس کے لیے پوری کتابیں وقف ہیں، لیکن یہ آپ کو شروع کرنے کے لیے کافی ہے۔ گڈ لک! + +[میرے مزید کام کے لیے یہاں دیکھیں](https://cryptodocguy.pro/)۔ diff --git a/public/content/translations/vi/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/vi/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..698cef04cac --- /dev/null +++ b/public/content/translations/vi/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "Tạo tác tử giao dịch AI của riêng bạn trên Ethereum" +description: "Trong hướng dẫn này, bạn sẽ học cách tạo một tác tử giao dịch AI đơn giản. Tác tử này đọc thông tin từ chuỗi khối, yêu cầu LLM đưa ra đề xuất dựa trên thông tin đó, thực hiện giao dịch mà LLM đề xuất, sau đó chờ và lặp lại." +author: Ori Pomerantz +tags: [ "AI", "giao dịch", "tác nhân", "python" ] +skill: intermediate +published: 2026-02-13 +lang: vi +sidebarDepth: 3 +--- + +Trong hướng dẫn này, bạn sẽ học cách xây dựng một tác tử giao dịch AI đơn giản. Tác tử này hoạt động theo các bước sau: + +1. Đọc giá hiện tại và quá khứ của một token, cũng như các thông tin khác có thể liên quan +2. Xây dựng truy vấn với thông tin này, cùng với thông tin nền tảng để giải thích mức độ liên quan của nó +3. Gửi truy vấn và nhận lại một mức giá dự kiến +4. Giao dịch dựa trên đề xuất +5. Chờ và lặp lại + +Tác tử này minh họa cách đọc thông tin, chuyển nó thành một truy vấn mang lại câu trả lời có thể sử dụng được và sử dụng câu trả lời đó. Tất cả những bước này đều cần thiết cho một tác tử AI. Tác tử này được triển khai bằng Python vì đây là ngôn ngữ phổ biến nhất được sử dụng trong AI. + +## Tại sao lại làm điều này? {#why-do-this} + +Các tác tử giao dịch tự động cho phép các nhà phát triển chọn và thực hiện một chiến lược giao dịch. [Các tác tử AI](/ai-agents) cho phép các chiến lược giao dịch phức tạp và năng động hơn, có khả năng sử dụng thông tin và thuật toán mà nhà phát triển thậm chí còn chưa cân nhắc sử dụng. + +## Các công cụ {#tools} + +Hướng dẫn này sử dụng [Python](https://www.python.org/), [thư viện Web3](https://web3py.readthedocs.io/en/stable/) và [Uniswap v3](https://github.com/Uniswap/v3-periphery) để lấy báo giá và giao dịch. + +### Tại sao lại là Python? {#python} + +Ngôn ngữ được sử dụng rộng rãi nhất cho AI là [Python](https://www.python.org/), vì vậy chúng tôi sử dụng nó ở đây. Đừng lo lắng nếu bạn không biết Python. Ngôn ngữ này rất rõ ràng, và tôi sẽ giải thích chính xác những gì nó làm. + +[Thư viện Web3](https://web3py.readthedocs.io/en/stable/) là Giao diện Lập trình Ứng dụng (API) Python Ethereum phổ biến nhất. Nó khá dễ sử dụng. + +### Giao dịch trên chuỗi khối {#trading-on-blockchain} + +Có [nhiều sàn giao dịch phi tập trung (DEX)](/apps/categories/defi/) cho phép bạn giao dịch token trên Ethereum. Tuy nhiên, chúng có xu hướng có tỷ giá hối đoái tương tự do [kinh doanh chênh lệch giá](/developers/docs/smart-contracts/composability/#better-user-experience). + +[Uniswap](https://app.uniswap.org/) là một sàn giao dịch phi tập trung (DEX) được sử dụng rộng rãi mà chúng ta có thể sử dụng cho cả báo giá (để xem giá trị tương đối của token) và giao dịch. + +### OpenAI {#openai} + +Đối với một mô hình ngôn ngữ lớn, tôi đã chọn bắt đầu với [OpenAI](https://openai.com/). Để chạy ứng dụng trong hướng dẫn này, bạn sẽ cần trả tiền để truy cập API. Khoản thanh toán tối thiểu là 5 đô la là quá đủ. + +## Phát triển, từng bước một {#step-by-step} + +Để đơn giản hóa việc phát triển, chúng tôi tiến hành theo từng giai đoạn. Mỗi bước là một nhánh trong GitHub. + +### Bắt đầu {#getting-started} + +Có các bước để bắt đầu trong UNIX hoặc Linux (bao gồm [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)) + +1. Nếu bạn chưa có, hãy tải xuống và cài đặt [Python](https://www.python.org/downloads/). + +2. Nhân bản kho lưu trữ GitHub. + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. Cài đặt [`uv`](https://docs.astral.sh/uv/getting-started/installation/). Lệnh trên hệ thống của bạn có thể khác. + + ```sh + pipx install uv + ``` + +4. Tải xuống các thư viện. + + ```sh + uv sync + ``` + +5. Kích hoạt môi trường ảo. + + ```sh + source .venv/bin/activate + ``` + +6. Để xác minh Python và Web3 đang hoạt động chính xác, hãy chạy `python3` và cung cấp cho nó chương trình này. Bạn có thể nhập nó tại dấu nhắc `>>>`; không cần tạo tệp. + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### Đọc từ chuỗi khối {#read-blockchain} + +Bước tiếp theo là đọc từ chuỗi khối. Để làm điều đó, bạn cần chuyển sang nhánh `02-read-quote` và sau đó sử dụng `uv` để chạy chương trình. + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +Bạn sẽ nhận được một danh sách các đối tượng `Quote`, mỗi đối tượng có một dấu thời gian, một mức giá và tài sản (hiện tại luôn là `WETH/USDC`). + +Đây là giải thích từng dòng. + +```python +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 +``` + +Nhập các thư viện chúng ta cần. Chúng được giải thích bên dưới khi được sử dụng. + +```python +print = functools.partial(print, flush=True) +``` + +Thay thế hàm `print` của Python bằng một phiên bản luôn xóa đầu ra ngay lập tức. Điều này hữu ích trong một kịch bản chạy dài vì chúng ta không muốn chờ cập nhật trạng thái hoặc đầu ra gỡ lỗi. + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +Một URL để đến mạng chính. Bạn có thể lấy một nút từ [Nút dưới dạng dịch vụ](/developers/docs/nodes-and-clients/nodes-as-a-service/) hoặc sử dụng một trong những nút được quảng cáo trong [Chainlist](https://chainlist.org/chain/1). + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +Một khối trên mạng chính Ethereum thường diễn ra sau mỗi mười hai giây, vì vậy đây là số lượng khối mà chúng tôi mong đợi sẽ xảy ra trong một khoảng thời gian. Lưu ý rằng đây không phải là một con số chính xác. Khi [người đề xuất khối](/developers/docs/consensus-mechanisms/pos/block-proposal/) bị hỏng, khối đó sẽ bị bỏ qua và thời gian cho khối tiếp theo là 24 giây. Nếu chúng tôi muốn lấy khối chính xác cho một dấu thời gian, chúng tôi sẽ sử dụng [tìm kiếm nhị phân](https://en.wikipedia.org/wiki/Binary_search). Tuy nhiên, điều này là đủ gần cho mục đích của chúng tôi. Dự đoán tương lai không phải là một môn khoa học chính xác. + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +Kích thước của chu kỳ. Chúng tôi xem xét các báo giá một lần mỗi chu kỳ và cố gắng ước tính giá trị vào cuối chu kỳ tiếp theo. + +```python +# The address of the pool we're reading +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +Các giá trị báo giá được lấy từ pool Uniswap 3 USDC/WETH tại địa chỉ [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract). Địa chỉ này đã ở dạng checksum, nhưng tốt hơn là sử dụng [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) để làm cho mã có thể tái sử dụng. + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +Đây là các [Giao diện nhị phân ứng dụng (ABI)](https://docs.soliditylang.org/en/latest/abi-spec.html) cho hai hợp đồng mà chúng ta cần liên hệ. Để giữ cho mã ngắn gọn, chúng tôi chỉ bao gồm các hàm mà chúng tôi cần gọi. + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +Khởi tạo thư viện [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) và kết nối với một nút Ethereum. + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +Đây là một cách để tạo một lớp dữ liệu trong Python. Kiểu dữ liệu [`Hợp đồng`](https://web3py.readthedocs.io/en/stable/web3.contract.html) được sử dụng để kết nối với hợp đồng. Lưu ý `(frozen=True)`. Trong Python, các [kiểu dữ liệu boolean](https://en.wikipedia.org/wiki/Boolean_data_type) được định nghĩa là `True` hoặc `False`, viết hoa. Lớp dữ liệu này là `frozen`, có nghĩa là các trường không thể bị sửa đổi. + +Lưu ý phần thụt lề. Trái ngược với [các ngôn ngữ có nguồn gốc từ C](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages), Python sử dụng thụt lề để biểu thị các khối. Trình thông dịch Python biết rằng định nghĩa sau đây không phải là một phần của lớp dữ liệu này vì nó không bắt đầu ở cùng một mức thụt lề như các trường của lớp dữ liệu. + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +Kiểu [`Decimal`](https://docs.python.org/3/library/decimal.html) được sử dụng để xử lý chính xác các phân số thập phân. + +```python + def get_price(self, block: int) -> Decimal: +``` + +Đây là cách định nghĩa một hàm trong Python. Định nghĩa được thụt lề để cho thấy nó vẫn là một phần của `PoolInfo`. + +Trong một hàm là một phần của lớp dữ liệu, tham số đầu tiên luôn là `self`, là thể hiện của lớp dữ liệu đã gọi ở đây. Ở đây có một tham số khác, đó là số khối. + +```python + assert block <= w3.eth.block_number, "Block is in the future" +``` + +Nếu chúng ta có thể đọc được tương lai, chúng ta sẽ không cần AI để giao dịch. + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +Cú pháp để gọi một hàm trên Máy chủ ảo Ethereum (EVM) từ Web3 là: `<đối tượng hợp đồng>.functions.().call()`. Các tham số có thể là tham số của hàm EVM (nếu có; ở đây không có) hoặc [tham số được đặt tên](https://en.wikipedia.org/wiki/Named_parameter) để sửa đổi hành vi của chuỗi khối. Ở đây chúng tôi sử dụng một tham số, `block_identifier`, để chỉ định [số khối](/developers/docs/apis/json-rpc/#default-block) mà chúng tôi muốn chạy trong đó. + +Kết quả là [cấu trúc này, ở dạng mảng](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72). Giá trị đầu tiên là một hàm của tỷ giá hối đoái giữa hai token. + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +Để giảm các phép tính trên chuỗi, Uniswap v3 không lưu trữ hệ số trao đổi thực tế mà là căn bậc hai của nó. Bởi vì Máy chủ ảo Ethereum (EVM) không hỗ trợ toán học dấu phẩy động hoặc phân số, thay vì giá trị thực, phản hồi là price296 + +```python + # (token1 per token0) + return 1/(raw_price * self.decimal_factor) +``` + +Giá thô chúng ta nhận được là số lượng `token0` mà chúng ta nhận được cho mỗi `token1`. Trong pool của chúng tôi, `token0` là USDC (stablecoin có giá trị tương đương đô la Mỹ) và `token1` là [WETH](https://opensea.io/learn/blockchain/what-is-weth). Giá trị mà chúng ta thực sự muốn là số đô la cho mỗi WETH, không phải là nghịch đảo. + +Hệ số thập phân là tỷ lệ giữa các [hệ số thập phân](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals) của hai token. + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +Lớp dữ liệu này đại diện cho một báo giá: giá của một tài sản cụ thể tại một thời điểm nhất định. Tại thời điểm này, trường `asset` không liên quan vì chúng tôi sử dụng một pool duy nhất và do đó có một tài sản duy nhất. Tuy nhiên, chúng tôi sẽ thêm nhiều tài sản hơn sau này. + +```python +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 + ) +``` + +Hàm này nhận một địa chỉ và trả về thông tin về hợp đồng token tại địa chỉ đó. Để tạo một [`Hợp đồng` Web3](https://web3py.readthedocs.io/en/stable/web3.contract.html) mới, chúng tôi cung cấp địa chỉ và Giao diện nhị phân ứng dụng (ABI) cho `w3.eth.contract`. + +```python +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) + ) +``` + +Hàm này trả về mọi thứ chúng ta cần về [một pool cụ thể](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol). Cú pháp `f""` là một [chuỗi được định dạng](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +Nhận một đối tượng `Quote`. Giá trị mặc định cho `block_number` là `None` (không có giá trị). + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +Nếu không chỉ định số khối, hãy sử dụng `w3.eth.block_number`, là số khối mới nhất. Đây là cú pháp cho [câu lệnh `if`](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement). + +Có vẻ như sẽ tốt hơn nếu chỉ đặt giá trị mặc định thành `w3.eth.block_number`, nhưng điều đó không hoạt động tốt vì nó sẽ là số khối tại thời điểm hàm được định nghĩa. Trong một tác tử chạy dài, đây sẽ là một vấn đề. + +```python + 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 + ) +``` + +Sử dụng [thư viện `datetime`](https://docs.python.org/3/library/datetime.html) để định dạng nó thành một định dạng có thể đọc được cho con người và các mô hình ngôn ngữ lớn (LLM). Sử dụng [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) để làm tròn giá trị đến hai chữ số thập phân. + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +Trong Python, bạn định nghĩa một [danh sách](https://docs.python.org/3/library/stdtypes.html#typesseq-list) chỉ có thể chứa một loại cụ thể bằng cách sử dụng `list[]`. + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +Trong Python, một [vòng lặp `for`](https://docs.python.org/3/tutorial/controlflow.html#for-statements) thường lặp qua một danh sách. Danh sách các số khối để tìm báo giá đến từ [`range`](https://docs.python.org/3/library/stdtypes.html#range). + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +Với mỗi số khối, lấy một đối tượng `Quote` và thêm nó vào danh sách `quotes`. Sau đó trả về danh sách đó. + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +Đây là mã chính của kịch bản. Đọc thông tin pool, lấy mười hai báo giá và [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) chúng. + +### Tạo một lời nhắc {#prompt} + +Tiếp theo, chúng ta cần chuyển đổi danh sách báo giá này thành một lời nhắc cho LLM và nhận được một giá trị tương lai dự kiến. + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +Đầu ra bây giờ sẽ là một lời nhắc cho LLM, tương tự như: + +``` +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. +``` + +Lưu ý rằng có báo giá cho hai tài sản ở đây, `WETH/USDC` và `WBTC/WETH`. Thêm báo giá từ một tài sản khác có thể cải thiện độ chính xác của dự đoán. + +#### Lời nhắc trông như thế nào {#prompt-explanation} + +Lời nhắc này chứa ba phần, khá phổ biến trong các lời nhắc LLM. + +1. Thông tin. Các LLM có rất nhiều thông tin từ quá trình đào tạo của chúng, nhưng chúng thường không có thông tin mới nhất. Đây là lý do chúng tôi cần lấy các báo giá mới nhất ở đây. Việc thêm thông tin vào một lời nhắc được gọi là [tạo sinh tăng cường truy xuất (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +2. Câu hỏi thực tế. Đây là những gì chúng tôi muốn biết. + +3. Hướng dẫn định dạng đầu ra. Thông thường, một LLM sẽ cho chúng ta một ước tính kèm theo giải thích về cách nó đi đến kết quả đó. Điều này tốt hơn cho con người, nhưng một chương trình máy tính chỉ cần kết quả cuối cùng. + +#### Giải thích mã {#prompt-code} + +Đây là mã mới. + +```python +from datetime import datetime, timezone, timedelta +``` + +Chúng ta cần cung cấp cho LLM thời gian mà chúng ta muốn ước tính. Để có được thời gian "n phút/giờ/ngày" trong tương lai, chúng ta sử dụng [lớp `timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta). + +```python +# The addresses of the pools we're reading +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +Chúng ta có hai pool cần đọc. + +```python +@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 # (token1 per token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +Trong pool WETH/USDC, chúng tôi muốn biết chúng tôi cần bao nhiêu `token0` (USDC) để mua một `token1` (WETH). Trong pool WETH/WBTC, chúng tôi muốn biết chúng tôi cần bao nhiêu `token1` (WETH) để mua một `token0` (WBTC, là Bitcoin được bọc). Chúng ta cần theo dõi xem tỷ lệ của pool có cần được đảo ngược hay không. + +```python +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 + ) +``` + +Để biết một pool có cần được đảo ngược hay không, chúng ta nhận nó làm đầu vào cho `read_pool`. Ngoài ra, biểu tượng tài sản cần được thiết lập chính xác. + +Cú pháp ` if else ` là tương đương trong Python của [toán tử điều kiện ba ngôi](https://en.wikipedia.org/wiki/Ternary_conditional_operator), mà trong một ngôn ngữ có nguồn gốc từ C sẽ là ` ?` ` : `. + +```python +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 +``` + +Hàm này xây dựng một chuỗi định dạng một danh sách các đối tượng `Quote`, giả sử tất cả chúng đều áp dụng cho cùng một tài sản. + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +Trong Python, [các chuỗi ký tự nhiều dòng](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp) được viết là `"""` .... `"""`. + +```python +Given these quotes: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +Ở đây, chúng tôi sử dụng mẫu [MapReduce](https://en.wikipedia.org/wiki/MapReduce) để tạo một chuỗi cho mỗi danh sách báo giá với `format_quotes`, sau đó rút gọn chúng thành một chuỗi duy nhất để sử dụng trong lời nhắc. + +```python +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. + """ +``` + +Phần còn lại của lời nhắc như mong đợi. + +```python +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 +) +``` + +Xem xét hai pool và lấy báo giá từ cả hai. + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +Xác định thời điểm trong tương lai mà chúng ta muốn ước tính và tạo lời nhắc. + +### Giao tiếp với một LLM {#interface-llm} + +Tiếp theo, chúng tôi nhắc một LLM thực tế và nhận được một giá trị tương lai dự kiến. Tôi đã viết chương trình này bằng OpenAI, vì vậy nếu bạn muốn sử dụng một nhà cung cấp khác, bạn sẽ cần phải điều chỉnh nó. + +1. Nhận một [tài khoản OpenAI](https://auth.openai.com/create-account) + +2. [Nạp tiền vào tài khoản](https://platform.openai.com/settings/organization/billing/overview)—số tiền tối thiểu tại thời điểm viết bài là 5 đô la + +3. [Tạo khóa API](https://platform.openai.com/settings/organization/api-keys) + +4. Trong dòng lệnh, xuất khóa API để chương trình của bạn có thể sử dụng nó + + ```sh + export OPENAI_API_KEY=sk- + ``` + +5. Thanh toán và chạy tác tử + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +Đây là mã mới. + +```python +from openai import OpenAI + +open_ai = OpenAI() # The client reads the OPENAI_API_KEY environment variable +``` + +Nhập và khởi tạo API OpenAI. + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +Gọi API OpenAI (`open_ai.chat.completions.create`) để tạo phản hồi. + +```python +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") +``` + +Xuất giá và đưa ra khuyến nghị mua hoặc bán. + +#### Kiểm tra các dự đoán {#testing-the-predictions} + +Bây giờ chúng ta có thể tạo ra các dự đoán, chúng ta cũng có thể sử dụng dữ liệu lịch sử để đánh giá xem chúng ta có tạo ra các dự đoán hữu ích hay không. + +```sh +uv run test-predictor.py +``` + +Kết quả mong đợi tương tự như: + +``` +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% +``` + +Hầu hết trình kiểm tra giống hệt với tác tử, nhưng đây là những phần mới hoặc đã được sửa đổi. + +```python +CYCLES_FOR_TEST = 40 # For the backtest, how many cycles we test over + +# Get lots of quotes +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 +) +``` + +Chúng tôi xem xét lại `CYCLES_FOR_TEST` (được chỉ định là 40 ở đây) ngày. + +```python +# Create predictions and check them against real history + +total_error = Decimal(0) +changes = [] +``` + +Có hai loại lỗi mà chúng tôi quan tâm. Thứ nhất, `total_error`, chỉ đơn giản là tổng các lỗi mà trình dự đoán đã mắc phải. + +Để hiểu được thứ hai, `changes`, chúng ta cần nhớ mục đích của tác tử. Đó không phải là để dự đoán tỷ lệ WETH/USDC (giá ETH). Đó là để đưa ra các khuyến nghị bán và mua. Nếu giá hiện tại là 2000 đô la và nó dự đoán là 2010 đô la vào ngày mai, chúng tôi không phiền nếu kết quả thực tế là 2020 đô la và chúng tôi kiếm được thêm tiền. Nhưng chúng tôi _có_ phiền nếu nó dự đoán là 2010 đô la và mua ETH dựa trên khuyến nghị đó, và giá giảm xuống còn 1990 đô la. + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +Chúng ta chỉ có thể xem xét các trường hợp có sẵn lịch sử hoàn chỉnh (các giá trị được sử dụng để dự đoán và giá trị thực tế để so sánh với nó). Điều này có nghĩa là trường hợp mới nhất phải là trường hợp bắt đầu cách đây `CYCLES_BACK`. + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +Sử dụng [các lát cắt](https://www.w3schools.com/python/ref_func_slice.asp) để có được cùng số lượng mẫu với số lượng mà tác tử sử dụng. Mã giữa đây và phân đoạn tiếp theo là cùng một mã nhận dự đoán mà chúng tôi có trong tác tử. + +```python + 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 +``` + +Lấy giá dự đoán, giá thực tế và giá tại thời điểm dự đoán. Chúng ta cần giá tại thời điểm dự đoán để xác định xem khuyến nghị là mua hay bán. + +```python + 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") +``` + +Tính toán lỗi và cộng nó vào tổng. + +```python + 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) +``` + +Đối với `changes`, chúng tôi muốn tác động tiền tệ của việc mua hoặc bán một ETH. Vì vậy, trước tiên, chúng ta cần xác định khuyến nghị, sau đó đánh giá giá thực tế đã thay đổi như thế nào và liệu khuyến nghị đó có kiếm được tiền (thay đổi tích cực) hay tốn tiền (thay đổi tiêu cực) hay không. + +```python +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") +``` + +Báo cáo kết quả. + +```python +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%}") +``` + +Sử dụng [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) để đếm số ngày có lãi và số ngày tốn kém. Kết quả là một đối tượng bộ lọc, chúng ta cần chuyển đổi nó thành một danh sách để lấy độ dài. + +### Gửi giao dịch {#submit-txn} + +Bây giờ chúng ta cần thực sự gửi giao dịch. Tuy nhiên, tôi không muốn tiêu tiền thật vào thời điểm này, trước khi hệ thống được chứng minh. Thay vào đó, chúng tôi sẽ tạo một bản phân nhánh cục bộ của mạng chính và "giao dịch" trên mạng đó. + +Đây là các bước để tạo một bản phân nhánh cục bộ và cho phép giao dịch. + +1. Cài đặt [Foundry](https://getfoundry.sh/introduction/installation) + +2. Bắt đầu [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` đang lắng nghe trên URL mặc định cho Foundry, http://localhost:8545, vì vậy chúng tôi không cần chỉ định URL cho [lệnh `cast`](https://getfoundry.sh/cast/overview) mà chúng tôi sử dụng để thao tác chuỗi khối. + +3. Khi chạy trong `anvil`, có mười tài khoản thử nghiệm có ETH—thiết lập các biến môi trường cho tài khoản đầu tiên + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. Đây là các hợp đồng chúng ta cần sử dụng. [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) là hợp đồng Uniswap v3 mà chúng tôi sử dụng để thực sự giao dịch. Chúng tôi có thể giao dịch trực tiếp thông qua pool, nhưng cách này dễ dàng hơn nhiều. + + Hai biến dưới cùng là các đường dẫn Uniswap v3 cần thiết để hoán đổi giữa WETH và USDC. + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. Mỗi tài khoản thử nghiệm có 10.000 ETH. Sử dụng hợp đồng WETH để bọc 1000 ETH để có được 1000 WETH để giao dịch. + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. Sử dụng `SwapRouter` để giao dịch 500 WETH lấy USDC. + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + Lệnh gọi `approve` tạo ra một khoản trợ cấp cho phép `SwapRouter` chi tiêu một số token của chúng tôi. Các hợp đồng không thể giám sát các sự kiện, vì vậy nếu chúng tôi chuyển token trực tiếp đến hợp đồng `SwapRouter`, nó sẽ không biết rằng nó đã được thanh toán. Thay vào đó, chúng tôi cho phép hợp đồng `SwapRouter` chi tiêu một số tiền nhất định, và sau đó `SwapRouter` sẽ thực hiện điều đó. Điều này được thực hiện thông qua một hàm được gọi bởi `SwapRouter`, vì vậy nó biết liệu nó có thành công hay không. + +7. Xác minh bạn có đủ cả hai loại token. + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +Bây giờ chúng ta đã có WETH và USDC, chúng ta có thể thực sự chạy tác tử. + +```sh +git checkout 05-trade +uv run agent.py +``` + +Đầu ra sẽ trông tương tự như: + +``` +(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 +``` + +Để thực sự sử dụng nó, bạn cần một vài thay đổi nhỏ. + +- Trong dòng 14, thay đổi `MAINNET_URL` thành một điểm truy cập thực, chẳng hạn như `https://eth.drpc.org` +- Trong dòng 28, thay đổi `PRIVATE_KEY` thành khóa riêng tư của riêng bạn +- Trừ khi bạn rất giàu có và có thể mua hoặc bán 1 ETH mỗi ngày cho một tác tử chưa được chứng minh, bạn có thể muốn thay đổi 29 để giảm `WETH_TRADE_AMOUNT` + +#### Giải thích mã {#trading-code} + +Đây là mã mới. + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +Các biến tương tự chúng ta đã sử dụng trong bước 4. + +```python +WETH_TRADE_AMOUNT=1 +``` + +Số tiền để giao dịch. + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +Để thực sự giao dịch, chúng ta cần hàm `approve`. Chúng tôi cũng muốn hiển thị số dư trước và sau, vì vậy chúng tôi cũng cần `balanceOf`. + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +Trong `SwapRouter` ABI, chúng ta chỉ cần `exactInput`. Có một hàm liên quan, `exactOutput`, chúng ta có thể sử dụng để mua chính xác một WETH, nhưng để đơn giản, chúng ta chỉ sử dụng `exactInput` trong cả hai trường hợp. + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +Các định nghĩa Web3 cho [`tài khoản`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) và hợp đồng `SwapRouter`. + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +Các tham số giao dịch. Chúng ta cần một hàm ở đây vì [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) phải thay đổi mỗi lần. + +```python +def approve_token(contract: Contract, amount: int): +``` + +Phê duyệt một khoản trợ cấp token cho `SwapRouter`. + +```python + 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) +``` + +Đây là cách chúng tôi gửi một giao dịch trong Web3. Đầu tiên, chúng tôi sử dụng [đối tượng `Hợp đồng`](https://web3py.readthedocs.io/en/stable/web3.contract.html) để xây dựng giao dịch. Sau đó, chúng tôi sử dụng [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) để ký giao dịch, sử dụng `PRIVATE_KEY`. Cuối cùng, chúng tôi sử dụng [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) để gửi giao dịch. + +```python + 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`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) chờ cho đến khi giao dịch được khai thác. Nó trả về biên lai nếu cần. + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +Đây là các tham số khi bán WETH. + +```python +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, + } +``` + +Trái ngược với `SELL_PARAMS`, các tham số mua có thể thay đổi. Số tiền đầu vào là chi phí của 1 WETH, có sẵn trong `quote`. + +```python +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.") +``` + +Các hàm `buy()` và `sell()` gần như giống hệt nhau. Đầu tiên, chúng tôi phê duyệt một khoản trợ cấp đủ cho `SwapRouter`, và sau đó chúng tôi gọi nó với đường dẫn và số tiền chính xác. + +```python +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)}") +``` + +Báo cáo số dư người dùng bằng cả hai loại tiền tệ. + +```python +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() +``` + +Tác tử này hiện chỉ hoạt động một lần. Tuy nhiên, bạn có thể thay đổi nó để hoạt động liên tục bằng cách chạy nó từ [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) hoặc bằng cách bọc các dòng 368-400 trong một vòng lặp và sử dụng [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) để đợi cho đến khi đến lúc cho chu kỳ tiếp theo. + +## Các cải tiến có thể có {#improvements} + +Đây không phải là một phiên bản sản xuất đầy đủ; nó chỉ là một ví dụ để dạy những điều cơ bản. Đây là một số ý tưởng để cải tiến. + +### Giao dịch thông minh hơn {#smart-trading} + +Có hai sự thật quan trọng mà tác tử bỏ qua khi quyết định phải làm gì. + +- _Mức độ thay đổi dự kiến_. Tác tử bán một lượng `WETH` cố định nếu giá dự kiến sẽ giảm, bất kể mức độ giảm. + Có thể cho rằng, sẽ tốt hơn nếu bỏ qua những thay đổi nhỏ và bán dựa trên mức độ chúng ta mong đợi giá sẽ giảm. +- _Danh mục đầu tư hiện tại_. Nếu 10% danh mục đầu tư của bạn là WETH và bạn nghĩ rằng giá sẽ tăng, có lẽ nên mua thêm. Nhưng nếu 90% danh mục đầu tư của bạn là WETH, bạn có thể đã đủ rủi ro và không cần phải mua thêm. Điều ngược lại cũng đúng nếu bạn mong đợi giá sẽ giảm. + +### Điều gì sẽ xảy ra nếu bạn muốn giữ bí mật chiến lược giao dịch của mình? {#secret} + +Các nhà cung cấp AI có thể thấy các truy vấn bạn gửi đến LLM của họ, điều này có thể làm lộ hệ thống giao dịch thiên tài mà bạn đã phát triển với tác tử của mình. Một hệ thống giao dịch mà quá nhiều người sử dụng là vô giá trị vì quá nhiều người cố gắng mua khi bạn muốn mua (và giá tăng lên) và cố gắng bán khi bạn muốn bán (và giá giảm xuống). + +Bạn có thể chạy một LLM cục bộ, ví dụ, bằng cách sử dụng [LM-Studio](https://lmstudio.ai/), để tránh vấn đề này. + +### Từ bot AI đến tác tử AI {#bot-to-agent} + +Bạn có thể đưa ra một trường hợp tốt rằng đây là [một bot AI, không phải là một tác tử AI](/ai-agents/#ai-agents-vs-ai-bots). Nó thực hiện một chiến lược tương đối đơn giản dựa trên thông tin được xác định trước. Chúng ta có thể kích hoạt khả năng tự cải thiện, ví dụ, bằng cách cung cấp một danh sách các pool Uniswap v3 và các giá trị mới nhất của chúng và hỏi sự kết hợp nào có giá trị dự đoán tốt nhất. + +### Bảo vệ chống trượt giá {#slippage-protection} + +Hiện tại không có [bảo vệ chống trượt giá](https://uniswapv3book.com/milestone_3/slippage-protection.html). Nếu báo giá hiện tại là 2000 đô la và giá dự kiến là 2100 đô la, tác tử sẽ mua. Tuy nhiên, nếu trước khi tác tử mua, chi phí tăng lên 2200 đô la, thì không còn ý nghĩa gì để mua nữa. + +Để thực hiện bảo vệ chống trượt giá, hãy chỉ định một giá trị `amountOutMinimum` trong các dòng 325 và 334 của [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325). + +## Kết luận {#conclusion} + +Hy vọng rằng bây giờ bạn đã biết đủ để bắt đầu với các tác tử AI. Đây không phải là một cái nhìn tổng quan toàn diện về chủ đề này; có cả những cuốn sách dành riêng cho điều đó, nhưng điều này là đủ để bạn bắt đầu. Chúc may mắn! + +[Xem thêm công việc của tôi tại đây](https://cryptodocguy.pro/). diff --git a/public/content/translations/zh-tw/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/zh-tw/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..e1b6913fa96 --- /dev/null +++ b/public/content/translations/zh-tw/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "在以太坊上打造你自己的 AI 交易代理" +description: "在本教學中,你將學會如何打造一個簡單的 AI 交易代理。 這個代理會從區塊鏈讀取資訊,根據該資訊向大型語言模型 (LLM) 尋求建議,執行 LLM 建議的交易,然後等待並重複此過程。" +author: Ori Pomerantz +tags: [ "AI", "交易", "代理", "python" ] +skill: intermediate +published: 2026-02-13 +lang: zh-tw +sidebarDepth: 3 +--- + +在本教學中,你將學會如何建立一個簡單的 AI 交易代理。 此代理的運作方式包含以下步驟: + +1. 讀取代幣的當前和過去價格,以及其他潛在的相關資訊 +2. 使用此資訊建立查詢,並附上背景資訊以解釋其可能存在的關聯性 +3. 提交查詢並接收回傳的預測價格 +4. 根據建議進行交易 +5. 等待並重複 + +此代理示範了如何讀取資訊、將其轉譯為能產生可用答案的查詢,並使用該答案。 這些都是 AI 代理所需的步驟。 此代理以 Python 實作,因為它是 AI 領域中最常用的語言。 + +## 為何要這麼做? {#why-do-this} + +自動化交易代理讓開發者可以選擇並執行交易策略。 [AI 代理](/ai-agents)可實現更複雜且動態的交易策略,可能會使用到開發者甚至從未考慮過的資訊和演算法。 + +## 工具 {#tools} + +本教學使用 [Python](https://www.python.org/)、[Web3 函式庫](https://web3py.readthedocs.io/en/stable/) 和 [Uniswap v3](https://github.com/Uniswap/v3-periphery) 來進行報價和交易。 + +### 為何選擇 Python? {#python} + +AI 領域中最廣泛使用的語言是 [Python](https://www.python.org/),所以我們在這裡使用它。 如果你不懂 Python,不用擔心。 這種語言非常清晰,我會詳細解釋它的功能。 + +[Web3 函式庫](https://web3py.readthedocs.io/en/stable/) 是最常見的 Python 以太坊應用程式介面 (API)。 它相當容易使用。 + +### 在區塊鏈上交易 {#trading-on-blockchain} + +有[許多去中心化交易所 (DEX)](/apps/categories/defi/) 讓你在以太坊上交易代幣。 然而,由於[套利](/developers/docs/smart-contracts/composability/#better-user-experience)的關係,它們的匯率往往相似。 + +[Uniswap](https://app.uniswap.org/) 是一個廣泛使用的去中心化交易所 (DEX),我們可以用它來進行報價(查看代幣的相對價值)和交易。 + +### OpenAI {#openai} + +在大型語言模型方面,我選擇從 [OpenAI](https://openai.com/) 開始。 要執行本教學中的應用程式,你需要付費取得 API 存取權限。 最低付款金額 5 美元已綽綽有餘。 + +## 開發,按部就班 {#step-by-step} + +為了簡化開發過程,我們分階段進行。 每個步驟都是 GitHub 中的一個分支。 + +### 入門 {#getting-started} + +以下是在 UNIX 或 Linux(包含 [WSL](https://learn.microsoft.com/en-us/windows/wsl/install))下開始的步驟 + +1. 如果你還沒有,請下載並安裝 [Python](https://www.python.org/downloads/)。 + +2. 複製 GitHub 存放庫。 + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. 安裝 [`uv`](https://docs.astral.sh/uv/getting-started/installation/)。 你系統上的指令可能會有所不同。 + + ```sh + pipx install uv + ``` + +4. 下載函式庫。 + + ```sh + uv sync + ``` + +5. 啟動虛擬環境。 + + ```sh + source .venv/bin/activate + ``` + +6. 為了驗證 Python 和 Web3 是否正常運作,請執行 `python3` 並提供這個程式。 你可以在 `>>>` 提示符號後輸入;不需要建立檔案。 + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### 從區塊鏈讀取 {#read-blockchain} + +下一步是從區塊鏈讀取資料。 要做到這點,你需要切換到 `02-read-quote` 分支,然後使用 `uv` 執行程式。 + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +你應該會收到一個 `Quote` 物件的列表,每個物件都包含一個時間戳、一個價格和資產(目前固定為 `WETH/USDC`)。 + +以下是逐行解釋。 + +```python +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 +``` + +匯入我們需要的庫。 使用到它們時,會在下面解釋。 + +```python +print = functools.partial(print, flush=True) +``` + +將 Python 的 `print` 取代為總是立即清空輸出緩衝區的版本。 這在長時間運行的腳本中很有用,因為我們不想等待狀態更新或除錯輸出。 + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +一個用來連線到主網的 URL。 你可以從[節點即服務](/developers/docs/nodes-and-clients/nodes-as-a-service/)取得,或使用 [Chainlist](https://chainlist.org/chain/1) 上所宣傳的其中一個。 + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +以太坊主網區塊通常每 12 秒產生一個,所以這些是我們預期在一段時間內會產生的區塊數量。 請注意,這不是一個精確的數字。 當[區塊提議者](/developers/docs/consensus-mechanisms/pos/block-proposal/)離線時,該區塊會被跳過,而下一個區塊的時間將是 24 秒。 如果我們想為某個時間戳取得確切的區塊,我們會使用[二元搜尋法](https://en.wikipedia.org/wiki/Binary_search)。 然而,這對我們的目的來說已經足夠接近了。 預測未來並不是一門精確的科學。 + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +週期的規模。 我們每個週期會審視一次報價,並嘗試估計下一個週期結束時的價值。 + +```python +# 我們正在讀取的資金池地址 +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +報價價值取自 Uniswap 3 USDC/WETH 資金池,地址為 [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract)。 這個地址已經是總和檢查碼格式,但最好使用 [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) 來讓程式碼可重複使用。 + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +這些是我們需要聯繫的兩個合約的[應用程式二進位介面 (ABI)](https://docs.soliditylang.org/en/latest/abi-spec.html)。 為了讓程式碼保持簡潔,我們只包含需要呼叫的函式。 + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +初始化 [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) 函式庫並連線到一個以太坊節點。 + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +這是在 Python 中建立資料類別的一種方式。 [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) 資料類型用於連線到合約。 注意 `(frozen=True)`。 在 Python 中,[布林值](https://en.wikipedia.org/wiki/Boolean_data_type)被定義為 `True` 或 `False`,首字母大寫。 這個資料類別是 `frozen`(凍結)的,意思是欄位不能被修改。 + +注意縮排。 與[源自 C 的語言](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages)相反,Python 使用縮排來表示區塊。 Python 直譯器知道下一個定義不屬於這個資料類別,因為它的起始縮排與資料類別的欄位不同。 + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +[`Decimal`](https://docs.python.org/3/library/decimal.html) 類型用於精確處理小數。 + +```python + def get_price(self, block: int) -> Decimal: +``` + +這是在 Python 中定義函式的方式。 這個定義有縮排,表示它仍然是 `PoolInfo` 的一部分。 + +在屬於資料類別的函式中,第一個參數總是 `self`,也就是呼叫它的資料類別實例。 這裡還有另一個參數,也就是區塊號碼。 + +```python + assert block <= w3.eth.block_number, "區塊在未來" +``` + +如果我們能預知未來,我們就不需要用 AI 來交易了。 + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +從 Web3 呼叫以太坊虛擬機 (EVM) 上函式的語法是:`.functions.``().call()`。 參數可以是 EVM 函式的參數(如果有的話;這裡沒有)或用於修改區塊鏈行為的[具名參數](https://en.wikipedia.org/wiki/Named_parameter)。 這裡我們使用其中一個,`block_identifier`,來指定我們希望執行的[區塊號碼](/developers/docs/apis/json-rpc/#default-block)。 + +結果是[這個結構體,以陣列形式呈現](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72)。 第一個值是兩種代幣之間匯率的函式。 + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +為了減少鏈上計算,Uniswap v3 不儲存實際的匯率因子,而是儲存其平方根。 因為 EVM 不支援浮點數運算或分數,所以回應值並非實際數值,而是 price296 + +```python + # (每 token0 的 token1 數量) + return 1/(raw_price * self.decimal_factor) +``` + +我們得到的原始價格是每一個 `token1` 能換到的 `token0` 數量。 在我們的資金池中,`token0` 是 USDC(一種與美元價值相同的穩定幣),而 `token1` 是 [WETH](https://opensea.io/learn/blockchain/what-is-weth)。 我們真正想要的價值是每一 WETH 對應的美元數量,而不是其倒數。 + +小數位數因子是兩種代幣的[小數位數因子](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals)之間的比例。 + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +這個資料類別代表一個報價:特定資產在特定時間點的價格。 在這個階段,`asset` 欄位不重要,因為我們只使用一個資金池,所以只有一種資產。 不過,我們稍後會新增更多資產。 + +```python +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 + ) +``` + +這個函式接收一個地址,並回傳該地址上代幣合約的資訊。 要建立一個新的 [Web3 `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html),我們將地址和應用程式二進位介面 (ABI) 提供給 `w3.eth.contract`。 + +```python +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) + ) +``` + +這個函式回傳關於[特定資金池](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol)的所有所需資訊。 `f""` 語法是一種[格式化字串](https://docs.python.org/3/reference/lexical_analysis.html#f-strings)。 + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +取得一個 `Quote` 物件。 `block_number` 的預設值是 `None`(無值)。 + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +如果沒有指定區塊號碼,就使用 `w3.eth.block_number`,也就是最新的區塊號碼。 這是 [一個 `if` 陳述式](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement) 的語法。 + +看起來似乎直接將預設值設為 `w3.eth.block_number` 會更好,但這行不通,因為那會是函式定義時的區塊號碼。 在長時間運行的代理中,這會是個問題。 + +```python + 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 + ) +``` + +使用 [`datetime` 函式庫](https://docs.python.org/3/library/datetime.html)將其格式化為人類和大型語言模型 (LLM) 都可讀的格式。 使用 [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) 將數值四捨五入至小數點後兩位。 + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +在 Python 中,你可以使用 `list[]` 來定義一個只能包含特定類型的[列表](https://docs.python.org/3/library/stdtypes.html#typesseq-list)。 + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +在 Python 中,[`for` 迴圈](https://docs.python.org/3/tutorial/controlflow.html#for-statements)通常會疊代一個列表。 要尋找報價的區塊號碼列表來自 [`range`](https://docs.python.org/3/library/stdtypes.html#range)。 + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +對於每個區塊號碼,取得一個 `Quote` 物件並將其附加到 `quotes` 列表中。 然後回傳該列表。 + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +這是腳本的主要程式碼。 讀取資金池資訊,取得十二個報價,然後用 [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) 將它們印出。 + +### 建立提示 {#prompt} + +接下來,我們需要將這個報價列表轉換為給 LLM 的提示,並取得一個預期的未來價值。 + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +現在的輸出將會是一個給 LLM 的提示,類似於: + +``` +給定以下報價: +資產:WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +資產:WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +您預期在 2026-02-02T17:56 時,WETH/USDC 的價值會是多少? + +請以單一數字提供您的答案,四捨五入至小數點後兩位, +不要包含任何其他文字。 +``` + +請注意,這裡有兩種資產的報價:`WETH/USDC` 和 `WBTC/WETH`。 新增另一種資產的報價可能會提高預測的準確性。 + +#### 提示的樣貌 {#prompt-explanation} + +這個提示包含三個部分,這在 LLM 提示中相當常見。 + +1. 資訊。 LLM 從訓練中獲得了大量資訊,但通常不是最新的。 這就是我們需要在此處擷取最新報價的原因。 在提示中新增資訊的作法稱為[檢索增強生成 (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation)。 + +2. 實際問題。 這是我們想知道的。 + +3. 輸出格式說明。 通常,LLM 會給我們一個估計值,並解釋它是如何得出的。 這對人類來說更好,但電腦程式只需要最終結果。 + +#### 程式碼說明 {#prompt-code} + +以下是新的程式碼。 + +```python +from datetime import datetime, timezone, timedelta +``` + +我們需要提供 LLM 我們想要估計的時間。 要取得未來「n 分鐘/小時/天」的時間,我們使用 [`timedelta` 類別](https://docs.python.org/3/library/datetime.html#datetime.timedelta)。 + +```python +# 我們正在讀取的資金池地址 +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +我們需要讀取兩個資金池。 + +```python +@dataclass(frozen=True) +class PoolInfo: + . + . + . + reverse: bool = False + + def get_price(self, block: int) -> Decimal: + assert block <= w3.eth.block_number, "區塊在未來" + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (每 token0 的 token1 數量) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +在 WETH/USDC 資金池中,我們想知道需要多少 `token0` (USDC) 才能購買一個 `token1` (WETH)。 在 WETH/WBTC 資金池中,我們想知道需要多少 `token1` (WETH) 才能購買一個 `token0` (WBTC,即包裝比特幣)。 我們需要追蹤資金池的比率是否需要反轉。 + +```python +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 + ) +``` + +要知道一個資金池是否需要反轉,我們需要將其作為輸入傳遞給 `read_pool`。 此外,資產符號也需要正確設定。 + +語法 ` if else ` 是 Python 中相當於[三元條件運算子](https://en.wikipedia.org/wiki/Ternary_conditional_operator)的寫法,在源自 C 的語言中會是 ` ?` ` : `。 + +```python +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 +``` + +這個函式建立一個格式化 `Quote` 物件列表的字串,假設它們都適用於同一個資產。 + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +在 Python 中,[多行字串文字](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp)寫成 `"""` ...。 `"""`。 + +```python +給定以下報價: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +在這裡,我們使用 [MapReduce](https://en.wikipedia.org/wiki/MapReduce) 模式,用 `format_quotes` 為每個報價列表產生一個字串,然後將它們縮減成一個單一字串,以便在提示中使用。 + +```python +您預期在 {expected_time} 時,{asset} 的價值會是多少? + +請以單一數字提供您的答案,四捨五入至小數點後兩位, +不要包含任何其他文字。 + """ +``` + +提示的其餘部分如預期。 + +```python +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 +) +``` + +檢查這兩個資金池並從兩者取得報價。 + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +確定我們想要估計的未來時間點,並建立提示。 + +### 與 LLM 介接 {#interface-llm} + +接下來,我們提示一個實際的 LLM 並接收一個預期的未來價值。 我用 OpenAI 編寫了這個程式,所以如果你想使用不同的供應商,你需要進行調整。 + +1. 取得一個 [OpenAI 帳戶](https://auth.openai.com/create-account) + +2. [為帳戶儲值](https://platform.openai.com/settings/organization/billing/overview) — 撰寫本文時的最低金額為 5 美元 + +3. [建立一個 API 金鑰](https://platform.openai.com/settings/organization/api-keys) + +4. 在命令列中,匯出 API 金鑰,以便你的程式可以使用它 + + ```sh + export OPENAI_API_KEY=sk-<金鑰的其餘部分放在這裡> + ``` + +5. 簽出並執行代理 + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +以下是新的程式碼。 + +```python +from openai import OpenAI + +open_ai = OpenAI() # 用戶端會讀取 OPENAI_API_KEY 環境變數 +``` + +匯入並實例化 OpenAI API。 + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +呼叫 OpenAI API (`open_ai.chat.completions.create`) 來建立回應。 + +```python +expected_price = Decimal(response.choices[0].message.content.strip()) +current_price = wethusdc_quotes[-1].price + +print ("目前價格:", wethusdc_quotes[-1].price) +print(f"在 {future_time},預期價格:{expected_price} USD") + +if (expected_price > current_price): + print(f"買入,我預期價格會上漲 {expected_price - current_price} USD") +else: + print(f"賣出,我預期價格會下跌 {current_price - expected_price} USD") +``` + +輸出價格並提供買入或賣出建議。 + +#### 測試預測 {#testing-the-predictions} + +既然我們能產生預測,我們也可以使用歷史資料來評估我們是否產生了有用的預測。 + +```sh +uv run test-predictor.py +``` + +預期結果類似於: + +``` +2026-01-05T19:50 的預測:預測 3138.93 USD,實際 3218.92 USD,誤差 79.99 USD +2026-01-06T19:56 的預測:預測 3243.39 USD,實際 3221.08 USD,誤差 22.31 USD +2026-01-07T20:02 的預測:預測 3223.24 USD,實際 3146.89 USD,誤差 76.35 USD +2026-01-08T20:11 的預測:預測 3150.47 USD,實際 3092.04 USD,誤差 58.43 USD +. +. +. +2026-01-31T22:33 的預測:預測 2637.73 USD,實際 2417.77 USD,誤差 219.96 USD +2026-02-01T22:41 的預測:預測 2381.70 USD,實際 2318.84 USD,誤差 62.86 USD +2026-02-02T22:49 的預測:預測 2234.91 USD,實際 2349.28 USD,誤差 114.37 USD +29 次預測的平均預測誤差:83.87103448275862068965517241 USD +每次建議的平均變動:4.787931034482758620689655172 USD +變動的標準差:104.42 USD +獲利天數:51.72% +虧損天數:48.28% +``` + +測試器的大部分內容與代理相同,但以下是新增或修改的部分。 + +```python +CYCLES_FOR_TEST = 40 # 針對回測,我們測試多少個週期 + +# 取得大量報價 +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 +) +``` + +我們回溯 `CYCLES_FOR_TEST`(此處指定為 40)天。 + +```python +# 建立預測並與真實歷史進行比對 + +total_error = Decimal(0) +changes = [] +``` + +我們感興趣的有兩種類型的錯誤。 第一種 `total_error`,是預測器所犯錯誤的總和。 + +要理解第二種 `changes`,我們需要記住代理的目的。 它的目的不是預測 WETH/USDC 的比率(ETH 價格)。 而是發出賣出和買入的建議。 如果目前價格是 2000 美元,而它預測明天是 2010 美元,我們不介意實際結果是 2020 美元,我們還能多賺錢。 但我們_確實_介意如果它預測了 2010 美元,並根據該建議購買了 ETH,結果價格卻跌到 1990 美元。 + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +我們只能檢視那些有完整歷史紀錄的案例(用於預測的數值和與之比較的真實世界數值)。 這表示最新的案例必須是從 `CYCLES_BACK` 之前開始的那個。 + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +使用[切片 (slices)](https://www.w3schools.com/python/ref_func_slice.asp) 來取得與代理使用的樣本數量相同的樣本。 從這裡到下一個段落之間的程式碼,與我們在代理中用來取得預測的程式碼相同。 + +```python + 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 +``` + +取得預測價格、實際價格,以及預測當時的價格。 我們需要預測當時的價格,以判斷建議是買入還是賣出。 + +```python + error = abs(predicted_price - real_price) + total_error += error + print (f"針對 {prediction_time} 的預測:預測 {predicted_price} USD,實際 {real_price} USD,誤差 {error} USD") +``` + +計算誤差,並將其加到總和中。 + +```python + recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell' + price_increase = real_price - prediction_time_price + changes.append(price_increase if recomended_action == 'buy' else -price_increase) +``` + +對於 `changes`,我們想要知道買入或賣出一個 ETH 的金錢影響。 所以首先,我們需要確定建議,然後評估實際價格的變化,以及該建議是賺錢(正向變動)還是虧錢(負向變動)。 + +```python +print (f"在 {len(wethusdc_quotes)-CYCLES_BACK} 次預測中的平均預測誤差:{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} USD") +var = sum((x - mean_change) ** 2 for x in changes) / length_changes +print (f"變動的標準差:{var.sqrt().quantize(Decimal("0.01"))} USD") +``` + +回報結果。 + +```python +print (f"獲利天數:{len(list(filter(lambda x: x > 0, changes)))/length_changes:.2%}") +print (f"虧損天數:{len(list(filter(lambda x: x < 0, changes)))/length_changes:.2%}") +``` + +使用 [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) 來計算獲利天數和虧損天數。 結果是一個 filter 物件,我們需要將它轉換成列表才能取得長度。 + +### 提交交易 {#submit-txn} + +現在我們需要實際提交交易。 然而,在系統被證實有效之前,我不想在此時花費真金白銀。 取而代之,我們將建立一個主網的本地分叉,並在該網路上進行「交易」。 + +以下是建立本地分叉並啟用交易的步驟。 + +1. 安裝 [Foundry](https://getfoundry.sh/introduction/installation) + +2. 啟動 [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` 正在 Foundry 的預設 URL (http://localhost:8545) 上監聽,所以我們不需要為我們用來操作區塊鏈的 [`cast` 指令](https://getfoundry.sh/cast/overview)指定 URL。 + +3. 在 `anvil` 中運行時,有十個擁有 ETH 的測試帳戶 — 為第一個帳戶設定環境變數 + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. 這些是我們需要使用的合約。 [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) 是我們用來實際進行交易的 Uniswap v3 合約。 我們可以透過資金池直接交易,但這樣做要簡單得多。 + + 下面兩個變數是 WETH 和 USDC 之間進行交換所需的 Uniswap v3 路徑。 + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. 每個測試帳戶都有 10,000 ETH。 使用 WETH 合約來包裝 1000 ETH,以取得 1000 WETH 用於交易。 + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. 使用 `SwapRouter` 將 500 WETH 交易為 USDC。 + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + `approve` 呼叫會建立一個額度,允許 `SwapRouter` 花費我們的一些代幣。 合約無法監控事件,所以如果我們直接將代幣轉移到 `SwapRouter` 合約,它不會知道自己收到了款項。 取而代之,我們允許 `SwapRouter` 合約花費一定金額,然後由 `SwapRouter` 執行此操作。 這是透過 `SwapRouter` 呼叫的一個函式來完成的,所以它知道是否成功。 + +7. 確認你擁有足夠的兩種代幣。 + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +既然我們有了 WETH 和 USDC,我們就可以實際運行代理了。 + +```sh +git checkout 05-trade +uv run agent.py +``` + +輸出將類似於: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +目前價格:1843.16 +在 2026-02-06T23:07,預期價格:1724.41 USD +交易前帳戶餘額: +USDC 餘額:927301.578272 +WETH 餘額:500 +賣出,我預期價格會下跌 118.75 USD +授權交易已發送:74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +授權交易已上鏈。 +賣出交易已發送:fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +賣出交易已上鏈。 +交易後帳戶餘額: +USDC 餘額:929143.797116 +WETH 餘額:499 +``` + +要實際使用它,你需要做一些小小的改動。 + +- 在第 14 行,將 `MAINNET_URL` 更改為一個真實的存取點,例如 `https://eth.drpc.org` +- 在第 28 行,將 `PRIVATE_KEY` 更改為你自己的私密金鑰 +- 除非你非常富有,並且可以每天為一個未經證實的代理買入或賣出 1 個 ETH,否則你可能需要更改第 29 行以減少 `WETH_TRADE_AMOUNT` + +#### 程式碼說明 {#trading-code} + +以下是新的程式碼。 + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +與我們在步驟 4 中使用的變數相同。 + +```python +WETH_TRADE_AMOUNT=1 +``` + +交易的數量。 + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +要實際進行交易,我們需要 `approve` 函式。 我們也想顯示交易前後的餘額,所以我們還需要 `balanceOf`。 + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +在 `SwapRouter` ABI 中,我們只需要 `exactInput`。 還有一個相關的函式 `exactOutput`,我們可以用它來剛好買入一個 WETH,但為求簡單,我們在這兩種情況下都只使用 `exactInput`。 + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +[`account`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) 和 `SwapRouter` 合約的 Web3 定義。 + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +交易參數。 我們在這裡需要一個函式,因為[隨機數 (nonce)](https://en.wikipedia.org/wiki/Cryptographic_nonce) 每次都必須改變。 + +```python +def approve_token(contract: Contract, amount: int): +``` + +為 `SwapRouter` 授權一個代幣額度。 + +```python + txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params()) + signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY) + tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction) +``` + +這就是我們在 Web3 中發送交易的方式。 首先我們使用[`Contract` 物件](https://web3py.readthedocs.io/en/stable/web3.contract.html)來建構交易。 然後我們使用 [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) 來簽署交易,使用 `PRIVATE_KEY`。 最後,我們使用 [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) 來發送交易。 + +```python + print(f"授權交易已發送:{tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("授權交易已上鏈。") +``` + +[`w3.eth.wait_for_transaction_receipt`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) 會等到交易被挖出。 如果需要,它會回傳收據。 + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +這些是賣出 WETH 時的參數。 + +```python +def make_buy_params(quote: Quote) -> dict: + return { + "path": USDC_TO_WETH, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals, + "amountOutMinimum": 0, + } +``` + +與 `SELL_PARAMS` 不同,買入參數是可以變動的。 輸入金額是 1 WETH 的成本,可在 `quote` 中取得。 + +```python +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"買入交易已發送:{tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("買入交易已上鏈。") + + +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"賣出交易已發送:{tx_hash.hex()}") + w3.eth.wait_for_transaction_receipt(tx_hash) + print("賣出交易已上鏈。") +``` + +`buy()` 和 `sell()` 函式幾乎完全相同。 首先我們為 `SwapRouter` 授權一個足夠的額度,然後用正確的路徑和金額呼叫它。 + +```python +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} 餘額:{Decimal(token0_balance) / Decimal(10 ** wethusdc_pool.token0.decimals)}") + print(f"{wethusdc_pool.token1.symbol} 餘額:{Decimal(token1_balance) / Decimal(10 ** wethusdc_pool.token1.decimals)}") +``` + +回報使用者兩種貨幣的餘額。 + +```python +print("交易前帳戶餘額:") +balances() + +if (expected_price > current_price): + print(f"買入,我預期價格會上漲 {expected_price - current_price} USD") + buy(wethusdc_quotes[-1]) +else: + print(f"賣出,我預期價格會下跌 {current_price - expected_price} USD") + sell() + +print("交易後帳戶餘額:") +balances() +``` + +這個代理目前只能運作一次。 然而,你可以透過 [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) 運行它,或將第 368-400 行包裝在一個迴圈中並使用 [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) 等待下一個週期,使其連續運作。 + +## 可能的改進 {#improvements} + +這不是一個完整的產品版本;它只是一個用來教導基礎知識的範例。 以下是一些改進的建議。 + +### 更聰明的交易 {#smart-trading} + +代理在決定要做什麼時忽略了兩個重要的事實。 + +- _預期變動的幅度_。 如果預期價格會下跌,代理會賣出固定數量的 `WETH`,無論下跌幅度如何。 + 可以說,忽略微小的變化,並根據我們預期價格下跌的幅度來賣出會更好。 +- _目前的投資組合_。 如果你投資組合的 10% 是 WETH,而你認為價格會上漲,那麼購買更多可能是有意義的。 但如果你投資組合的 90% 是 WETH,你可能已經有足夠的曝險,不需要再購買更多。 如果你預期價格會下跌,情況則相反。 + +### 如果你想讓你的交易策略保密怎麼辦? {#secret} + +AI 供應商可以看到你發送給他們 LLM 的查詢,這可能會暴露你用代理開發出的天才交易系統。 一個太多人使用的交易系統是沒有價值的,因為太多人會在你想要買入時嘗試買入(導致價格上漲),並在你想要賣出時嘗試賣出(導致價格下跌)。 + +你可以本地運行一個 LLM,例如使用 [LM-Studio](https://lmstudio.ai/),來避免這個問題。 + +### 從 AI 機器人到 AI 代理 {#bot-to-agent} + +你可以很有力地論證這是一個[AI 機器人,而不是 AI 代理](/ai-agents/#ai-agents-vs-ai-bots)。 它實作了一個相對簡單的策略,依賴於預先定義的資訊。 我們可以啟用自我改進,例如,提供一個 Uniswap v3 資金池及其最新價值的列表,並詢問哪種組合具有最佳的預測價值。 + +### 滑價保護 {#slippage-protection} + +目前沒有[滑價保護](https://uniswapv3book.com/milestone_3/slippage-protection.html)。 如果目前的報價是 2000 美元,預期價格是 2100 美元,代理將會買入。 然而,如果在代理買入前成本上升到 2200 美元,那麼再買入就沒有意義了。 + +要實作滑價保護,請在 [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325) 的第 325 行和第 334 行指定一個 `amountOutMinimum` 值。 + +## 結論 {#conclusion} + +希望你現在已經學會足夠的知識來開始使用 AI 代理了。 這並不是對這個主題的全面概述;有專門的書籍來探討這個主題,但這足以讓你入門。 祝您好運! + +[在此查看我的更多作品](https://cryptodocguy.pro/)。 diff --git a/public/content/translations/zh/developers/tutorials/ai-trading-agent/index.md b/public/content/translations/zh/developers/tutorials/ai-trading-agent/index.md new file mode 100644 index 00000000000..bf082a9d128 --- /dev/null +++ b/public/content/translations/zh/developers/tutorials/ai-trading-agent/index.md @@ -0,0 +1,980 @@ +--- +title: "在以太坊上创建你自己的 AI 交易代理" +description: "在本教程中,您将学习如何创建一个简单的 AI 交易代理。 该代理从区块链读取信息,根据该信息向大语言模型 (LLM) 请求建议,执行 LLM 推荐的交易,然后等待并重复。" +author: Ori Pomerantz +tags: [ "AI", "交易", "代理", "python" ] +skill: intermediate +published: 2026-02-13 +lang: zh +sidebarDepth: 3 +--- + +在本教程中,您将学习如何构建一个简单的 AI 交易代理。 该代理的工作步骤如下: + +1. 读取代币的当前和过去价格以及其他潜在的相关信息 +2. 利用此信息和用于解释其相关性的背景信息来构建查询 +3. 提交查询并接收预测价格 +4. 根据建议进行交易 +5. 等待并重复 + +此代理演示了如何读取信息,将其转换为可生成可用答案的查询,以及如何使用该答案。 所有这些都是 AI 代理所需的步骤。 此代理使用 Python 实现,因为它是人工智能领域最常用的语言。 + +## 为什么要这样做? {#why-do-this} + +自动化交易代理允许开发者选择并执行交易策略。 [AI 代理](/ai-agents) 允许采用更复杂、更动态的交易策略,可能会使用开发者甚至没有考虑过使用的信息和算法。 + +## 工具 {#tools} + +本教程使用 [Python](https://www.python.org/)、[Web3 程序库](https://web3py.readthedocs.io/en/stable/) 和 [Uniswap v3](https://github.com/Uniswap/v3-periphery) 进行报价和交易。 + +### 为什么使用 Python? {#python} + +AI 领域使用最广泛的语言是 [Python](https://www.python.org/),所以我们在这里使用它。 如果您不了解 Python,也不用担心。 这种语言非常清晰易懂,我会准确地解释它的作用。 + +[Web3 程序库](https://web3py.readthedocs.io/en/stable/) 是最常见的 Python 以太坊应用程序接口。 它非常易于使用。 + +### 在区块链上交易 {#trading-on-blockchain} + +有[许多去中心化交易所 (DEX)](/apps/categories/defi/) 可以让您在以太坊上交易代币。 然而,由于存在[套利](/developers/docs/smart-contracts/composability/#better-user-experience),它们的汇率往往类似。 + +[Uniswap](https://app.uniswap.org/) 是一个广泛使用的去中心化交易所 (DEX),我们可以用它来进行报价(查看代币的相对价值)和交易。 + +### OpenAI {#openai} + +对于大语言模型,我选择从 [OpenAI](https://openai.com/) 开始。 要运行本教程中的应用,您需要付费访问其应用程序接口。 最低支付 5 美元就绰绰有余了。 + +## 逐步开发 {#step-by-step} + +为简化开发,我们分阶段进行。 每个步骤都是 GitHub 中的一个分支。 + +### 入门 {#getting-started} + +在 UNIX 或 Linux(包括 [WSL](https://learn.microsoft.com/en-us/windows/wsl/install))下,您可以按照以下步骤开始 + +1. 如果您还没有安装 [Python](https://www.python.org/downloads/),请下载并安装它。 + +2. 克隆 GitHub 存储库。 + + ```sh + git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started + cd 260215-ai-agent + ``` + +3. 安装 [`uv`](https://docs.astral.sh/uv/getting-started/installation/)。 您系统上的命令可能有所差异。 + + ```sh + pipx install uv + ``` + +4. 下载程序库。 + + ```sh + uv sync + ``` + +5. 激活虚拟环境。 + + ```sh + source .venv/bin/activate + ``` + +6. 为了验证 Python 和 Web3 是否正常工作,请运行 `python3` 并向其提供此程序。 您可以在 `>>>` 提示符处输入,无需创建文件。 + + ```python + from web3 import Web3 + MAINNET_URL = "https://eth.drpc.org" + w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) + w3.eth.block_number + quit() + ``` + +### 从区块链读取 {#read-blockchain} + +下一步是从区块链中读取信息。 为此,您需要切换到 `02-read-quote` 分支,然后使用 `uv` 运行程序。 + +```sh +git checkout 02-read-quote +uv run agent.py +``` + +您应该会收到一个 `Quote` 对象列表,每个对象都包含一个时间戳、一个价格和一个资产(目前始终为 `WETH/USDC`)。 + +下面是逐行解释。 + +```python +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 +``` + +导入我们需要的程序库。 在使用时,下面将对它们进行解释。 + +```python +print = functools.partial(print, flush=True) +``` + +用一个总是立即刷新输出的版本替换 Python 的 `print`。 这在长时间运行的脚本中很有用,因为我们不想等待状态更新或调试输出。 + +```python +MAINNET_URL = "https://eth.drpc.org" +``` + +一个访问主网的 URL。 您可以从[节点即服务](/developers/docs/nodes-and-clients/nodes-as-a-service/)获取一个,或使用 [Chainlist](https://chainlist.org/chain/1) 中宣传的节点。 + +```python +BLOCK_TIME_SECONDS = 12 +MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS) +HOUR_BLOCKS = MINUTE_BLOCKS * 60 +DAY_BLOCKS = HOUR_BLOCKS * 24 +``` + +以太坊主网区块通常每十二秒产生一个,因此这些是我们预期在一段时间内产生的区块数量。 请注意,这不是一个精确的数字。 当[区块提议者](/developers/docs/consensus-mechanisms/pos/block-proposal/)宕机时,该区块将被跳过,下一个区块的时间将是 24 秒。 如果我们想获取某个时间戳的精确区块,我们会使用[二分查找](https://en.wikipedia.org/wiki/Binary_search)。 然而,这对我们的目的来说已经足够接近了。 预测未来并非一门精确的科学。 + +```python +CYCLE_BLOCKS = DAY_BLOCKS +``` + +周期的大小。 我们每个周期审查一次报价,并尝试估算下一个周期结束时的价值。 + +```python +# 我们正在读取的池子的地址 +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +``` + +报价取自地址为 [`0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640`](https://eth.blockscout.com/address/0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640?tab=read_write_contract) 的 Uniswap 3 USDC/WETH 池子。 此地址已经是校验和格式,但最好使用 [`Web3.to_checksum_address`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_checksum_address) 以使代码可重用。 + +```python +POOL_ABI = [ + { "name": "slot0", ... }, + { "name": "token0", ... }, + { "name": "token1", ... }, +] + +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... } +] +``` + +这些是我们需要联系的两个合约的 [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html)。 为保持代码简洁,我们只包含需要调用的函数。 + +```python +w3 = Web3(Web3.HTTPProvider(MAINNET_URL)) +``` + +初始化 [`Web3`](https://web3py.readthedocs.io/en/stable/quickstart.html#remote-providers) 程序库并连接到一个以太坊节点。 + +```python +@dataclass(frozen=True) +class ERC20Token: + address: str + symbol: str + decimals: int + contract: Contract +``` + +这是在 Python 中创建数据类的一种方法。 [`Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html) 数据类型用于连接到合约。 注意 `(frozen=True)`。 在 Python 中,[布尔值](https://en.wikipedia.org/wiki/Boolean_data_type) 定义为大写的 `True` 或 `False`。 这个数据类是 `frozen` 的,意味着字段不能被修改。 + +注意缩进。 与 [C 派生语言](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages) 不同,Python 使用缩进来表示代码块。 Python 解释器知道下面的定义不属于这个数据类,因为它没有与数据类字段相同的缩进。 + +```python +@dataclass(frozen=True) +class PoolInfo: + address: str + token0: ERC20Token + token1: ERC20Token + contract: Contract + asset: str + decimal_factor: Decimal = 1 +``` + +[`Decimal`](https://docs.python.org/3/library/decimal.html) 类型用于精确处理小数。 + +```python + def get_price(self, block: int) -> Decimal: +``` + +这是在 Python 中定义函数的方式。 该定义被缩进以表明它仍然是 `PoolInfo` 的一部分。 + +在作为数据类一部分的函数中,第一个参数总是 `self`,即调用此函数的数据类实例。 这里有另一个参数,即区块号。 + +```python + assert block <= w3.eth.block_number, "区块在未来" +``` + +如果我们能预知未来,我们就不需要用 AI 来进行交易了。 + +```python + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) +``` + +从 Web3 调用 EVM 上的函数的语法是:`.functions.().call()`。 参数可以是 EVM 函数的参数(如果有的话;这里没有),也可以是用于修改区块链行为的[命名参数](https://en.wikipedia.org/wiki/Named_parameter)。 这里我们使用一个参数 `block_identifier` 来指定我们希望运行的[区块号](/developers/docs/apis/json-rpc/#default-block)。 + +结果是[这个结构体,以数组形式](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L56-L72)。 第一个值是两个代币之间汇率的函数。 + +```python + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 +``` + +为减少链上计算,Uniswap v3 不存储实际的兑换系数,而是存储其平方根。 由于 EVM 不支持浮点数学或分数,因此响应不是实际值,而是 price296 + +```python + # (每个 token0 对应的 token1 数量) + return 1/(raw_price * self.decimal_factor) +``` + +我们得到的原始价格是每个 `token1` 可以兑换的 `token0` 的数量。 在我们的池子中,`token0` 是 USDC(与美元等值的稳定币),`token1` 是 [WETH](https://opensea.io/learn/blockchain/what-is-weth)。 我们真正想要的价值是每 WETH 对应的美元数量,而不是其倒数。 + +小数位数因数是两个代币的[小数位数因数](https://docs.openzeppelin.com/contracts/4.x/erc20#a-note-on-decimals)之间的比率。 + +```python +@dataclass(frozen=True) +class Quote: + timestamp: str + price: Decimal + asset: str +``` + +这个数据类代表一个报价:特定资产在特定时间点的价格。 此时,`asset` 字段是无关紧要的,因为我们使用单个池子,因此只有一个资产。 但是,我们稍后会添加更多资产。 + +```python +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 + ) +``` + +该函数接受一个地址,并返回该地址上代币合约的信息。 要创建一个新的 [Web3 `Contract`](https://web3py.readthedocs.io/en/stable/web3.contract.html),我们需要向 `w3.eth.contract` 提供地址和 ABI。 + +```python +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) + ) +``` + +该函数返回我们需要的关于[特定池子](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol)的所有信息。 `f""` 语法是[格式化字符串](https://docs.python.org/3/reference/lexical_analysis.html#f-strings)。 + +```python +def get_quote(pool: PoolInfo, block_number: int = None) -> Quote: +``` + +获取一个 `Quote` 对象。 `block_number` 的默认值为 `None`(无值)。 + +```python + if block_number is None: + block_number = w3.eth.block_number +``` + +如果未指定区块号,则使用 `w3.eth.block_number`,即最新的区块号。 这是 [`if` 语句](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement)的语法。 + +看起来似乎将默认值设置为 `w3.eth.block_number` 会更好,但这样做效果不佳,因为这会是函数定义时的区块号。 在一个长期运行的代理中,这会是一个问题。 + +```python + 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 + ) +``` + +使用 [`datetime` 程序库](https://docs.python.org/3/library/datetime.html)将其格式化为人类和大型语言模型 (LLM) 可读的格式。 使用 [`Decimal.quantize`](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) 将值四舍五入到两位小数。 + +```python +def get_quotes(pool: PoolInfo, start_block: int, end_block: int, step: int) -> list[Quote]: +``` + +在 Python 中,您可以使用 `list[]` 定义一个只能包含特定类型的[列表](https://docs.python.org/3/library/stdtypes.html#typesseq-list)。 + +```python + quotes = [] + for block in range(start_block, end_block + 1, step): +``` + +在 Python 中,[`for` 循环](https://docs.python.org/3/tutorial/controlflow.html#for-statements)通常遍历一个列表。 用于查找报价的区块号列表来自 [`range`](https://docs.python.org/3/library/stdtypes.html#range)。 + +```python + quote = get_quote(pool, block) + quotes.append(quote) + return quotes +``` + +对于每个区块号,获取一个 `Quote` 对象并将其附加到 `quotes` 列表中。 然后返回该列表。 + +```python +pool = read_pool(WETHUSDC_ADDRESS) +quotes = get_quotes( + pool, + w3.eth.block_number - 12*CYCLE_BLOCKS, + w3.eth.block_number, + CYCLE_BLOCKS +) + +pprint(quotes) +``` + +这是脚本的主代码。 读取池子信息,获取十二个报价,并使用 [`pprint`](https://docs.python.org/3/library/pprint.html#pprint.pprint) 打印它们。 + +### 创建提示 {#prompt} + +接下来,我们需要将此报价列表转换为对大语言模型 (LLM) 的提示,并获取预期的未来价值。 + +```sh +git checkout 03-create-prompt +uv run agent.py +``` + +现在的输出将是给大语言模型 (LLM) 的提示,类似如下: + +``` +给定以下报价: +资产:WETH/USDC + 2026-01-20T16:34 3016.21 + . + . + . + 2026-02-01T17:49 2299.10 + +资产:WBTC/WETH + 2026-01-20T16:34 29.84 + . + . + . + 2026-02-01T17:50 33.46 + + +您预计在 2026-02-02T17:56 时 WETH/USDC 的价值会是多少? + +请以一个四舍五入到两位小数的数字作为您的答案, +不要包含任何其他文本。 +``` + +请注意,这里有两种资产的报价:`WETH/USDC` 和 `WBTC/WETH`。 添加另一种资产的报价可能会提高预测准确性。 + +#### 提示是什么样的 {#prompt-explanation} + +此提示包含三个部分,这在 LLM 提示中非常常见。 + +1. 信息。 LLM 从训练中获得了大量信息,但通常没有最新的信息。 这就是我们在这里需要检索最新报价的原因。 向提示中添加信息称为[检索增强生成 (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation)。 + +2. 实际问题。 这是我们想知道的。 + +3. 输出格式说明。 通常情况下,LLM 会给我们一个估算值,并解释它是如何得出的。 这对人类来说更好,但计算机程序只需要最终结果。 + +#### 代码说明 {#prompt-code} + +这是新的代码。 + +```python +from datetime import datetime, timezone, timedelta +``` + +我们需要向 LLM 提供我们想要估算的时间。 要获得未来“n 分钟/小时/天”的时间,我们使用 [`timedelta` 类](https://docs.python.org/3/library/datetime.html#datetime.timedelta)。 + +```python +# 我们正在读取的池子的地址 +WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") +WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD") +``` + +我们需要读取两个池子。 + +```python +@dataclass(frozen=True) +class PoolInfo: + . + . + . + reverse: bool = False + + def get_price(self, block: int) -> Decimal: + assert block <= w3.eth.block_number, "区块在未来" + sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0]) + raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (token1 per token0) + if self.reverse: + return 1/(raw_price * self.decimal_factor) + else: + return raw_price * self.decimal_factor +``` + +在 WETH/USDC 池子中,我们想知道需要多少 `token0` (USDC) 来购买一个 `token1` (WETH)。 在 WETH/WBTC 池子中,我们想知道需要多少 `token1` (WETH) 来购买一个 `token0`(WBTC,即包装比特币)。 我们需要跟踪池子的比率是否需要反转。 + +```python +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 + ) +``` + +要了解是否需要反转池子,我们将其作为 `read_pool` 的输入。 此外,资产符号需要正确设置。 + +语法 ` if else ` 是 Python 中[三元条件运算符](https://en.wikipedia.org/wiki/Ternary_conditional_operator) 的等价形式,在 C 派生语言中为 ` ? : `。 + +```python +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 +``` + +该函数构建一个字符串来格式化 `Quote` 对象列表,假设它们都适用于同一资产。 + +```python +def make_prompt(quotes: list[list[Quote]], expected_time: str, asset: str) -> str: + return f""" +``` + +在 Python 中,[多行字符串字面量](https://www.w3schools.com/python/gloss_python_multi_line_strings.asp)写为 `"""` .... `"""`。 + +```python +给定以下报价: +{ + functools.reduce(lambda acc, q: acc + '\n' + q, + map(lambda q: format_quotes(q), quotes)) +} +``` + +在这里,我们使用 [MapReduce](https://en.wikipedia.org/wiki/MapReduce) 模式,通过 `format_quotes` 为每个报价列表生成一个字符串,然后将它们归约为一个字符串以在提示中使用。 + +```python +您预计 {asset} 在 {expected_time} 的价值会是多少? + +请以一个四舍五入到两位小数的数字作为您的答案, +不要包含任何其他文本。 + """ +``` + +提示的其余部分与预期一致。 + +```python +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 +) +``` + +查看两个池子并从两者获取报价。 + +```python +future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16] + +print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset)) +``` + +确定我们想要估算的未来时间点,并创建提示。 + +### 与 LLM 交互 {#interface-llm} + +接下来,我们提示一个实际的大语言模型 (LLM),并接收预期的未来价值。 我用 OpenAI 编写了这个程序,所以如果你想使用不同的提供商,你需要进行调整。 + +1. 获取一个 [OpenAI 帐户](https://auth.openai.com/create-account) + +2. [为帐户注资](https://platform.openai.com/settings/organization/billing/overview)——在撰写本文时,最低金额为 5 美元 + +3. [创建应用程序接口密钥](https://platform.openai.com/settings/organization/api-keys) + +4. 在命令行中,导出应用程序接口密钥,以便您的程序可以使用它 + + ```sh + export OPENAI_API_KEY=sk-<密钥的其余部分写在这里> + ``` + +5. 检出并运行代理 + + ```sh + git checkout 04-interface-llm + uv run agent.py + ``` + +这是新的代码。 + +```python +from openai import OpenAI + +open_ai = OpenAI() # 客户端读取 OPENAI_API_KEY 环境变量 +``` + +导入并实例化 OpenAI 应用程序接口。 + +```python +response = open_ai.chat.completions.create( + model="gpt-4-turbo", + messages=[ + {"role": "user", "content": prompt} + ], + temperature=0.0, + max_tokens=16, +) +``` + +调用 OpenAI 应用程序接口 (`open_ai.chat.completions.create`) 来创建响应。 + +```python +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") +``` + +输出价格并提供买入或卖出建议。 + +#### 测试预测 {#testing-the-predictions} + +既然我们可以生成预测,我们也可以使用历史数据来评估我们是否产生了有用的预测。 + +```sh +uv run test-predictor.py +``` + +预期结果类似: + +``` +2026-01-05T19:50 的预测:预测 3138.93 美元,实际 3218.92 美元,误差 79.99 美元 +2026-01-06T19:56 的预测:预测 3243.39 美元,实际 3221.08 美元,误差 22.31 美元 +2026-01-07T20:02 的预测:预测 3223.24 美元,实际 3146.89 美元,误差 76.35 美元 +2026-01-08T20:11 的预测:预测 3150.47 美元,实际 3092.04 美元,误差 58.43 美元 +. +. +. +2026-01-31T22:33 的预测:预测 2637.73 美元,实际 2417.77 美元,误差 219.96 美元 +2026-02-01T22:41 的预测:预测 2381.70 美元,实际 2318.84 美元,误差 62.86 美元 +2026-02-02T22:49 的预测:预测 2234.91 美元,实际 2349.28 美元,误差 114.37 美元 +29 次预测的平均预测误差:83.87103448275862068965517241 美元 +每个建议的平均变化:4.787931034482758620689655172 美元 +变化的 标准方差:104.42 美元 +盈利天数:51.72% +亏损天数:48.28% +``` + +测试器的大部分内容与代理相同,但以下是新的或修改过的部分。 + +```python +CYCLES_FOR_TEST = 40 # 对于回测,我们测试多少个周期 + +# 获取大量报价 +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 +) +``` + +我们回顾 `CYCLES_FOR_TEST`(此处指定为 40)天前的数据。 + +```python +# 创建预测并与真实历史进行比较 + +total_error = Decimal(0) +changes = [] +``` + +我们感兴趣的错误有两种类型。 第一种,`total_error`,就是预测器所犯错误的总和。 + +要理解第二种,`changes`,我们需要记住代理的目的。 它不是为了预测 WETH/USDC 比率(ETH 价格)。 而是为了发出卖出和买入的建议。 如果当前价格是 2000 美元,而它预测明天是 2010 美元,如果实际结果是 2020 美元并且我们赚了额外的钱,我们不会介意。 但如果它预测为 2010 美元,并基于该建议购买了 ETH,而价格下跌到 1990 美元,我们_确实_会介意。 + +```python +for index in range(0,len(wethusdc_quotes)-CYCLES_BACK): +``` + +我们只能看那些历史记录完整(用于预测的值和与之比较的真实世界值都可用)的情况。 这意味着最新的案例必须是 `CYCLES_BACK` 之前开始的那个。 + +```python + wethusdc_slice = wethusdc_quotes[index:index+CYCLES_BACK] + wethwbtc_slice = wethwbtc_quotes[index:index+CYCLES_BACK] +``` + +使用[切片](https://www.w3schools.com/python/ref_func_slice.asp)来获取与代理使用的样本数量相同的样本。 这里到下一个片段之间的代码与我们在代理中使用的获取预测的代码相同。 + +```python + 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 +``` + +获取预测价格、真实价格以及预测时的价格。 我们需要预测时的价格来决定建议是买入还是卖出。 + +```python + 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") +``` + +计算误差,并将其加到总误差中。 + +```python + recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell' + price_increase = real_price - prediction_time_price + changes.append(price_increase if recomended_action == 'buy' else -price_increase) +``` + +对于 `changes`,我们想要的是买入或卖出一个 ETH 的货币影响。 因此,首先我们需要确定建议,然后评估实际价格的变化,以及建议是盈利(正向变化)还是亏损(负向变化)。 + +```python +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") +``` + +报告结果。 + +```python +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%}") +``` + +使用 [`filter`](https://www.w3schools.com/python/ref_func_filter.asp) 来计算盈利天数和亏损天数。 结果是一个过滤器对象,我们需要将其转换为列表以获取其长度。 + +### 提交交易 {#submit-txn} + +现在我们需要实际提交交易。 然而,在系统被证明之前,我不想在这个阶段花真钱。 相反,我们将在本地创建一个主网分叉,并在此网络上进行“交易”。 + +以下是创建本地分叉并启用交易的步骤。 + +1. 安装 [Foundry](https://getfoundry.sh/introduction/installation) + +2. 启动 [`anvil`](https://getfoundry.sh/anvil/overview) + + ```sh + anvil --fork-url https://eth.drpc.org --block-time 12 + ``` + + `anvil` 正在 Foundry 的默认 URL http://localhost:8545 上监听,所以我们不需要为用于操作区块链的 [`cast` 命令](https://getfoundry.sh/cast/overview)指定 URL。 + +3. 在 `anvil` 中运行时,有十个拥有 ETH 的测试帐户——为第一个帐户设置环境变量 + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ADDRESS=`cast wallet address $PRIVATE_KEY` + ``` + +4. 这些是我们需要使用的合约。 [`SwapRouter`](https://github.com/Uniswap/v3-periphery/blob/main/contracts/SwapRouter.sol) 是我们用来实际交易的 Uniswap v3 合约。 我们可以直接通过池子进行交易,但这要容易得多。 + + 底部的两个变量是在 WETH 和 USDC 之间进行交换所需的 Uniswap v3 路径。 + + ```sh + WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 + SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564 + WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ``` + +5. 每个测试帐户都有 10,000 ETH。 使用 WETH 合约包装 1000 ETH 以获得 1000 WETH 用于交易。 + + ```sh + cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY + ``` + +6. 使用 `SwapRouter` 交易 500 WETH 以换取 USDC。 + + ```sh + cast send $WETH_ADDRESS "approve(address,uint256)" $SWAP_ROUTER 500ether --private-key $PRIVATE_KEY + MAXINT=`cast max-int uint256` + cast send $SWAP_ROUTER \ + "exactInput((bytes,address,uint256,uint256,uint256))" \ + "($WETH_TO_USDC,$ADDRESS,$MAXINT,500ether,1000000)" \ + --private-key $PRIVATE_KEY + ``` + + `approve` 调用创建了一个许可,允许 `SwapRouter` 花费我们的一些代币。 合约无法监听事件,因此如果我们直接将代币转移到 `SwapRouter` 合约,它将不知道已收到付款。 相反,我们允许 `SwapRouter` 合约花费一定金额,然后由 `SwapRouter` 执行。 这是通过 `SwapRouter` 调用的一个函数完成的,因此它知道是否成功。 + +7. 验证您是否拥有足够的两种代币。 + + ```sh + cast call $WETH_ADDRESS "balanceOf(address)" $ADDRESS | cast from-wei + echo `cast call $USDC_ADDRESS "balanceOf(address)" $ADDRESS | cast to-dec`/10^6 | bc + ``` + +现在我们有了 WETH 和 USDC,可以实际运行代理了。 + +```sh +git checkout 05-trade +uv run agent.py +``` + +输出将类似如下: + +``` +(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py +当前价格:1843.16 +2026-02-06T23:07,预期价格:1724.41 美元 +交易前帐户余额: +USDC 余额:927301.578272 +WETH 余额:500 +卖出,我预计价格将下跌 118.75 美元 +批准交易已发送:74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f +批准交易已挖出。 +卖出交易已发送:fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf +卖出交易已挖出。 +交易后帐户余额: +USDC 余额:929143.797116 +WETH 余额:499 +``` + +要实际使用它,您需要进行一些小的更改。 + +- 在第 14 行,将 `MAINNET_URL` 更改为真实的访问点,例如 `https://eth.drpc.org` +- 在第 28 行,将 `PRIVATE_KEY` 更改为您自己的私钥 +- 除非您非常富有,并且可以为一个未经证实的代理每天买卖 1 个 ETH,否则您可能希望更改第 29 行以减少 `WETH_TRADE_AMOUNT` + +#### 代码说明 {#trading-code} + +这是新的代码。 + +```python +SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564") +WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +我们在第 4 步中使用的相同变量。 + +```python +WETH_TRADE_AMOUNT=1 +``` + +交易金额。 + +```python +ERC20_ABI = [ + { "name": "symbol", ... }, + { "name": "decimals", ... }, + { "name": "balanceOf", ...}, + { "name": "approve", ...} +] +``` + +要实际交易,我们需要 `approve` 函数。 我们还希望显示交易前后的余额,因此我们还需要 `balanceOf`。 + +```python +SWAP_ROUTER_ABI = [ + { "name": "exactInput", ...}, +] +``` + +在 `SwapRouter` ABI 中,我们只需要 `exactInput`。 还有一个相关函数 `exactOutput`,我们可以用它来精确购买一个 WETH,但为简单起见,我们在这两种情况下都只使用 `exactInput`。 + +```python +account = w3.eth.account.from_key(PRIVATE_KEY) +swap_router = w3.eth.contract( + address=SWAP_ROUTER_ADDRESS, + abi=SWAP_ROUTER_ABI +) +``` + +Web3 为 [`account`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html) 和 `SwapRouter` 合约的定义。 + +```python +def txn_params() -> dict: + return { + "from": account.address, + "value": 0, + "gas": 300000, + "nonce": w3.eth.get_transaction_count(account.address), + } +``` + +交易参数。 我们在这里需要一个函数,因为[随机数](https://en.wikipedia.org/wiki/Cryptographic_nonce)每次都必须改变。 + +```python +def approve_token(contract: Contract, amount: int): +``` + +批准 `SwapRouter` 的代币许可。 + +```python + txn = contract.functions.approve(SWAP_ROUTER_ADDRESS, amount).build_transaction(txn_params()) + signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY) + tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction) +``` + +这是我们在 Web3 中发送交易的方式。 首先我们使用 [`Contract` 对象](https://web3py.readthedocs.io/en/stable/web3.contract.html) 来构建交易。 然后我们使用 [`web3.eth.account.sign_transaction`](https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction) 来使用 `PRIVATE_KEY` 签署交易。 最后,我们使用 [`w3.eth.send_raw_transaction`](https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction) 来发送交易。 + +```python + 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`](https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.wait_for_transaction_receipt) 会等到交易被挖出。 如果需要,它会返回收据。 + +```python +SELL_PARAMS = { + "path": WETH_TO_USDC, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": WETH_TRADE_AMOUNT * 10 ** wethusdc_pool.token1.decimals, + "amountOutMinimum": 0, +} +``` + +这些是卖出 WETH 时的参数。 + +```python +def make_buy_params(quote: Quote) -> dict: + return { + "path": USDC_TO_WETH, + "recipient": account.address, + "deadline": 2**256 - 1, + "amountIn": int(quote.price*WETH_TRADE_AMOUNT) * 10**wethusdc_pool.token0.decimals, + "amountOutMinimum": 0, + } +``` + +与 `SELL_PARAMS` 不同,买入参数可能会改变。 输入金额是 1 WETH 的成本,可在 `quote` 中获取。 + +```python +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.") +``` + +`buy()` 和 `sell()` 函数几乎相同。 我们首先为 `SwapRouter` 批准足够的许可,然后用正确的路径和金额调用它。 + +```python +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)}") +``` + +报告两种货币的用户余额。 + +```python +print("交易前帐户余额:") +balances() + +if (expected_price > current_price): + print(f"买入,我预计价格将上涨 {expected_price - current_price} 美元") + buy(wethusdc_quotes[-1]) +else: + print(f"卖出,我预计价格将下跌 {current_price - expected_price} 美元") + sell() + +print("交易后帐户余额:") +balances() +``` + +该代理目前只能工作一次。 然而,您可以通过从 [`crontab`](https://man7.org/linux/man-pages/man1/crontab.1.html) 运行它,或者将 368-400 行包装在一个循环中并使用 [`time.sleep`](https://docs.python.org/3/library/time.html#time.sleep) 等待下一个周期的时间,来使其连续工作。 + +## 可能的改进 {#improvements} + +这不是一个完整的生产版本;它仅仅是一个教授基础知识的例子。 以下是一些改进建议。 + +### 更智能的交易 {#smart-trading} + +代理在决定做什么时忽略了两个重要的事实。 + +- _预期变化的幅度_。 如果价格预期下跌,代理会卖出固定数量的 `WETH`,而不管下跌的幅度如何。 + 可以说,更好的做法是忽略微小的变化,并根据我们预期的价格下跌幅度来决定卖出。 +- _当前投资组合_。 如果你的投资组合中有 10% 是 WETH,并且你认为价格会上涨,那么买入更多可能是明智的。 但如果你的投资组合中有 90% 是 WETH,你可能已经有足够的敞口,没有必要再买更多。 如果你预期价格会下跌,情况则相反。 + +### 如果你想对你的交易策略保密怎么办? {#secret} + +AI 供应商可以看到你发送给他们的大语言模型 (LLM) 的查询,这可能会暴露你用代理开发的绝妙交易系统。 一个太多人使用的交易系统是毫无价值的,因为太多人会在你想买的时候尝试买入(价格上涨),在你像卖的时候尝试卖出(价格下跌)。 + +您可以在本地运行一个大语言模型 (LLM),例如使用 [LM-Studio](https://lmstudio.ai/),以避免此问题。 + +### 从 AI 机器人到 AI 代理 {#bot-to-agent} + +你可以很有力地论证这[是一个 AI 机器人,而不是一个 AI 代理](/ai-agents/#ai-agents-vs-ai-bots)。 它实现了一个依赖于预定义信息的相对简单的策略。 我们可以启用自我改进,例如,通过提供 Uniswap v3 池子及其最新值的列表,并询问哪种组合具有最佳的预测价值。 + +### 滑点保护 {#slippage-protection} + +目前没有[滑点保护](https://uniswapv3book.com/milestone_3/slippage-protection.html)。 如果当前报价是 2000 美元,预期价格是 2100 美元,代理将会买入。 然而,如果在代理买入之前,成本上升到 2200 美元,那么再买入就没有意义了。 + +要实现滑点保护,请在 [`agent.py`](https://github.com/qbzzt/260215-ai-agent/blob/05-trade/agent.py#L325) 的第 325 行和 334 行中指定一个 `amountOutMinimum` 值。 + +## 结论 {#conclusion} + +希望现在您已经足够了解,可以开始使用 AI 代理了。 这不是对该主题的全面概述;有整本书专门讨论这个问题,但这足以让您入门。 祝你好运! + +[点击此处查看我的更多作品](https://cryptodocguy.pro/)。