تخطٍ إلى المحتوى الرئيسي

اصنع وكيل تداول الذكاء الاصطناعي الخاص بك على الإيثريوم

الذكاء الاصطناعي
تداول
وكيل
python
Intermediate
Ori Pomerantz
13 فبراير 2026
22 minute read

في هذا البرنامج التعليمي، ستتعلم كيفية إنشاء وكيل تداول بسيط للذكاء الاصطناعي. يعمل هذا الوكيل باستخدام هذه الخطوات:

  1. قراءة الأسعار الحالية والسابقة لرمز مميز، بالإضافة إلى معلومات أخرى قد تكون ذات صلة
  2. إنشاء استعلام بهذه المعلومات، بالإضافة إلى معلومات أساسية لشرح مدى صلتها بالموضوع
  3. إرسال الاستعلام واستلام سعر متوقع
  4. التداول بناءً على التوصية
  5. انتظر وكرر

يوضح هذا الوكيل كيفية قراءة المعلومات وترجمتها إلى استعلام ينتج عنه إجابة قابلة للاستخدام واستخدام تلك الإجابة. كل هذه خطوات مطلوبة لوكيل الذكاء الاصطناعي. يتم تنفيذ هذا الوكيل بلغة بايثون Python لأنها اللغة الأكثر شيوعًا المستخدمة في الذكاء الاصطناعي.

لماذا نفعل هذا؟

تسمح وكلاء التداول الآلي للمطورين باختيار وتنفيذ استراتيجية تداول. تسمح وكلاء الذكاء الاصطناعي باستراتيجيات تداول أكثر تعقيدًا وديناميكية، ومن المحتمل استخدام معلومات وخوارزميات لم يفكر المطور في استخدامها.

الأدوات

يستخدم هذا البرنامج التعليمي Python (opens in a new tab) ومكتبة Web3 (opens in a new tab) وUniswap v3 (opens in a new tab) للحصول على عروض الأسعار والتداول.

لماذا لغة Python؟

اللغة الأكثر استخدامًا في الذكاء الاصطناعي هي Python (opens in a new tab)، لذلك نستخدمها هنا. لا تقلق إذا كنت لا تعرف لغة بايثون Python. اللغة واضحة جدًا، وسأشرح بالضبط ما تفعله.

تُعد مكتبة Web3 (opens in a new tab) هي واجهة برمجة تطبيقات Ethereum API الأكثر شيوعًا في لغة Python. إنه سهل الاستخدام إلى حد ما.

التداول على سلسلة الكتل (blockchain)

هناك العديد من منصات التداول الموزعة (DEX) التي تتيح لك تداول الرموز المميزة على Ethereum. ومع ذلك، فإنها تميل إلى أن تكون لها أسعار صرف متشابهة بسبب المراجحة.

تعتبر Uniswap (opens in a new tab) منصة تداول لامركزية مستخدمة على نطاق واسع ويمكننا استخدامها لكل من عروض الأسعار (لرؤية القيم النسبية للرموز المميزة) والصفقات.

OpenAI

بالنسبة لنموذج لغوي كبير، اخترت أن أبدأ مع OpenAI (opens in a new tab). لتشغيل التطبيق في هذا البرنامج التعليمي، ستحتاج إلى الدفع للوصول إلى واجهة برمجة التطبيقات API. الحد الأدنى للدفع وهو 5 دولارات هو أكثر من كافٍ.

التطوير، خطوة بخطوة

لتبسيط التطوير، ننتقل على مراحل. كل خطوة هي فرع في GitHub.

البدء

هناك خطوات للبدء في استخدام UNIX أو Linux (بما في ذلك WSL (opens in a new tab))

  1. إذا لم يكن لديك بالفعل، فقم بتنزيل وتثبيت Python (opens in a new tab).

  2. استنسخ مستودع GitHub.

    1git clone https://github.com/qbzzt/260215-ai-agent.git -b 01-getting-started
    2cd 260215-ai-agent
  3. قم بتثبيت uv (opens in a new tab). قد يكون الأمر على نظامك مختلفًا.

    1pipx install uv
  4. قم بتنزيل المكتبات.

    1uv sync
  5. قم بتنشيط البيئة الافتراضية.

    1source .venv/bin/activate
  6. للتحقق من أن Python وWeb3 يعملان بشكل صحيح، قم بتشغيل python3 وزوده بهذا البرنامج. يمكنك إدخاله في الموجه >>>؛ ليست هناك حاجة لإنشاء ملف.

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

القراءة من سلسلة الكتل (blockchain)

الخطوة التالية هي القراءة من سلسلة الكتل (blockchain). للقيام بذلك، تحتاج إلى التغيير إلى الفرع 02-read-quote ثم استخدام uv لتشغيل البرنامج.

1git checkout 02-read-quote
2uv run agent.py

يجب أن تتلقى قائمة بكائنات Quote، لكل منها طابع زمني وسعر وأصل (حاليًا دائمًا WETH/USDC).

إليك شرح سطر بسطر.

1from web3 import Web3
2from web3.contract import Contract
3from decimal import Decimal, ROUND_HALF_UP
4from dataclasses import dataclass
5from datetime import datetime, timezone
6from pprint import pprint
7import time
8import functools
9import sys
إظهار الكل

استورد المكتبات التي نحتاجها. يتم شرحها أدناه عند استخدامها.

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

يستبدل print في لغة Python بإصدار يقوم دائمًا بتفريغ الإخراج على الفور. هذا مفيد في نص برمجي طويل الأمد لأننا لا نريد انتظار تحديثات الحالة أو إخراج تصحيح الأخطاء.

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

