0Pricing
AI Agents · درس

أدوات الوكيل لتحليل المحافظ الاستثمارية

حساب العوائد ونسبة Sharpe وتحليل التراجع باستخدام تعليمات برمجية ينفذها الوكيل.

أدوات الوكيل لتحليل المحافظ الاستثمارية درس مجاني في AI Agents على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Agents، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Agents 4 دروس في المجموع.

تحليل المحفظة بوصفه أدوات للوكيل

تحتاج وكلاء التحليل المالي إلى مجموعة من أدوات الحساب التي يستطيع LLM استدعاءها، وتشمل العوائد، ومقاييس المخاطر، والارتباط، واقتراحات إعادة الموازنة. كل أداة هي دالة Python ذات توقيع واضح، وتُرجع JSON منظمًا يستطيع الوكيل الاستدلال عليه.

حساب العوائد المئوية

هذا هو الأساس لتحليل المحفظة. تحسب pct_change() العوائد من يوم إلى آخر. واستنادًا إلى هذه العوائد، احسب العائد التراكمي خلال فترة التحليل.

import pandas as pd
import yfinance as yf

def calculate_returns(ticker: str, period: str = '1y') -> dict:
    hist = yf.Ticker(ticker).history(period=period)['Close']
    if hist.empty:
        return {'error': f'No data for {ticker}'}

    daily_returns = hist.pct_change().dropna()
    cumulative    = (1 + daily_returns).cumprod() - 1

    return {
        'ticker':            ticker,
        'period':            period,
        'total_return_pct':  round(float(cumulative.iloc[-1]) * 100, 2),
        'avg_daily_return':  round(float(daily_returns.mean()) * 100, 4),
        'best_day_pct':      round(float(daily_returns.max()) * 100, 2),
        'worst_day_pct':     round(float(daily_returns.min()) * 100, 2)
    }

نسبة شارب

تقيس نسبة شارب العائد المعدّل حسب المخاطر، أي مقدار العائد الزائد الذي تحصل عليه مقابل كل وحدة من المخاطر (التقلب). وكلما ارتفعت كانت أفضل. وتُعد النسبة التي تتجاوز 1.0 جيدة، والتي تتجاوز 2.0 ممتازة.

import numpy as np
import yfinance as yf

def sharpe_ratio(ticker: str, period: str = '1y',
                 risk_free_annual: float = 0.05) -> dict:
    hist = yf.Ticker(ticker).history(period=period)['Close']
    daily_returns = hist.pct_change().dropna()

    risk_free_daily = risk_free_annual / 252  # annualized to daily
    excess_returns  = daily_returns - risk_free_daily

    if daily_returns.std() == 0:
        return {'error': 'Zero volatility — cannot compute Sharpe ratio'}

    sharpe = float(excess_returns.mean() / excess_returns.std() * np.sqrt(252))
    return {
        'ticker': ticker,
        'sharpe_ratio': round(sharpe, 3),
        'interpretation': 'excellent' if sharpe > 2 else 'good' if sharpe > 1 else 'acceptable' if sharpe > 0 else 'poor'
    }

أقصى تراجع

أقصى تراجع (MDD) هو أكبر انخفاض من قمة إلى قاع في قيمة المحفظة. ويمثل أسوأ خسارة كان سيتكبدها المستثمر لو اشترى عند القمة وباع عند القاع.

import yfinance as yf
import numpy as np

def max_drawdown(ticker: str, period: str = '1y') -> dict:
    hist = yf.Ticker(ticker).history(period=period)['Close']
    if hist.empty:
        return {'error': 'No data'}

    rolling_max = hist.cummax()
    drawdown    = (hist - rolling_max) / rolling_max
    mdd         = float(drawdown.min())
    mdd_date    = str(drawdown.idxmin().date())

    return {
        'ticker':           ticker,
        'max_drawdown_pct': round(mdd * 100, 2),
        'drawdown_date':    mdd_date,
        'interpretation':   'severe' if mdd < -0.30 else 'moderate' if mdd < -0.15 else 'mild'
    }

مصفوفة الارتباط

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

import pandas as pd
import yfinance as yf

def correlation_matrix(tickers: list[str], period: str = '1y') -> dict:
    prices = yf.download(tickers, period=period)['Close']
    returns = prices.pct_change().dropna()
    corr = returns.corr().round(3)

    # Convert to JSON-serializable format
    result = {}
    for t1 in tickers:
        result[t1] = {}
        for t2 in tickers:
            if t1 in corr.columns and t2 in corr.index:
                result[t1][t2] = float(corr.loc[t2, t1])
    return {'correlation': result, 'period': period}

التقلب وبيتا

