0Pricing
AI Agents · Lekcja

Narzędzia agenta do analizy portfela

Obliczanie stóp zwrotu, współczynnika Sharpe’a i obsunięcia za pomocą kodu wykonywanego przez agenta.

Narzędzia agenta do analizy portfela to bezpłatna lekcja AI Agents na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Agents, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Agents zawiera 4 lekcji w sumie.

Analiza portfela jako narzędzia agenta

Agenty do analizy finansowej potrzebują zestawu narzędzi obliczeniowych, które LLM może wywoływać: do obliczania stóp zwrotu, miar ryzyka, korelacji oraz sugestii dotyczących rebalansowania. Każde narzędzie jest funkcją języka Python z jasno określoną sygnaturą i zwraca ustrukturyzowany kod JSON, który agent może analizować.

Obliczanie procentowych stóp zwrotu

To podstawa analizy portfela. pct_change() oblicza stopy zwrotu dzień do dnia. Na ich podstawie można obliczyć skumulowaną stopę zwrotu za analizowany okres.

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)
    }

Wskaźnik Sharpe’a

Wskaźnik Sharpe’a mierzy stopę zwrotu skorygowaną o ryzyko, czyli wysokość dodatkowej stopy zwrotu przypadającą na jednostkę ryzyka (zmienności). Wyższa wartość jest lepsza. Wartość Sharpe’a powyżej 1,0 uznaje się za dobrą, a powyżej 2,0 za doskonałą.

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'
    }

Maksymalne obsunięcie

Maksymalne obsunięcie (MDD) to największy spadek wartości portfela od szczytu do dołka. Obrazuje ono najgorszą stratę, jakiej doświadczyłby inwestor, gdyby kupił aktywa na szczycie i sprzedał je w dołku.

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'
    }

Macierz korelacji

Macierz korelacji pokazuje, jak aktywa zmieniają się względem siebie. Niska lub ujemna korelacja między aktywami oznacza korzyść z dywersyfikacji — jeśli jedno aktywo traci na wartości, drugie może nie odnotować spadku. Informacje te pomagają w wyborze aktywów i podejmowaniu decyzji o rebalansowaniu.

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}

Zmienność i beta

Zmienność annualizowana (odchylenie standardowe przeskalowane do 252 dni handlowych) mierzy ryzyko bezwzględne. Beta mierzy ryzyko względem rynku (zazwyczaj indeksu S&P 500): beta > 1 oznacza większą zmienność niż na rynku.

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'
    }

Stopa zwrotu i ryzyko na poziomie portfela

Cały portfel należy analizować, obliczając ważone stopy zwrotu i zmienność. Dzięki dywersyfikacji portfel może charakteryzować się mniejszym ryzykiem niż każdy z jego składników z osobna.

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)}
    }

Sugestia rebalansowania

Odchylenie struktury portfela powstaje, gdy poszczególne aktywa rosną w różnym tempie. Narzędzie do rebalansowania oblicza bieżące i docelowe wagi oraz sugeruje transakcje przywracające docelową alokację.

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']})")

Rejestrowanie narzędzi analitycznych dla LLM

Każdą funkcję analityczną należy zarejestrować jako narzędzie agenta. LLM wywołuje je sekwencyjnie: najpierw pobiera dane, następnie oblicza miary, a na końcu generuje sugestię rebalansowania.

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']}")

Generowanie zrozumiałego podsumowania portfela

Po obliczeniu miar należy polecić LLM przetłumaczenie liczb na praktyczne sformułowania. Miary są danymi wejściowymi, a finalny wynik powinien być zrozumiały dla inwestora, który nie jest specjalistą od metod ilościowych.

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

Buforowanie obliczonych miar

Miary obliczone na podstawie tych samych danych źródłowych będą identyczne. Należy buforować obliczone miary razem z danymi surowymi, aby uniknąć ponownego obliczania wskaźników Sharpe’a i obsunięć przy każdej turze agenta.

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))

Co oznacza wskaźnik Sharpe’a powyżej 2,0?

Wskaźnik Sharpe’a to standardowa miara służąca do porównywania wyników skorygowanych o ryzyko między portfelami i pojedynczymi aktywami.

Podsumowanie narzędzi do analizy portfela

Zestaw narzędzi agenta finansowego obejmuje: pct_change() do obliczania stóp zwrotu, wskaźnik Sharpe’a do oceny wyników skorygowanych o ryzyko, maksymalne obsunięcie do określania najgorszej straty, macierz korelacji do oceny dywersyfikacji, betę do pomiaru wrażliwości na rynek oraz sugestie rebalansowania do utrzymania właściwej struktury portfela.

Każde z tych narzędzi należy zarejestrować jako narzędzie agenta, a obliczone wyniki buforować w celu zwiększenia wydajności.

Często zadawane pytania

Czy lekcja „Narzędzia agenta do analizy portfela” jest bezpłatna?

Tak — pełny tekst „Narzędzia agenta do analizy portfela” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Agents, przejdź na CoddyKit PRO. Kurs AI Agents zawiera 4 lekcji w sumie.

Co nauczysz się w „Narzędzia agenta do analizy portfela”?

Obliczanie stóp zwrotu, współczynnika Sharpe’a i obsunięcia za pomocą kodu wykonywanego przez agenta. Ćwiczysz AI Agents z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Agents?

Nie wymagamy żadnego doświadczenia. AI Agents w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Narzędzia agenta do analizy portfela”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Agents?

Tak. Każda lekcja AI Agents zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Integracja z interfejsami API danych rynkowych
  2. Narzędzia agenta do analizy portfela
  3. Mechanizmy ochronne dotyczące ryzyka i zgodności
  4. Testowanie wsteczne decyzji agenta
← Powrót do AI Agents