عنوان URL للوصول إلى الشبكة الرئيسية (mainnet). يمكنك الحصول على واحدة من العقدة كخدمة أو استخدام إحدى تلك المعلن عنها في Chainlist (opens in a new tab).

1BLOCK_TIME_SECONDS = 12
2MINUTE_BLOCKS = int(60 / BLOCK_TIME_SECONDS)
3HOUR_BLOCKS = MINUTE_BLOCKS * 60
4DAY_BLOCKS = HOUR_BLOCKS * 24

تحدث كتلة الشبكة الرئيسية لـ Ethereum عادةً كل اثنتي عشرة ثانية، لذا فهذه هي عدد الكتل التي نتوقع حدوثها في فترة زمنية. لاحظ أن هذا ليس رقمًا دقيقًا. عندما يكون مقدم الكتلة معطلاً، يتم تخطي تلك الكتلة، ويكون وقت الكتلة التالية 24 ثانية. إذا أردنا الحصول على الكتلة الدقيقة للطابع الزمني، فسنستخدم البحث الثنائي (opens in a new tab). ومع ذلك، هذا قريب بما فيه الكفاية لأغراضنا. التنبؤ بالمستقبل ليس علمًا دقيقًا.

1CYCLE_BLOCKS = DAY_BLOCKS

حجم الدورة. نراجع عروض الأسعار مرة واحدة في كل دورة ونحاول تقدير القيمة في نهاية الدورة التالية.

1# عنوان المجمع الذي نقرأ منه
2WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")

تؤخذ قيم عروض الأسعار من مجمع Uniswap 3 USDC/WETH على العنوان 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 (opens in a new tab). هذا العنوان موجود بالفعل في شكل المجموع الاختباري، ولكن من الأفضل استخدام Web3.to_checksum_address (opens in a new tab) لجعل الكود قابلاً لإعادة الاستخدام.

1POOL_ABI = [
2 { "name": "slot0", ... },
3 { "name": "token0", ... },
4 { "name": "token1", ... },
5]
6
7ERC20_ABI = [
8 { "name": "symbol", ... },
9 { "name": "decimals", ... }
10]
إظهار الكل

هذه هي واجهات التطبيق الثنائية ABIs (opens in a new tab) للعقدين اللذين نحتاج إلى الاتصال بهما. للحفاظ على الكود موجزًا، نقوم بتضمين الوظائف التي نحتاج إلى استدعائها فقط.

1w3 = Web3(Web3.HTTPProvider(MAINNET_URL))

قم ببدء مكتبة Web3 (opens in a new tab) واتصل بعقدة Ethereum.

1@dataclass(frozen=True)
2class ERC20Token:
3 address: str
4 symbol: str
5 decimals: int
6 contract: Contract

هذه إحدى طرق إنشاء فئة بيانات في لغة Python. يُستخدم نوع البيانات Contract (opens in a new tab) للاتصال بالعقد. لاحظ (frozen=True). في لغة بايثون Python، تُعرَّف القيم المنطقية booleans (opens in a new tab) على أنها True أو False، بأحرف كبيرة. فئة البيانات هذه frozen، مما يعني أنه لا يمكن تعديل الحقول.

لاحظ المسافة البادئة. على عكس اللغات المشتقة من C (opens in a new tab)، تستخدم لغة Python المسافة البادئة للإشارة إلى الكتل. يعرف مفسر Python أن التعريف التالي ليس جزءًا من فئة البيانات هذه لأنه لا يبدأ بنفس المسافة البادئة لحقول فئة البيانات.

1@dataclass(frozen=True)
2class PoolInfo:
3 address: str
4 token0: ERC20Token
5 token1: ERC20Token
6 contract: Contract
7 asset: str
8 decimal_factor: Decimal = 1

يُستخدم النوع Decimal (opens in a new tab) للتعامل مع الكسور العشرية بدقة.

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

هذه هي طريقة تعريف دالة في Python. التعريف بمسافة بادئة لإظهار أنه لا يزال جزءًا من PoolInfo.

في دالة تعد جزءًا من فئة بيانات، يكون المعامل الأول دائمًا هو self، وهو مثيل فئة البيانات الذي تم استدعاؤه هنا. هنا يوجد معلمة أخرى، رقم الكتلة.

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

إذا استطعنا قراءة المستقبل، فلن نحتاج إلى الذكاء الاصطناعي للتداول.

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

بناء الجملة لاستدعاء دالة على آلة الإيثريوم الافتراضية EVM من Web3 هو كالتالي: <contract object>.functions.<function name>().call(<parameters>). يمكن أن تكون المعلمات هي معلمات دالة آلة الإيثريوم الافتراضية EVM (إن وجدت؛ هنا لا توجد) أو معلمات مسماة (opens in a new tab) لتعديل سلوك سلسلة الكتل (blockchain). هنا نستخدم واحدًا، block_identifier، لتحديد رقم الكتلة الذي نرغب في التشغيل فيه.

النتيجة هي هذه البنية، في شكل مصفوفة (opens in a new tab). القيمة الأولى هي دالة لسعر الصرف بين الرمزين.

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

لتقليل الحسابات على السلسلة، لا يقوم Uniswap v3 بتخزين عامل الصرف الفعلي بل جذره التربيعي. نظرًا لأن آلة إيثريوم الافتراضية (EVM) لا تدعم حسابات النقطة العائمة أو الكسور، فبدلاً من القيمة الفعلية، تكون الاستجابة price&#x22C5296

1 # (token1 لكل token0)
2 return 1/(raw_price * self.decimal_factor)