يقيس التقلب السنوي (الانحراف المعياري مضروبًا في مقياس يستند إلى 252 يوم تداول) المخاطر المطلقة. وتقيس بيتا المخاطر مقارنةً بالسوق (عادةً S&P 500): بيتا > 1 تعني تقلبًا أكبر من تقلب السوق.

import numpy as np
import yfinance as yf

def volatility_and_beta(ticker: str, benchmark: str = 'SPY',
                        period: str = '1y') -> dict:
    data = yf.download([ticker, benchmark], period=period)['Close'].pct_change().dropna()

    if ticker not in data.columns or benchmark not in data.columns:
        return {'error': 'Insufficient data'}

    t_ret = data[ticker]
    b_ret = data[benchmark]

    vol_annualized = float(t_ret.std() * np.sqrt(252))

    cov = np.cov(t_ret, b_ret)
    beta = float(cov[0][1] / cov[1][1]) if cov[1][1] != 0 else 0.0

    return {
        'ticker':          ticker,
        'annual_vol_pct':  round(vol_annualized * 100, 2),
        'beta':            round(beta, 3),
        'market_sensitivity': 'high' if beta > 1.3 else 'market' if beta > 0.7 else 'low'
    }

العائد والمخاطر على مستوى المحفظة

حلّل المحفظة بأكملها من خلال حساب العوائد والتقلبات الموزونة. وقد تكون مخاطر المحفظة أقل من مخاطر أي أصل فردي بفضل التنويع.

import pandas as pd, numpy as np
import yfinance as yf

def portfolio_metrics(holdings: dict, period: str = '1y') -> dict:
    tickers  = list(holdings.keys())
    weights  = np.array(list(holdings.values()))
    weights /= weights.sum()  # normalize to 1.0

    prices  = yf.download(tickers, period=period)['Close'].pct_change().dropna()
    port_ret = (prices * weights).sum(axis=1)
    cov_matrix = prices.cov() * 252  # annualized

    port_return  = float(port_ret.mean() * 252)
    port_vol     = float(np.sqrt(weights @ cov_matrix.values @ weights))
    port_sharpe  = port_return / port_vol if port_vol > 0 else 0

    return {
        'annual_return_pct':  round(port_return * 100, 2),
        'annual_vol_pct':     round(port_vol * 100, 2),
        'sharpe_ratio':       round(port_sharpe, 3),
        'holdings':           {t: round(float(w), 3) for t, w in zip(tickers, weights)}
    }

اقتراح إعادة الموازنة

يحدث انحراف توزيع المحفظة عندما تنمو الأصول المختلفة بمعدلات مختلفة. وتحسب أداة إعادة الموازنة الأوزان الحالية مقارنةً بالأوزان المستهدفة، وتقترح صفقات لإعادة التوزيع إلى النسب المستهدفة.

def rebalancing_suggestion(current_values: dict, target_weights: dict) -> dict:
    total_value = sum(current_values.values())
    current_weights = {t: v / total_value for t, v in current_values.items()}
    trades = {}

    for ticker in target_weights:
        current_w = current_weights.get(ticker, 0)
        target_w  = target_weights[ticker]
        diff_w    = target_w - current_w
        diff_usd  = diff_w * total_value

        if abs(diff_w) > 0.01:  # only suggest if drift > 1%
            trades[ticker] = {
                'action':      'BUY' if diff_usd > 0 else 'SELL',
                'amount_usd':  round(abs(diff_usd), 2),
                'from_weight': round(current_w, 4),
                'to_weight':   round(target_w, 4)
            }
    return {'total_portfolio': round(total_value, 2), 'trades': trades}

if __name__ == '__main__':
    current = {'AAPL': 6000, 'BND': 2000, 'VTI': 2000}
    target = {'AAPL': 0.4, 'BND': 0.3, 'VTI': 0.3}
    result = rebalancing_suggestion(current, target)
    print('Total portfolio: $%s' % result['total_portfolio'])
    for ticker, trade in result['trades'].items():
        print(f"  {ticker}: {trade['action']} ${trade['amount_usd']} "
              f"(weight {trade['from_weight']} -> {trade['to_weight']})")

تسجيل أدوات التحليل لدى LLM

سجّل كل دالة تحليل بوصفها أداة للوكيل. ويستدعيها LLM بالتسلسل: يجلب البيانات أولًا، ثم يحسب المقاييس، ثم ينشئ اقتراح إعادة الموازنة.

