0Pricing
AI Agents · Урок

Инструменты агента для анализа портфеля

Расчёт доходности, коэффициента Шарпа и просадки с помощью кода, выполняемого агентом.

«Инструменты агента для анализа портфеля» — бесплатный урок 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 уроков всего.

Чему я научусь в уроке «Инструменты агента для анализа портфеля»?

Расчёт доходности, коэффициента Шарпа и просадки с помощью кода, выполняемого агентом. Ты практикуешь AI Agents с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать AI Agents?

Предыдущий опыт не требуется. AI Agents на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Инструменты агента для анализа портфеля»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке AI Agents?

Да. Каждый урок AI Agents включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Интеграция с API рыночных данных
  2. Инструменты агента для анализа портфеля
  3. Ограничители риска и соответствия требованиям
  4. Бэктестинг решений агента
← Назад к AI Agents