السعر الخام الذي نحصل عليه هو عدد token0 الذي نحصل عليه مقابل كل token1. في مجمعنا، token0 هو USDC (عملة مستقرة بنفس قيمة الدولار الأمريكي) وtoken1 هو WETH (opens in a new tab). القيمة التي نريدها حقًا هي عدد الدولارات لكل WETH، وليس العكس.

العامل العشري هو النسبة بين العوامل العشرية (opens in a new tab) للرمزين.

1@dataclass(frozen=True)
2class Quote:
3 timestamp: str
4 price: Decimal
5 asset: str

تمثل فئة البيانات هذه عرض سعر: سعر أصل معين في نقطة زمنية معينة. في هذه المرحلة، حقل asset غير ذي صلة لأننا نستخدم مجمعًا واحدًا وبالتالي لدينا أصل واحد. ومع ذلك، سنضيف المزيد من الأصول لاحقًا.

1def read_token(address: str) -> ERC20Token:
2 token = w3.eth.contract(address=address, abi=ERC20_ABI)
3 symbol = token.functions.symbol().call()
4 decimals = token.functions.decimals().call()
5
6 return ERC20Token(
7 address=address,
8 symbol=symbol,
9 decimals=decimals,
10 contract=token
11 )
إظهار الكل

تأخذ هذه الدالة عنوانًا وتعيد معلومات حول عقد الرمز المميز في ذلك العنوان. لإنشاء عقد Web3 Contract جديد (opens in a new tab)، نوفر العنوان وواجهة التطبيق الثنائية ABI لـ w3.eth.contract.

1def read_pool(address: str) -> PoolInfo:
2 pool_contract = w3.eth.contract(address=address, abi=POOL_ABI)
3 token0Address = pool_contract.functions.token0().call()
4 token1Address = pool_contract.functions.token1().call()
5 token0 = read_token(token0Address)
6 token1 = read_token(token1Address)
7
8 return PoolInfo(
9 address=address,
10 asset=f"{token1.symbol}/{token0.symbol}",
11 token0=token0,
12 token1=token1,
13 contract=pool_contract,
14 decimal_factor=Decimal(10) ** Decimal(token0.decimals - token1.decimals)
15 )
إظهار الكل

تعيد هذه الدالة كل ما نحتاجه حول مجمع معين (opens in a new tab). البناء f"<string>" هو سلسلة نصية منسقة (opens in a new tab).

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

الحصول على كائن Quote. القيمة الافتراضية لـ block_number هي None (بدون قيمة).

1 if block_number is None:
2 block_number = w3.eth.block_number

إذا لم يتم تحديد رقم كتلة، فاستخدم w3.eth.block_number، وهو أحدث رقم كتلة. هذا هو بناء الجملة لـعبارة if (opens in a new tab).

قد يبدو أنه كان من الأفضل تعيين القيمة الافتراضية على w3.eth.block_number، ولكن هذا لا يعمل جيدًا لأنه سيكون رقم الكتلة في وقت تعريف الدالة. في وكيل يعمل لفترة طويلة، ستكون هذه مشكلة.

1 block = w3.eth.get_block(block_number)
2 price = pool.get_price(block_number)
3 return Quote(
4 timestamp=datetime.fromtimestamp(block.timestamp, timezone.utc).isoformat(),
5 price=price.quantize(Decimal("0.01")),
6 asset=pool.asset
7 )

استخدم مكتبة datetime (opens in a new tab) لتنسيقها إلى تنسيق قابل للقراءة من قبل البشر ونماذج اللغة الكبيرة (LLMs). استخدم Decimal.quantize (opens in a new tab) لتقريب القيمة إلى منزلتين عشريتين.

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

في لغة Python، يمكنك تعريف قائمة (opens in a new tab) لا يمكن أن تحتوي إلا على نوع معين باستخدام list[<type>].

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

في Python، تتكرر حلقة for (opens in a new tab) عادةً على قائمة. تأتي قائمة أرقام الكتل التي يتم البحث عن عروض الأسعار فيها من range (opens in a new tab).

1 quote = get_quote(pool, block)
2 quotes.append(quote)
3 return quotes

لكل رقم كتلة، احصل على كائن Quote وأضفه إلى قائمة quotes. ثم قم بإرجاع تلك القائمة.

1pool = read_pool(WETHUSDC_ADDRESS)
2quotes = get_quotes(
3 pool,
4 w3.eth.block_number - 12*CYCLE_BLOCKS,
5 w3.eth.block_number,
6 CYCLE_BLOCKS
7)
8
9pprint(quotes)
إظهار الكل

هذا هو الكود الرئيسي للبرنامج النصي. اقرأ معلومات المجمع، واحصل على اثني عشر عرض سعر، وقم بطباعتها بشكل جميل pprint (opens in a new tab).

إنشاء موجه

بعد ذلك، نحتاج إلى تحويل هذه القائمة من عروض الأسعار إلى موجه لـ LLM والحصول على قيمة مستقبلية متوقعة.

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

سيكون الإخراج الآن موجهًا إلى LLM، على غرار:

1بالنظر إلى عروض الأسعار هذه:
2الأصل: WETH/USDC
3 2026-01-20T16:34 3016.21
4 .
5 .
6 .
7 2026-02-01T17:49 2299.10
8
9الأصل: WBTC/WETH
10 2026-01-20T16:34 29.84
11 .
12 .
13 .
14 2026-02-01T17:50 33.46
15
16
17ماذا تتوقع أن تكون قيمة WETH/USDC في الوقت 2026-02-02T17:56؟
18
19قدم إجابتك كرقم واحد مقرب إلى منزلتين عشريتين،
20بدون أي نص آخر.
إظهار الكل