FINANCIAL_TOOLS = [
    {
        'type': 'function',
        'function': {
            'name': 'calculate_returns',
            'description': 'Calculate total and daily returns for a ticker.',
            'parameters': {'type': 'object',
                           'properties': {
                               'ticker': {'type': 'string'},
                               'period': {'type': 'string', 'default': '1y'}
                           }, 'required': ['ticker']}
        }
    },
    {
        'type': 'function',
        'function': {
            'name': 'sharpe_ratio',
            'description': 'Compute the annualized Sharpe ratio.',
            'parameters': {'type': 'object',
                           'properties': {
                               'ticker': {'type': 'string'},
                               'period': {'type': 'string', 'default': '1y'}
                           }, 'required': ['ticker']}
        }
    }
]

if __name__ == '__main__':
    print('Registered financial tools for the LLM:')
    for tool in FINANCIAL_TOOLS:
        fn = tool['function']
        print(f"  {fn['name']}: {fn['description']}")

إنشاء ملخص للمحفظة بلغة واضحة

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

import openai, json

client = openai.OpenAI(api_key='YOUR_OPENAI_KEY')

def portfolio_narrative(metrics: dict, holdings: dict) -> str:
    prompt = (
        f'Portfolio metrics:\n{json.dumps(metrics, indent=2)}\n\n'
        f'Holdings: {json.dumps(holdings)}\n\n'
        f'Write a 3-sentence plain-language portfolio assessment for a retail investor.\n'
        f'Include: overall performance, main risk, and one recommendation.'
    )
    resp = client.chat.completions.create(
        model='gpt-4o',
        messages=[{'role': 'user', 'content': prompt}]
    )
    return resp.choices[0].message.content

التخزين المؤقت للمقاييس المحسوبة

ستكون المقاييس المحسوبة من البيانات الأساسية نفسها متطابقة. خزّن المقاييس المحسوبة مؤقتًا إلى جانب البيانات الأولية لتجنب إعادة حساب نسب شارب وأقصى التراجعات في كل دورة للوكيل.

import time, hashlib, json

metrics_cache = {}

def cached_metric(func, *args, ttl: int = 3600):
    key = hashlib.md5(f'{func.__name__}:{args}'.encode()).hexdigest()
    entry = metrics_cache.get(key)
    if entry and time.time() - entry['ts'] < ttl:
        return entry['data']
    result = func(*args)
    metrics_cache[key] = {'data': result, 'ts': time.time()}
    return result

# Usage:
# sharpe = cached_metric(sharpe_ratio, 'AAPL', '1y')

if __name__ == '__main__':
    def slow_average(*nums):
        return sum(nums) / len(nums)

    r1 = cached_metric(slow_average, 1, 2, 3)
    r2 = cached_metric(slow_average, 1, 2, 3)
    print('First call result:', r1)
    print('Second call result (served from cache):', r2)
    print('Cache entries:', len(metrics_cache))

ماذا تشير نسبة شارب التي تتجاوز 2.0؟

تُعد نسبة شارب مقياسًا قياسيًا لمقارنة الأداء المعدّل حسب المخاطر بين المحافظ والأصول الفردية.

مراجعة أدوات تحليل المحفظة

تتضمن مجموعة أدوات الوكيل المالي: pct_change() لحساب العوائد، ونسبة شارب لقياس الأداء المعدّل حسب المخاطر، وأقصى تراجع لقياس أسوأ خسارة، ومصفوفة الارتباط للتنويع، وبيتا لقياس الحساسية للسوق، واقتراحات إعادة الموازنة لصيانة المحفظة.

سجّل كل أداة بوصفها أداة للوكيل، وخزّن النتائج المحسوبة مؤقتًا لتحقيق الكفاءة.

الأسئلة الشائعة

هل درس «أدوات الوكيل لتحليل المحافظ الاستثمارية» مجاني؟

نعم — نص درس «أدوات الوكيل لتحليل المحافظ الاستثمارية» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Agents، انتقل إلى CoddyKit PRO. تتضمن دورة AI Agents 4 دروس في المجموع.

ماذا ستتعلم في «أدوات الوكيل لتحليل المحافظ الاستثمارية»؟

حساب العوائد ونسبة Sharpe وتحليل التراجع باستخدام تعليمات برمجية ينفذها الوكيل. تتمرن على AI Agents مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ AI Agents؟

لا تُشترط خبرة سابقة. AI Agents على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «أدوات الوكيل لتحليل المحافظ الاستثمارية»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس AI Agents هذا؟

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

جميع الدروس في هذه الدورة

  1. تكامل واجهات برمجة تطبيقات بيانات السوق
  2. أدوات الوكيل لتحليل المحافظ الاستثمارية
  3. ضوابط المخاطر والامتثال
  4. اختبار قرارات الوكيل بأثر رجعي
← العودة إلى AI Agents