File size: 10,318 Bytes
ce180e5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
"""
CFA AI Agent - Finance Calculation Tools
This module contains various financial calculation functions for CFA analysis.
"""

import numpy as np
import pandas as pd
from typing import List, Dict, Union, Optional
import yfinance as yf
from langchain.tools import tool


@tool
def calculate_dcf(
    cash_flows: List[float],
    terminal_value: float,
    discount_rate: float
) -> Dict[str, float]:
    """
    Calculate Discounted Cash Flow (DCF) valuation.

    Args:
        cash_flows: List of projected free cash flows
        terminal_value: Terminal value at end of projection period
        discount_rate: Weighted average cost of capital (WACC) as decimal

    Returns:
        Dictionary with NPV, terminal value present value, and total enterprise value
    """
    try:
        if not cash_flows:
            raise ValueError("Cash flows list cannot be empty")
        if discount_rate <= 0:
            raise ValueError("Discount rate must be positive")

        # Calculate present value of cash flows
        pv_cash_flows = []
        for i, cf in enumerate(cash_flows, 1):
            pv = cf / ((1 + discount_rate) ** i)
            pv_cash_flows.append(pv)

        # Calculate present value of terminal value
        years = len(cash_flows)
        pv_terminal = terminal_value / ((1 + discount_rate) ** years)

        # Total enterprise value
        enterprise_value = sum(pv_cash_flows) + pv_terminal

        return {
            "pv_cash_flows": sum(pv_cash_flows),
            "pv_terminal_value": pv_terminal,
            "enterprise_value": enterprise_value,
            "cash_flow_details": pv_cash_flows
        }
    except Exception as e:
        return {"error": f"DCF calculation failed: {str(e)}"}


@tool
def calculate_sharpe_ratio(
    returns: List[float],
    risk_free_rate: float
) -> Dict[str, float]:
    """
    Calculate Sharpe Ratio for risk-adjusted returns.

    Args:
        returns: List of periodic returns (as decimals)
        risk_free_rate: Risk-free rate (as decimal)

    Returns:
        Dictionary with Sharpe ratio, average return, and standard deviation
    """
    try:
        if not returns:
            raise ValueError("Returns list cannot be empty")
        if len(returns) < 2:
            raise ValueError("Need at least 2 return observations")

        returns_array = np.array(returns)

        # Calculate metrics
        avg_return = np.mean(returns_array)
        std_dev = np.std(returns_array, ddof=1)  # Sample standard deviation
        excess_return = avg_return - risk_free_rate

        if std_dev == 0:
            raise ValueError("Standard deviation cannot be zero")

        sharpe_ratio = excess_return / std_dev

        return {
            "sharpe_ratio": sharpe_ratio,
            "average_return": avg_return,
            "standard_deviation": std_dev,
            "excess_return": excess_return,
            "risk_free_rate": risk_free_rate
        }
    except Exception as e:
        return {"error": f"Sharpe ratio calculation failed: {str(e)}"}


@tool
def compare_pe_ratios(ticker1: str, ticker2: str) -> Dict[str, Union[float, str]]:
    """
    Compare P/E ratios of two stocks using real-time data.

    Args:
        ticker1: First stock ticker symbol
        ticker2: Second stock ticker symbol

    Returns:
        Dictionary with P/E ratios and comparison analysis
    """
    try:
        # Fetch stock data
        stock1 = yf.Ticker(ticker1.upper())
        stock2 = yf.Ticker(ticker2.upper())

        # Get info
        info1 = stock1.info
        info2 = stock2.info

        # Extract P/E ratios
        pe1 = info1.get('trailingPE') or info1.get('forwardPE')
        pe2 = info2.get('trailingPE') or info2.get('forwardPE')

        if pe1 is None or pe2 is None:
            return {"error": f"Could not retrieve P/E ratios for {ticker1} or {ticker2}"}

        # Calculate comparison metrics
        pe_difference = pe1 - pe2
        pe_ratio = pe1 / pe2 if pe2 != 0 else None

        # Determine which is more expensive
        comparison = "equal"
        if pe1 > pe2:
            comparison = f"{ticker1} is more expensive"
        elif pe1 < pe2:
            comparison = f"{ticker2} is more expensive"

        return {
            f"{ticker1}_pe": pe1,
            f"{ticker2}_pe": pe2,
            "pe_difference": pe_difference,
            "pe_ratio": pe_ratio,
            "comparison": comparison,
            f"{ticker1}_name": info1.get('longName', ticker1),
            f"{ticker2}_name": info2.get('longName', ticker2)
        }
    except Exception as e:
        return {"error": f"P/E comparison failed: {str(e)}"}