لاحظ أن هناك عروض أسعار لأصلين هنا، WETH/USDC وWBTC/WETH. قد تؤدي إضافة عروض أسعار من أصل آخر إلى تحسين دقة التنبؤ.

كيف يبدو الموجه

يحتوي هذا الموجه على ثلاثة أقسام، وهي شائعة جدًا في موجهات LLM.

  1. المعلومات. تحتوي نماذج اللغة الكبيرة على الكثير من المعلومات من تدريبها، لكنها عادة لا تملك الأحدث. هذا هو سبب حاجتنا إلى استرداد أحدث عروض الأسعار هنا. تسمى إضافة المعلومات إلى موجه التوليد المعزز بالاسترداد (RAG) (opens in a new tab).

  2. السؤال الفعلي. هذا ما نريد أن نعرفه.

  3. تعليمات تنسيق الإخراج. عادة، سيعطينا نموذج اللغة الكبير تقديرًا مع شرح لكيفية وصوله إليه. هذا أفضل للبشر، لكن برنامج الكمبيوتر يحتاج فقط إلى الخلاصة.

شرح الكود

ها هو الكود الجديد.

1from datetime import datetime, timezone, timedelta

نحتاج إلى تزويد LLM بالوقت الذي نريد تقديرًا له. للحصول على وقت "n دقيقة/ساعة/يوم" في المستقبل، نستخدم فئة timedelta (opens in a new tab).

1# عناوين المجمعات التي نقرأها
2WETHUSDC_ADDRESS = Web3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
3WETHWBTC_ADDRESS = Web3.to_checksum_address("0xCBCdF9626bC03E24f779434178A73a0B4bad62eD")

لدينا مجمعان نحتاج إلى قراءتهما.

1@dataclass(frozen=True)
2class PoolInfo:
3 .
4 .
5 .
6 reverse: bool = False
7
8 def get_price(self, block: int) -> Decimal:
9 assert block <= w3.eth.block_number, "Block is in the future"
10 sqrt_price_x96 = Decimal(self.contract.functions.slot0().call(block_identifier=block)[0])
11 raw_price = (sqrt_price_x96 / Decimal(2**96)) ** 2 # (token1 per token0)
12 if self.reverse:
13 return 1/(raw_price * self.decimal_factor)
14 else:
15 return raw_price * self.decimal_factor
إظهار الكل

في مجمع WETH/USDC، نريد أن نعرف كم عدد token0 (USDC) الذي نحتاجه لشراء واحد من token1 (WETH). في مجمع WETH/WBTC، نريد أن نعرف كم عدد token1 (WETH) الذي نحتاجه لشراء واحد token0 (WBTC، وهو بيتكوين مغلف). نحتاج إلى تتبع ما إذا كانت نسبة المجمع بحاجة إلى عكسها.

1def read_pool(address: str, reverse: bool = False) -> PoolInfo:
2 .
3 .
4 .
5
6 return PoolInfo(
7 .
8 .
9 .
10
11 asset= f"{token1.symbol}/{token0.symbol}" if reverse else f"{token0.symbol}/{token1.symbol}",
12 reverse=reverse
13 )
إظهار الكل

لمعرفة ما إذا كان المجمع بحاجة إلى عكسه، نحصل على ذلك كمدخل إلى read_pool. أيضًا، يجب إعداد رمز الأصل بشكل صحيح.

إن البناء <a> if <b> else <c> هو المكافئ في لغة Python لـالمشغل الشرطي الثلاثي (opens in a new tab)، والذي سيكون في لغة مشتقة من C <b> ? <a> : <c>.

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

تبني هذه الدالة سلسلة نصية تنسق قائمة بكائنات Quote، بافتراض أنها تنطبق جميعها على نفس الأصل.

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

في Python، تُكتب السلاسل النصية الحرفية متعددة الأسطر (opens in a new tab) على النحو التالي: """ .... """.

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

هنا، نستخدم نمط MapReduce (opens in a new tab) لإنشاء سلسلة نصية لكل قائمة عرض أسعار باستخدام format_quotes، ثم نختصرها إلى سلسلة نصية واحدة لاستخدامها في الموجه.