@tool
def calculate_beta(ticker: str, market_ticker: str = "^GSPC", period: str = "2y") -> Dict[str, float]:
    """
    Calculate beta coefficient for a stock relative to market.

    Args:
        ticker: Stock ticker symbol
        market_ticker: Market index ticker (default S&P 500)
        period: Time period for calculation

    Returns:
        Dictionary with beta, correlation, and other metrics
    """
    try:
        # Fetch data
        stock = yf.Ticker(ticker.upper())
        market = yf.Ticker(market_ticker)

        # Get historical data
        stock_data = stock.history(period=period)
        market_data = market.history(period=period)

        if stock_data.empty or market_data.empty:
            raise ValueError("Could not fetch historical data")

        # Calculate returns
        stock_returns = stock_data['Close'].pct_change().dropna()
        market_returns = market_data['Close'].pct_change().dropna()

        # Align data
        aligned_data = pd.concat([stock_returns, market_returns], axis=1, join='inner')
        aligned_data.columns = ['stock', 'market']
        aligned_data = aligned_data.dropna()

        if len(aligned_data) < 20:
            raise ValueError("Insufficient data points for beta calculation")

        # Calculate beta
        covariance = np.cov(aligned_data['stock'], aligned_data['market'])[0][1]
        market_variance = np.var(aligned_data['market'], ddof=1)
        beta = covariance / market_variance

        # Calculate correlation
        correlation = np.corrcoef(aligned_data['stock'], aligned_data['market'])[0][1]

        return {
            "beta": beta,
            "correlation": correlation,
            "stock_volatility": np.std(aligned_data['stock'], ddof=1),
            "market_volatility": np.std(aligned_data['market'], ddof=1),
            "data_points": len(aligned_data),
            "period": period
        }
    except Exception as e:
        return {"error": f"Beta calculation failed: {str(e)}"}


@tool
def calculate_wacc(
    cost_of_equity: float,
    cost_of_debt: float,
    tax_rate: float,
    market_value_equity: float,
    market_value_debt: float
) -> Dict[str, float]:
    """
    Calculate Weighted Average Cost of Capital (WACC).

    Args:
        cost_of_equity: Cost of equity as decimal
        cost_of_debt: Cost of debt as decimal
        tax_rate: Corporate tax rate as decimal
        market_value_equity: Market value of equity
        market_value_debt: Market value of debt

    Returns:
        Dictionary with WACC and component calculations
    """
    try:
        total_value = market_value_equity + market_value_debt

        if total_value <= 0:
            raise ValueError("Total market value must be positive")

        # Calculate weights
        weight_equity = market_value_equity / total_value
        weight_debt = market_value_debt / total_value

        # Calculate after-tax cost of debt
        after_tax_cost_debt = cost_of_debt * (1 - tax_rate)

        # Calculate WACC
        wacc = (weight_equity * cost_of_equity) + (weight_debt * after_tax_cost_debt)

        return {
            "wacc": wacc,
            "weight_equity": weight_equity,
            "weight_debt": weight_debt,
            "after_tax_cost_debt": after_tax_cost_debt,
            "cost_of_equity": cost_of_equity,
            "cost_of_debt": cost_of_debt,
            "tax_rate": tax_rate
        }
    except Exception as e:
        return {"error": f"WACC calculation failed: {str(e)}"}


@tool
def financial_ratios_analysis(ticker: str) -> Dict[str, Union[float, str]]:
    """
    Perform comprehensive financial ratios analysis for a stock.

    Args:
        ticker: Stock ticker symbol

    Returns:
        Dictionary with various financial ratios and metrics
    """
    try:
        stock = yf.Ticker(ticker.upper())
        info = stock.info

        # Extract key metrics
        ratios = {
            "ticker": ticker.upper(),
            "company_name": info.get('longName', 'N/A'),

            # Valuation ratios
            "pe_ratio": info.get('trailingPE'),
            "forward_pe": info.get('forwardPE'),
            "price_to_book": info.get('priceToBook'),
            "price_to_sales": info.get('priceToSalesTrailing12Months'),
            "peg_ratio": info.get('pegRatio'),

            # Profitability ratios
            "profit_margin": info.get('profitMargins'),
            "operating_margin": info.get('operatingMargins'),
            "roe": info.get('returnOnEquity'),
            "roa": info.get('returnOnAssets'),

            # Financial health
            "current_ratio": info.get('currentRatio'),
            "quick_ratio": info.get('quickRatio'),
            "debt_to_equity": info.get('debtToEquity'),
            "total_debt": info.get('totalDebt'),
            "total_cash": info.get('totalCash'),

            # Market data
            "market_cap": info.get('marketCap'),
            "enterprise_value": info.get('enterpriseValue'),
            "beta": info.get('beta'),
            "52_week_high": info.get('fiftyTwoWeekHigh'),
            "52_week_low": info.get('fiftyTwoWeekLow'),

            # Dividend info
            "dividend_yield": info.get('dividendYield'),
            "payout_ratio": info.get('payoutRatio'),

            # Growth metrics
            "earnings_growth": info.get('earningsGrowth'),
            "revenue_growth": info.get('revenueGrowth')
        }

        # Remove None values
        ratios = {k: v for k, v in ratios.items() if v is not None}

        return ratios
    except Exception as e:
        return {"error": f"Financial ratios analysis failed: {str(e)}"}