1What would you expect the value for {asset} to be at time {expected_time}?
2
3Provide your answer as a single number rounded to two decimal places,
4without any other text.
5 """

بقية الموجه كما هو متوقع.

1wethusdc_pool = read_pool(WETHUSDC_ADDRESS, True)
2wethusdc_quotes = get_quotes(
3 wethusdc_pool,
4 w3.eth.block_number - 12*CYCLE_BLOCKS,
5 w3.eth.block_number,
6 CYCLE_BLOCKS,
7)
8
9wethwbtc_pool = read_pool(WETHWBTC_ADDRESS)
10wethwbtc_quotes = get_quotes(
11 wethwbtc_pool,
12 w3.eth.block_number - 12*CYCLE_BLOCKS,
13 w3.eth.block_number,
14 CYCLE_BLOCKS
15)
إظهار الكل

راجع المجمعين واحصل على عروض أسعار من كليهما.

1future_time = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat()[0:16]
2
3print(make_prompt(wethusdc_quotes + wethwbtc_quotes, future_time, wethusdc_pool.asset))

حدد النقطة الزمنية المستقبلية التي نريد التقدير لها، وقم بإنشاء الموجه.

التفاعل مع LLM

بعد ذلك، نقوم بتوجيه LLM فعلي ونحصل على قيمة مستقبلية متوقعة. لقد كتبت هذا البرنامج باستخدام OpenAI، لذا إذا كنت تريد استخدام مزود مختلف، فستحتاج إلى تعديله.

  1. احصل على حساب OpenAI (opens in a new tab)

  2. قم بتمويل الحساب (opens in a new tab)—الحد الأدنى للمبلغ في وقت كتابة هذا التقرير هو 5 دولارات

  3. أنشئ مفتاح واجهة برمجة تطبيقات (opens in a new tab)

  4. في سطر الأوامر، قم بتصدير مفتاح واجهة برمجة التطبيقات حتى يتمكن برنامجك من استخدامه

    1export OPENAI_API_KEY=sk-<بقية المفتاح هنا>
  5. سحب الوكيل وتشغيله

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

ها هو الكود الجديد.

1from openai import OpenAI
2
3open_ai = OpenAI() # يقرأ العميل متغير البيئة OPENAI_API_KEY

قم باستيراد واجهة برمجة تطبيقات OpenAI وإنشاء مثيل لها.

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

استدعاء واجهة برمجة تطبيقات OpenAI (open_ai.chat.completions.create) لإنشاء الاستجابة.

1expected_price = Decimal(response.choices[0].message.content.strip())
2current_price = wethusdc_quotes[-1].price
3
4print ("Current price:", wethusdc_quotes[-1].price)
5print(f"In {future_time}, expected price: {expected_price} USD")
6
7if (expected_price > current_price):
8 print(f"Buy, I expect the price to go up by {expected_price - current_price} USD")
9else:
10 print(f"Sell, I expect the price to go down by {current_price - expected_price} USD")
إظهار الكل

إخراج السعر وتقديم توصية بالشراء أو البيع.

اختبار التنبؤات

الآن بعد أن أصبح بإمكاننا إنشاء تنبؤات، يمكننا أيضًا استخدام البيانات التاريخية لتقييم ما إذا كنا ننتج تنبؤات مفيدة.

1uv run test-predictor.py

النتيجة المتوقعة مشابهة لـ:

1التنبؤ لـ 2026-01-05T19:50: التنبؤ 3138.93 دولارًا أمريكيًا، الحقيقي 3218.92 دولارًا أمريكيًا، الخطأ 79.99 دولارًا أمريكيًا
2التنبؤ لـ 2026-01-06T19:56: التنبؤ 3243.39 دولارًا أمريكيًا، الحقيقي 3221.08 دولارًا أمريكيًا، الخطأ 22.31 دولارًا أمريكيًا
3التنبؤ لـ 2026-01-07T20:02: التنبؤ 3223.24 دولارًا أمريكيًا، الحقيقي 3146.89 دولارًا أمريكيًا، الخطأ 76.35 دولارًا أمريكيًا
4التنبؤ لـ 2026-01-08T20:11: التنبؤ 3150.47 دولارًا أمريكيًا، الحقيقي 3092.04 دولارًا أمريكيًا، الخطأ 58.43 دولارًا أمريكيًا
5.
6.
7.
8التنبؤ لـ 2026-01-31T22:33: التنبؤ 2637.73 دولارًا أمريكيًا، الحقيقي 2417.77 دولارًا أمريكيًا، الخطأ 219.96 دولارًا أمريكيًا
9التنبؤ لـ 2026-02-01T22:41: التنبؤ 2381.70 دولارًا أمريكيًا، الحقيقي 2318.84 دولارًا أمريكيًا، الخطأ 62.86 دولارًا أمريكيًا
10التنبؤ لـ 2026-02-02T22:49: التنبؤ 2234.91 دولارًا أمريكيًا، الحقيقي 2349.28 دولارًا أمريكيًا، الخطأ 114.37 دولارًا أمريكيًا
11متوسط خطأ التنبؤ على مدار 29 تنبؤًا: 83.87103448275862068965517241 دولارًا أمريكيًا
12متوسط التغيير لكل توصية: 4.787931034482758620689655172 دولار أمريكي
13التباين المعياري للتغيرات: 104.42 دولار أمريكي
14أيام مربحة: 51.72%
15أيام خاسرة: 48.28%
إظهار الكل

معظم المختبر مطابق للوكيل، ولكن إليك الأجزاء الجديدة أو المعدلة.

1CYCLES_FOR_TEST = 40 # للاختبار الخلفي، كم عدد الدورات التي نختبرها
2
3# الحصول على الكثير من عروض الأسعار
4wethusdc_pool = read_pool(WETHUSDC_ADDRESS, True)
5wethusdc_quotes = get_quotes(
6 wethusdc_pool,
7 w3.eth.block_number - CYCLE_BLOCKS*CYCLES_FOR_TEST,
8 w3.eth.block_number,
9 CYCLE_BLOCKS,
10)
11
12wethwbtc_pool = read_pool(WETHWBTC_ADDRESS)
13wethwbtc_quotes = get_quotes(
14 wethwbtc_pool,
15 w3.eth.block_number - CYCLE_BLOCKS*CYCLES_FOR_TEST,
16 w3.eth.block_number,
17 CYCLE_BLOCKS
18)
إظهار الكل

نحن ننظر إلى CYCLES_FOR_TEST (المحدد هنا بـ 40) يومًا إلى الوراء.

1# إنشاء تنبؤات والتحقق منها مقابل التاريخ الحقيقي
2
3total_error = Decimal(0)
4changes = []

هناك نوعان من الأخطاء التي نهتم بها. الأول، total_error، هو ببساطة مجموع الأخطاء التي ارتكبها المتنبئ.

لفهم الثاني، changes، نحتاج إلى تذكر غرض الوكيل. ليس التنبؤ بنسبة WETH/USDC (سعر ETH). بل إصدار توصيات البيع والشراء. إذا كان السعر حاليًا 2000 دولار وتنبأ بـ 2010 دولارات غدًا، فإننا لا نمانع إذا كانت النتيجة الفعلية 2020 دولارًا وحققنا أموالًا إضافية. لكننا نمانع إذا تنبأ بـ 2010 دولارات، واشترى ETH بناءً على تلك التوصية، وانخفض السعر إلى 1990 دولارًا.

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

لا يمكننا النظر إلا في الحالات التي يتوفر فيها السجل الكامل (القيم المستخدمة للتنبؤ والقيمة الحقيقية لمقارنتها بها). وهذا يعني أن أحدث حالة يجب أن تكون تلك التي بدأت قبل CYCLES_BACK.

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

استخدم شرائح (opens in a new tab) للحصول على نفس عدد العينات التي يستخدمها الوكيل. الكود بين هنا والجزء التالي هو نفس كود الحصول على التنبؤ الموجود لدينا في الوكيل.

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

احصل على السعر المتوقع والسعر الحقيقي والسعر وقت التنبؤ. نحتاج إلى السعر وقت التنبؤ لتحديد ما إذا كانت التوصية بالشراء أو البيع.

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

احسب الخطأ، وأضفه إلى الإجمالي.

1 recomended_action = 'buy' if predicted_price > prediction_time_price else 'sell'
2 price_increase = real_price - prediction_time_price
3 changes.append(price_increase if recomended_action == 'buy' else -price_increase)

بالنسبة لـ changes، نريد التأثير النقدي لشراء أو بيع عملة ETH واحدة. لذا أولاً، نحتاج إلى تحديد التوصية، ثم تقييم كيفية تغير السعر الفعلي، وما إذا كانت التوصية قد حققت ربحًا (تغيير إيجابي) أو كلفت أموالًا (تغيير سلبي).

1print (f"Mean prediction error over {len(wethusdc_quotes)-CYCLES_BACK} predictions: {total_error / Decimal(len(wethusdc_quotes)-CYCLES_BACK)} USD")
2
3length_changes = Decimal(len(changes))
4mean_change = sum(changes, Decimal(0)) / length_changes
5print (f"Mean change per recommendation: {mean_change} USD")
6var = sum((x - mean_change) ** 2 for x in changes) / length_changes
7print (f"Standard variance of changes: {var.sqrt().quantize(Decimal("0.01"))} USD")

تقرير النتائج.

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

استخدم filter (opens in a new tab) لحساب عدد الأيام المربحة وعدد الأيام المكلفة. النتيجة هي كائن مرشح، والذي نحتاج إلى تحويله إلى قائمة للحصول على الطول.

إرسال المعاملات

الآن نحن بحاجة إلى إرسال المعاملات بالفعل. ومع ذلك، لا أريد إنفاق أموال حقيقية في هذه المرحلة، قبل إثبات النظام. بدلاً من ذلك، سننشئ انقسام محلي للشبكة الرئيسية، و "نتداول" على تلك الشبكة.

فيما يلي خطوات إنشاء انقسام محلي وتمكين التداول.

  1. قم بتثبيت Foundry (opens in a new tab)

  2. ابدأ anvil (opens in a new tab)

    1anvil --fork-url https://eth.drpc.org --block-time 12

    يستمع anvil على عنوان URL الافتراضي لـ Foundry، http://localhost:8545، (opens in a new tab) لذلك لا نحتاج إلى تحديد عنوان URL لأمر cast الذي نستخدمه لمعالجة سلسلة الكتل (blockchain).

  3. عند التشغيل في anvil، هناك عشرة حسابات اختبار تحتوي على ETH—قم بتعيين متغيرات البيئة للحساب الأول

    1PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
    2ADDRESS=`cast wallet address $PRIVATE_KEY`
  4. هذه هي العقود التي نحتاج إلى استخدامها. SwapRouter (opens in a new tab) هو عقد Uniswap v3 الذي نستخدمه للتداول الفعلي. يمكننا التداول مباشرة من خلال المجمع، ولكن هذا أسهل بكثير.

    المتغيران السفليان هما مسارات Uniswap v3 المطلوبة للتبديل بين WETH وUSDC.

    1WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
    2USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
    3POOL_ADDRESS=0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640
    4SWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564
    5WETH_TO_USDC=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
    6USDC_TO_WETH=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
  5. يحتوي كل حساب من حسابات الاختبار على 10000 ETH. استخدم عقد WETH لتغليف 1000 ETH للحصول على 1000 WETH للتداول.

    1cast send $WETH_ADDRESS "deposit()" --value 1000ether --private-key $PRIVATE_KEY
  6. استخدم SwapRouter لتداول 500 WETH مقابل USDC.

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

    ينشئ استدعاء approve بدلًا يسمح لـ SwapRouter بإنفاق بعض رموزنا. لا يمكن للعقود مراقبة الأحداث، لذلك إذا قمنا بنقل الرموز المميزة مباشرة إلى عقد SwapRouter، فلن يعرف أنه تم الدفع له. بدلاً من ذلك، نسمح لعقد SwapRouter بإنفاق مبلغ معين، ثم يقوم SwapRouter بذلك. يتم ذلك من خلال دالة يستدعيها SwapRouter، لذلك يعرف ما إذا كانت ناجحة.

  7. تحقق من أن لديك ما يكفي من كلا الرمزين.

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

الآن بعد أن أصبح لدينا WETH وUSDC، يمكننا تشغيل الوكيل بالفعل.

1git checkout 05-trade
2uv run agent.py

سيبدو الإخراج مشابهًا لـ:

1(ai-trading-agent) qbzzt@Ori-Cloudnomics:~/260215-ai-agent$ uv run agent.py
2السعر الحالي: 1843.16
3في 2026-02-06T23:07، السعر المتوقع: 1724.41 دولار أمريكي
4أرصدة الحسابات قبل التداول:
5رصيد USDC: 927301.578272
6رصيد WETH: 500
7بيع، أتوقع أن ينخفض السعر بمقدار 118.75 دولارًا أمريكيًا
8تم إرسال معاملة الموافقة: 74e367ddbb407c1aaf567d87aa5863049991b1d2aa092b6b85195d925e2bd41f
9تم تعدين معاملة الموافقة.
10تم إرسال معاملة البيع: fad1bcf938585c9e90364b26ac7a80eea9efd34c37e5db81e58d7655bcae28bf
11تم تعدين معاملة البيع.
12أرصدة الحسابات بعد التداول:
13رصيد USDC: 929143.797116
14رصيد WETH: 499
إظهار الكل

لاستخدامه بالفعل، تحتاج إلى بعض التغييرات الطفيفة.

  • في السطر 14، قم بتغيير MAINNET_URL إلى نقطة وصول حقيقية، مثل https://eth.drpc.org
  • في السطر 28، قم بتغيير PRIVATE_KEY إلى مفتاحك الخاص
  • ما لم تكن ثريًا جدًا ويمكنك شراء أو بيع 1 ETH كل يوم لوكيل غير مثبت، فقد ترغب في تغيير 29 لتقليل WETH_TRADE_AMOUNT

شرح الكود

ها هو الكود الجديد.

1SWAP_ROUTER_ADDRESS=Web3.to_checksum_address("0xE592427A0AEce92De3Edee1F18E0157C05861564")
2WETH_TO_USDC=bytes.fromhex("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20001F4A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
3USDC_TO_WETH=bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480001F4C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
4PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"

نفس المتغيرات التي استخدمناها في الخطوة 4.

1WETH_TRADE_AMOUNT=1

المبلغ المراد تداوله.

1ERC20_ABI = [
2 { "name": "symbol", ... },
3 { "name": "decimals", ... },
4 { "name": "balanceOf", ...},
5 { "name": "approve", ...}
6]

للتداول فعليًا، نحتاج إلى وظيفة approve. نريد أيضًا إظهار الأرصدة قبل وبعد، لذلك نحتاج أيضًا إلى balanceOf.

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

في واجهة التطبيق الثنائية SwapRouter، نحتاج فقط إلى exactInput. هناك وظيفة ذات صلة، exactOutput، يمكننا استخدامها لشراء WETH واحد بالضبط، ولكن من أجل البساطة نستخدم exactInput فقط في كلتا الحالتين.

1account = w3.eth.account.from_key(PRIVATE_KEY)
2swap_router = w3.eth.contract(
3 address=SWAP_ROUTER_ADDRESS,
4 abi=SWAP_ROUTER_ABI
5)

تعريفات Web3 للحساب account (https://web3py.readthedocs.io/en/stable/web3.eth.account.html (opens in a new tab)) وعقد SwapRouter.

1def txn_params() -> dict:
2 return {
3 "from": account.address,
4 "value": 0,
5 "gas": 300000,
6 "nonce": w3.eth.get_transaction_count(account.address),
7 }

معلمات المعاملة. نحتاج إلى وظيفة هنا لأن النون (opens in a new tab) يجب أن يتغير في كل مرة.

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

الموافقة على بدل رمز مميز لـ SwapRouter.

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

هذه هي الطريقة التي نرسل بها معاملة في Web3. أولاً نستخدم كائن Contract (https://web3py.readthedocs.io/en/stable/web3.contract.html (opens in a new tab)) لبناء المعاملة. ثم نستخدم web3.eth.account.sign_transaction (https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-contract-transaction (opens in a new tab)) لتوقيع المعاملة، باستخدام PRIVATE_KEY. أخيرًا، نستخدم w3.eth.send_raw_transaction (https://web3py.readthedocs.io/en/stable/transactions.html#chapter-2-w3-eth-send-raw-transaction (opens in a new tab)) لإرسال المعاملة.

1 print(f"Approve transaction sent: {tx_hash.hex()}")
2 w3.eth.wait_for_transaction_receipt(tx_hash)
3 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 (opens in a new tab)) حتى يتم تعدين المعاملة. يعيد الإيصال إذا لزم الأمر.

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

هذه هي المعلمات عند بيع WETH.

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

على عكس SELL_PARAMS، يمكن أن تتغير معلمات الشراء. مبلغ الإدخال هو تكلفة 1 WETH، كما هو متاح في quote.

1def buy(quote: Quote):
2 buy_params = make_buy_params(quote)
3 approve_token(wethusdc_pool.token0.contract, buy_params["amountIn"])
4 txn = swap_router.functions.exactInput(buy_params).build_transaction(txn_params())
5 signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY)
6 tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)
7 print(f"Buy transaction sent: {tx_hash.hex()}")
8 w3.eth.wait_for_transaction_receipt(tx_hash)
9 print("Buy transaction mined.")
10
11
12def sell():
13 approve_token(wethusdc_pool.token1.contract,
14 WETH_TRADE_AMOUNT * 10**wethusdc_pool.token1.decimals)
15 txn = swap_router.functions.exactInput(SELL_PARAMS).build_transaction(txn_params())
16 signed_txn = w3.eth.account.sign_transaction(txn, private_key=PRIVATE_KEY)
17 tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)
18 print(f"Sell transaction sent: {tx_hash.hex()}")
19 w3.eth.wait_for_transaction_receipt(tx_hash)
20 print("Sell transaction mined.")
إظهار الكل

وظائف buy() و sell() متطابقة تقريبًا. أولاً، نوافق على بدل كافٍ لـ SwapRouter، ثم نستدعيه بالمسار والمبلغ الصحيحين.

1def balances():
2 token0_balance = wethusdc_pool.token0.contract.functions.balanceOf(account.address).call()
3 token1_balance = wethusdc_pool.token1.contract.functions.balanceOf(account.address).call()
4
5 print(f"{wethusdc_pool.token0.symbol} Balance: {Decimal(token0_balance) / Decimal(10 ** wethusdc_pool.token0.decimals)}")
6 print(f"{wethusdc_pool.token1.symbol} Balance: {Decimal(token1_balance) / Decimal(10 ** wethusdc_pool.token1.decimals)}")

تقرير أرصدة المستخدم بالعملتين.

1print("أرصدة الحساب قبل التداول:")
2balances()
3
4if (expected_price > current_price):
5 print(f"شراء، أتوقع أن يرتفع السعر بمقدار {expected_price - current_price} دولار أمريكي")
6 buy(wethusdc_quotes[-1])
7else:
8 print(f"بيع، أتوقع أن ينخفض السعر بمقدار {current_price - expected_price} دولار أمريكي")
9 sell()
10
11print("أرصدة الحساب بعد التداول:")
12balances()
إظهار الكل

يعمل هذا الوكيل حاليًا مرة واحدة فقط. ومع ذلك، يمكنك تغييره للعمل بشكل مستمر إما عن طريق تشغيله من crontab (opens in a new tab) أو عن طريق تغليف الأسطر 368-400 في حلقة واستخدام time.sleep (opens in a new tab) للانتظار حتى يحين وقت الدورة التالية.

التحسينات الممكنة

هذه ليست نسخة إنتاج كاملة؛ إنها مجرد مثال لتعليم الأساسيات. فيما يلي بعض الأفكار للتحسينات.

تداول أذكى

هناك حقيقتان مهمتان يتجاهلهما الوكيل عند تحديد ما يجب القيام به.

  • حجم التغيير المتوقع. يبيع الوكيل كمية ثابتة من WETH إذا كان من المتوقع أن ينخفض السعر، بغض النظر عن حجم الانخفاض. يمكن القول إنه من الأفضل تجاهل التغييرات الطفيفة والبيع بناءً على مدى توقعنا لانخفاض السعر.
  • المحفظة الحالية. إذا كانت 10% من محفظتك في WETH وتعتقد أن السعر سيرتفع، فمن المحتمل أن يكون من المنطقي شراء المزيد. ولكن إذا كانت 90% من محفظتك في WETH، فقد تكون معرضًا بشكل كافٍ، وليست هناك حاجة لشراء المزيد. والعكس صحيح إذا كنت تتوقع أن ينخفض السعر.

ماذا لو كنت تريد أن تبقي استراتيجية التداول الخاصة بك سرية؟

يمكن لبائعي الذكاء الاصطناعي رؤية الاستعلامات التي ترسلها إلى نماذج اللغة الكبيرة الخاصة بهم، مما قد يكشف عن نظام التداول العبقري الذي طورته مع وكيلك. نظام التداول الذي يستخدمه عدد كبير جدًا من الأشخاص لا قيمة له لأن عددًا كبيرًا جدًا من الأشخاص يحاولون الشراء عندما تريد الشراء (ويرتفع السعر) ويحاولون البيع عندما تريد البيع (وينخفض السعر).

يمكنك تشغيل LLM محليًا، على سبيل المثال، باستخدام LM-Studio (opens in a new tab)، لتجنب هذه المشكلة.

من بوت الذكاء الاصطناعي إلى وكيل الذكاء الاصطناعي

يمكنك تقديم حجة جيدة بأن هذا بوت ذكاء اصطناعي، وليس وكيل ذكاء اصطناعي. إنه يطبق استراتيجية بسيطة نسبيًا تعتمد على معلومات محددة مسبقًا. يمكننا تمكين التحسين الذاتي، على سبيل المثال، من خلال توفير قائمة بمجمعات Uniswap v3 وأحدث قيمها والسؤال عن المجموعة التي لديها أفضل قيمة تنبؤية.

الحماية من الانزلاق

حاليًا لا توجد حماية من الانزلاق (opens in a new tab). إذا كان السعر الحالي 2000 دولار، والسعر المتوقع 2100 دولار، فسيقوم الوكيل بالشراء. ومع ذلك، إذا ارتفعت التكلفة إلى 2200 دولار قبل أن يشتري الوكيل، فلن يكون هناك معنى للشراء بعد الآن.

لتنفيذ الحماية من الانزلاق، حدد قيمة amountOutMinimum في السطرين 325 و 334 من agent.py (opens in a new tab).

الخلاصة

نأمل أن تعرف الآن ما يكفي للبدء في استخدام وكلاء الذكاء الاصطناعي. هذه ليست نظرة عامة شاملة على الموضوع؛ فهناك كتب كاملة مخصصة لذلك، لكن هذا يكفي لتبدأ. حظ سعيد!

انظر هنا لمزيد من أعمالي (opens in a new tab).

آخر تحديث للصفحة: 10 فبراير 2026

هل كانت تعليمات الاستخدام هذه مفيدة؟